Using Active Record and Rails - Relationships
Tags: rails, beginners, ruby
Published: 2020-03-02 23:19:59

Active Record and Rails is meant to make your life easier, but it can also create annoying bugs if you don't understand the reasoning behind the syntax. I'll talk about a bug I ran into recently and explain the nature of relationships in Active Record and what is being interpreted by Rails. 

First, the bug. So I was in the middle of coding a simple web app for heroine(s) and their power(s). I set up a many to many relationship for powers and heroines through a join table (heroine_powers). PRO TIP: you should be using underscores in your SQL and file names, however for classes you should be using CamelCase (models and controllers). I attempted to set the add a power to my heroine by using collection_select (in my heroine create view) on a list of power ids, passing it to heroine_parameters (in my controller). My parameters was set to collect power_ids: []. However, the power was not set when I displayed the heroine. What happened? 

Let's dive into relationships in Active Record to explain where I went wrong. The Heroine class has many Power(s) through HeroinePower(s) and therefore should call powers belonging to a heroine as heroine.powers. Check. Similarly the Power class has many Heroine(s) through HeroinePower(s) and would call them as power.heroines. This means that the heroine class stores its powers as a list of power_id(s) in an array E.g. heroine.powers => [1,4,8]. Check? Sort of ...

Next we can look at collection_select. This function displays a dropdown menu of things to choose in the format: :key, Source, :store_this_key, :display_this_key. That means for my code :power_ids, Power.all, :id, :name, params will contain a key :power_ids with the :id of the selected power. If I put :power_ids[] this would cause an error as collection.select only returns ONE value. It is not looking for an array to place an number in, it is looking to set a value for a key-value pair. 

So, active record has an array of powers but collection.select returns one number. Uh oh. Can this still work? YES. Looking again at heroine_parameters, we can update the key to :power_ids. Even though this is a plural word, the value only contains one number. Why does it need to be plural even though it only stores one number? Because Active Record is looking for a plural value because each heroine HAS MANY powerS (emphasis on the S -- don't really capitalize it). So Active Record can handle receiving a single number for the value of power_ids and create the relationship through the join table. AMAZING. 

But what about the original code power_ids: []? What would this be used for? Well if you are using collection_check_boxes it has the format :power_ids[], Power.all, :id, :name. Why will this work? Because collection_check_boxes wants to give you the ability to select multiple entries. So it needs to pass back this list of power_ids in an array. This means the heroine_parameters would need to have power_ids: [] instead of :power_ids. Is this a problem? No not really. 

As a developer you should never have different collection options for the same attribute of the object. It just doesn't make sense. But if for some reason you REALLY needed this feature, you could use a second collection option through the Power create/edit view and utilize the ability of both Power and Heroine being able to set relationships through the heroine_power join table.

So to recap, many-to-many means AR needs to see that S on the end of things. Collection methods and parameter input syntax need to match. Single to single and plural to plural in your view/controller is independent of AR (though you can't use plural-to-plural with a has_one relationship).