Create join table using migrations or models? - ruby-on-rails

As a Ruby on Rails newbie, I'm going through the Rails Guides and tonight is Active Record Migrations.
After finishing the section on Join Tables (http://guides.rubyonrails.org/active_record_migrations.html#creating-a-join-table), I'm left with the impression that using create_join_table is preferred (and simpler) than creating the Join Table via rails generate model.
Is this a correct assumption? Are there nuances I should be aware of?

Using the example in the guides of categories and products:
A join table works transparently. You only work with the two existing models (Category and Product), and the join table exists only to enable the HABTM relationship between them, so you can call category.products, or product.categories, and things just work.
Generating a model, on the other hand, would only be necessary if you need to work with that association as a distinct thing in your application (e.g. if you need to do things with a Categorization directly).
Contrast the Rails Guides description of a has_and_belongs_to_many association (read more):
A has_and_belongs_to_many association creates a direct many-to-many
connection with another model, with no intervening model. For example,
if your application includes assemblies and parts, with each assembly
having many parts and each part appearing in many assemblies, you
could declare the models this way:
with that of a has_many :through association (read more):
A has_many :through association is often used
to set up a many-to-many connection with another model. This
association indicates that the declaring model can be matched with
zero or more instances of another model by proceeding through a third
model. For example, consider a medical practice where patients make
appointments to see physicians. The relevant association declarations
could look like this:
So, yes, you're correct that create_join_table would be simpler than creating a model for the association. You can also see this answer for another explanation of the difference between these approaches.

As the docs mention...
Migration method create_join_table creates a HABTM join table
--
When you create a has_and_belongs_to_many relationship, you only need a join table (no model).
has_and_belongs_to_many join tables are different from has_many :through as they don't have an id (primary_key); has_many :through are meant to represent an interim model, so each "record" should have a unique identifier.
Thus, your question about whether it's better to create a join table through rails g model is false. has_and_belongs_to_many has no binding model. If you were using has_many :through, you'd be able to use the rails g model no problem.

Related

How does one create a `has_one` relationship between two models using a join table/model?

TL;DR: How does one create a has_one association using through join table and vice-versa a belongs_to through?
Context: I have two models, ProcessLog and Encounter. ProcessLog, as the name (somewhat) suggests, saves log of a single run (corresponding to a row in DB) of an external process (which is run multiple times). On the other hand, Encounter is a model that keeps track of some information X. Encounters can be produced either internally or as a result of a successful execution of the external process mentioned earlier. What it entails is that not all Encounters have an associated ProcessLog and not all ProcessLogs have an associated Encounter. However, If there is a ProcessLog for an Encounter, this is a 1:1 relationship. An Encounter cannot have more than one ProcessLog and a ProcessLog cannot belong to more than one Encounter. From DB design perspective, this is an optional relationship (I hope I haven't forgotten my lessons). In a database, this would be modelled using a join table with encounter_id as the primary key and process_log_id as the foreign key.
Rails: In Rails, 1:1 relationships are generally modelled without using a join table and the belongs_to table generally having a foreign key to the other table. So in my case, this would be encounter_id in process_logs table.
Problem: With traditional Rails approach of has_one and belongs_to, this will result in many rows in process_logs table with NULL values for encounter_id column. Now there are pros and cons to this approach by Rails, however, that is not my intention to discuss here. Yes, it will keep the table structure simple, however, in my case it breaks the semantics and also introduces lots of NULL values, which I don't consider a good approach. And is also the reason why a join table exists for optional relationships.
What have I done so far?: There aren't a whole lot of helpful documents I could find on this topic, except for the following two linked documents, though they have their own issues and don't solve my problem.
SO Question The approach here is using has_many for the join model, whereas I have only one
Discussion on RoR Similarly, it is using has_many and yet somehow talks about has_one
I created a join model called EncounterProcessLog (which has belongs_to for both ProcessLog and Encounter) and then a has_one ..., through: on the other two models, but Rails is looking for a many-to-many association and of course looking for encounter_id on process_logs table.
Question: How can I achieve what I intend to achieve here? Something on the lines of (non-working) code below:
class Encounter:
has_one :process_log, through: :encounter_process_logs
class ProcessLog:
belongs_to :encounter, through: :encounter_process_logs # This may be incorrect way of specifying the relationship?
class EncounterProcessLog:
belongs_to :encounter
belongs_to :process_log # May be this should be a has_one?
I hope someone is able to guide me in the right direction. Thanks for reading so far.
One way I can think of for doing this is:
class Encounter:
has_one :encounter_process_log
has_one :process_log, through: :encounter_process_log
class ProcessLog:
has_one :encounter_process_log
has_one :encounter, through: :encounter_process_log
class EncounterProcessLog:
belongs_to :encounter
belongs_to :process_log
This would return the process_log for encounter and vice versa which is what you want probably.

Ruby on rails polymorphic associations

Is there a difference between the has_many , has_and_belongs_to_many and the polymorphic association.
Why should I use polymorphic association.
has_many, has_and_belongs_to_many and the polymorphic associations are solutions to different problems.
has_many means you have a parent object that has many other children. While the children do only belong to that specific parent.
has_and_belongs_to_many means the objects are not structured in a hierarchical structure. And that the objects on both sides can be connected to mutiple others. A good example is probably a tagging system. A thing can be tagged with multiple tags. But the tags do not only belong to one thing, they can be added to other things too.
polymorphic associations are used when the association doesn't point to one specific other model. An example might be an Author model that has a created_work association, but it doesn't point to a decicated CreatedWork model. But instead, it can return different things, like an instance of a Book, a Play an Article, or a simple Comment which are all different classes and have different database tables.

Rails - one model that many others can use?

Running Rails 3.2.8 with SQLite (basic setup from current dl on rubyonrails.org)
If i have several models that I want to have an attribute of "notes" and all "notes" are String, should there only be one specific Note model, with all the other models referencing the Note model?
or
Does each model has to have its own attribute for "notes"?
If the first response, does that mean that for each relationship that references the "notes", do I need to create another class that defines that :through relationship? (ie tableOne, tableTwo, and tableThree all reference notes, then I would need to create additional classes tableOne_notes, tableTwo_notes, and tableThree_notes)
Is a polymorphic solution applicable?
If you want one 'notes' model that can serve the same purpose for many other models this would be a polymorphic relationship.
This is actually true regardless of the application framework.
In the specific case of rails implementation of polymorphic relationships for this example you would add a
note_type
column in the notes model/table and in the Note model, using database migrations plus:
belongs_to :notable, :polymorphic => true
in Note and then
has_many notes, :as => notable`
in the other models that need the notes.
See http://guides.rubyonrails.org/association_basics.html#polymorphic-associations for more.
Frequent uses for Polymorphic relationships:
notes (as you are using)
update information (timestamp, ip address, user-agent, etc)
categories that are simple key-value lookups
It depends on your needs. But basically it is possible to create a model Notice and create a 1-n relation between Notice and Other and Other2...
As I understand your question only a 1-n or multiple 1-n relationships make sense. If you add to every table a column notes, it could only be one note for each data-line. Another class which defines :through is not necessary by a 1-n relation.

has_and_belongs_to_many vs has_many through

What is the difference between the
has_and_belongs_to_many and
has_many through relationships? When and where to use which one?
As far as I can remember, has_and_belongs_to_many gives you a simple lookup table which references your two models.
For example,
Stories can belong to many categories.
Categories can have many stories.
Categories_Stories Table
story_id | category_id
has_many :through gives you a third model which can be used to store various other pieces of information which don't belong to either of the original models.
For example
Person can subscribe to many magazines.
Magazines can have many subscribers.
Thus, we can have a subscription model in the middle, which gives us a similar table to the earlier example, but with additional properties.
Subscriptions Table
person_id | magazine_id | subscription_type | subscription_length | subscription_date
And so on.
From http://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many
The simplest rule of thumb is that you should set up a has_many :through relationship if you need to work with the relationship model as an independent entity. If you don’t need to do anything with the relationship model, it may be simpler to set up a has_and_belongs_to_many relationship (though you’ll need to remember to create the joining table in the database).
You should use has_many :through if you need validations, callbacks, or extra attributes on the join model.
My rule of thumb is, can I get by with a list of checkboxes here? If so, then it's a has-and-belongs-to-many (HABTM) association. If I need the checkbox to capture more about the relationship than simply yes/no it belongs, then use has_many :through. HABTM is as simple as using the _ids method with a simple_form collection_check_boxes. has_many :through often involves accepts_nested_attributes_for.
From my experience it's always better to use has_many: through because you can add timestamps to the table. Many times while debugging some ActiveRecord objects that are connected through HABTM, I was missing created_at, updated_at timestamps to get the clue what actually happened.
So keep in mind that it can help you to debug, investigate an issues with the data relations in the context of the time, because without it you are "blind" when relations were created or updated.
You should use has_many :through if you need validations, callbacks, or extra attributes on the join model.
Many of the answers clarify that you should use has_and_belongs_to_many vs. has_many through: if you will not need any extra data or validations on the join table.
However, beware of taking this approach. In the early stages of application development, it is nearly impossible to know what extra features or validations you may need in the far future of your project's lifecycle. If you decided to use has_and_belongs_to_many, and want to add one simple datapoint or validation 2 years down the road, migrating this change will be extremely difficult and bug-prone. To be safe, default to has_many :through
The simplest rule of thumb is that you can go with has_many :through relationship if you need to work with the relationship model as an independent entity.
If you don't need to do anything with the relationship model, it may be simpler to set up a has_and_belongs_to_many relationship (though you'll need to remember to create the joining table in the database).
You should use has_many :through if you need validations, callbacks, or extra attributes on the join model.
Rails offers two different ways to declare a many-to-many relationship between models. The first way is to use has_and_belongs_to_many, which allows you to make the association directly:
The second way to declare a many-to-many relationship is to use has_many :through. This makes the association indirectly, through a join model:
You should use has_many :through if you need validations, callbacks, or extra attributes on the join model.

Rails ActiveRecord Relationships

How do the relationships magically function when only the models are altered?
If I want a "has__and___belongs___to__many" relationship, what should I name the table (so Rails can use it) that contains the two foreign keys?
Short answer: You can't just tell the models that they're related; there have to be columns in the database for it too.
When you set up related models, Rails assumes you've followed a convention which allows it to find the things you wrote. Here's what happens:
You set up the tables.
Following conventions in Rails, you name the table in a particular, predictable way (a plural noun, e.g. people). In this table, when you have a relationship to another table, you have to create that column and name it in another predictable way (e.g. bank_account_id, if you're relating to the bank_accounts table).
You write a model class inheriting from ActiveRecord::Base
class Person < ActiveRecord::Base
When you instantiate one of these models, the ActiveRecord::Base constructor looks at the name of the class, converts it to lowercase and pluralizes it. Here, by reading Person, it yields people, the name of the table we created earlier. Now ActiveRecord knows where to get all the information about a person, and it can read the SQL output to figure out what the columns are.
You add relationships to the model: has_many, belongs_to or has_one.
When you type something like, has_many :bank_accounts, it assumes a few things:
The name of the model that you relate to is BankAccount (from camel-casing :bank_accounts).
The name of the column in the people table which refers to a bank account is bank_account_id (from singularizing :bank_accounts).
Since the relationship is has_many, ActiveRecord knows to give you methods like john.bank_accounts, using plural names for things.
Putting all of that together, ActiveRecord knows how to make SQL queries that will give you a person's bank accounts. It knows where to find everything, because you followed a naming convention that it understands when you created the table and its colums.
One of the neat things about Ruby is that you can run methods on a whole class, and those methods can add other methods to a class. That's exactly what has_many and friends are doing.
This works because you are following "Convention over Configuration".
If you state that a customer model has many orders then rails expects there to be a customer_id field on the orders table.
If you have followed these conventions then rails will use them and will be able to build the necessary SQL to find all the orders for a given customer.
If you look at the development.log file when you are developing your application you will be able to see the necessary SQL being built to select all orders for a given customer.
Rails does not create tables without you asking it to. The creation of tables is achieved by generating a migration which will create/alter tables for you. The fact that you create a customer model and then state within it that it has_many :orders will not create you an orders table. You will need to do that for yourself within a migration to create an orders table. Within that migration you will need to either add a customer_id column or use the belongs_to: customer statement to get the customer_id field added to the orders table.
The rails guide for this is pretty useful

Resources