How to do Many to Many relations in Rails? [duplicate] - ruby-on-rails

This question already has answers here:
Ordering First Model in Association with Second Model
(4 answers)
Closed 8 years ago.
What is the correct way to do many to many in Rails?
I wasted a bit of time on the subtleties of this, so I thought I would post the question and the answer here, in the case that it saves someone else some time.

I posted this question here, as it was not immediately apparent. So I wanted to make sure if others were in the same pickle that this hopefully helps them.
So firstly with Rails for those new to Rails.. when you generate a Model eg "Story", the generator will pluralise the Model when creating the table name for the database eg "stories". So eg when I ran "rails g model author_book", the model ends up called AuthorBook and rails names the table author_books.
Keep that in mind when choosing Model names, as initially "Story" was named "News", which violated the rails convention of having Models named in the singular form.
Also another note: Don't have really long model and primary key names, as when rails hooks up the many to many relation in the database it can cause issues as it is limited to a max of 64 chars (from what I can remember).
So for the example let say we have Books and Authors and a relation between them. Here is how they will be represented in rails:
app/models/book.rb
class Book < ActiveRecord::Base
attr_accessible :title
has_many :author_books #This is the database table name!!
has_many :authors, through: #This is the database table name!!
end
app/models/author.rb
class Author < ActiveRecord::Base
attr_accessible :name
has_many :author_books #This is the database table name!!
has_many :books, through: :author_books #This is the database table name!!
end
app/models/author_book.rb
class AuthorBook < ActiveRecord::Base
attr_accessible :author_id, :book_id
belongs_to :author #This is the MODEL name
belongs_to :book #This is the MODEL name
end
20131025011112_create_author_books.rb migration example:
class CreateAuthorBooks < ActiveRecord::Migration
def change
create_table :author_books, :id => false do |t|
t.belongs_to :author #this is the MODEL name
t.belongs_to :book #this is the MODEL name
end
add_index :author_books, [:author_id, :book_id], :unique => true #This is the database table name!!
end
end
I hope that saves someone else some time!

Related

Design pattern for person with multiple roles [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 5 years ago.
Improve this question
I want to create a base model Person with some person related attributes like name, address, phone and so on. One Person can be one ore more of the following:
LoginUser with fields for login, password, last_login, ...
CardHolder with fields for card_id, last_entrance, ...
Supplier with just a flag whether or not the person is a supplier
Recipient with just a flag whether or not the person is a recipient
Is there a common sense or best practise design pattern in Ruby on Rails to represent that inheritance? How it should be represented in the model(s) and table structure so that it is possible to check whether a Person is a LoginUser and to access the corresponding fields.
In another project I worked already with STI but in this case this isn't the right pattern.
What you're looking for is a reverse polymorphic association. Polymorphic associations allow you to link one model to many different ones. A reverse polymorphic association allows you to link many models to one single one. They're a little tricky to set up, but once you get the hang of it it's no problem.
In order to accomplish this, you need another model that acts as a go-between for the Person model and each of the different roles. This go-between model is the one that actually has the polymorphic association. Your Person model will has_many that model, and your various role models will has_one of it. You then use :through to make the rest of the necessary associations so your code doesn't know any different. Shazam!
Here's an example of how to do it with the Person and CardHolder models. I'm calling the extra model Role because that seems like an obvious choice:
class Person < ApplicationRecord
has_many :roles
# Reach through the Roles association to get the CardHolders, via polymorphic :rollable.
# Unfortunately, you can't has_one, so you'll have to enforce uniqueness in Role
# with a validation.
has_many :card_holders, through: :roles, source: :rollable, source_type: 'CardHolder'
end
class Role < ApplicationRecord
belongs_to :person
# Here is where our actual polymorphic connection is:
belongs_to :rollable, polymorphic: true
end
class CardHolder < ApplicationRecord
# The other side of the polymorphic connection, with has_one:
has_one :role, as: :rollable
# Get the person via the role, just like the inverse:
has_one :person, through: :role
end
The database setup is like this:
class CreatePeople < ActiveRecord::Migration[5.1]
def change
create_table :people do |t|
t.string :name
# put in whatever other Person columns you need
t.timestamps
end
end
end
class CreateRoles < ActiveRecord::Migration[5.1]
def change
create_table :roles do |t|
t.references :person, index: true
t.references :rollable, polymorphic: true, index: true
t.timestamps
end
end
end
class CreateCardHolders < ActiveRecord::Migration[5.1]
def change
create_table :card_holders do |t|
t.integer :card_id
t.datetime :last_entrance
# put in whatever other columns you need
t.timestamps
end
end
end
Using it is quite simple:
> p = Person.create(name: "Sven Reuter")
# directly add a card holder
> p.card_holders << CardHolder.create(card_id: 1, last_entrance: Time.current)
# build a role instead
> p.roles.build(rollable: CardHolder.new(card_id: 2, last_entrance: Time.current)
# get all of the roles
> p.roles
I would go with Person table and the PersonAttributes table that is a union of all the attributes the person might have. PersonAttributes might use STI if applicable, e.g. with LoginUser storing logins and CardHolder referencing Cards.
Clean and simple.

What is the process of creating a joining table which resolves a many-to-many relationship using migrations?

I would like to understand the process which can be followed to create models and migrations which in turn create a joining table within the database to resolve a many-to-many relationship. For instance. If i have a course table, a students table and i want to create a joining table called studies with the id from course and students along with extra data such as the grade and date started. Exactly What is the process for doing this?
If i am to use the generate model command for each of the above 3 table names, this will create a separate migration for all of them. Even if i go into the models and add the relevant associations this will not affect how the database is created? Please could you give me some guidance on what steps i must take in order to create the required foreign key relationships here with some examples?
Use a has_many :through association. Set it up manually.
Step 1: Generate your models separately. The joining model Study will contain foreign keys, so remember to include the columns.
rails g model Course title:string
rails g model Student name:string
rails g model Study course_id:integer student_id:integer start_date:date grade:string
Step 2: Set up associations:
# models/course.rb
class Course
has_many :studies
has_many :students, through: :studies
end
# models/student.rb
class Student
has_many :studies
has_many :courses, through: :studies
end
# models/study.rb
class Study
belongs_to :course
belongs_to :student
end
Step 3: Run migrations, restart server (if necessary), and that's it. Rails will handle the rest.
Accessing attributes in the joining table may require careful timing to ensure the correct object is being accessed, since the joining object is not returned via Rails' built-in methods, ie #course.students. Check out this answer for some ideas.
Read the guide for more information.
for many-to-many relationships use:
class Car < ActiveRecord::Base
has_and_belongs_to_many :tires
end
class Tire < ActiveRecord::Base
has_and_belongs_to_many :cars
end
Then you create a migration like this:
class CreateCarsAndTires < ActiveRecord::Migration
def change
create_table :cars do |t|
t.string :name
end
create_table :tires do |t|
t.string :something
end
create_table :cars_tires do |t|
t.belongs_to :car
t.belongs_to :tire
t.string :additional_dataA //optional
t.int :something_else //optional
end
end
end
It is very important that you name your join table in the migration in alphabetical order (c in cars comes before t for tires) as ActiveRecord will look in has_many_and_belongs_to relations for a table which is named this way pluralized-classA_pluralized_classB like apples_bananas vs bananas_apples which would not work and you would have to add the table name to your classes and it goes against the convention over configuration paradigm.
Hope it helps.

Rails: polymorphic assoziation, has_many :through

Im not sure i understand rails polymorphic.
In Java you can create Objects from the same Objecttype:
http://www.fh-kl.de/~guenter.biehl/lehrgebiete/java2/j2-08-Dateien/abb.8.10.jpg
Person trainer = new Trainer()
Person sportler = new Trainer()
In Rails http://guides.rubyonrails.org/association_basics.html#polymorphic-associations:
In this example: picture can be from an employee or from a product, sounds strange because this is not realy the same type.
Do i understand the real purpose: to save objects in the same container an array of person or image?
In my rails project: I have several person: sportsmen, trainer and guest. They are sons of person (inheritance).
I think i meet the inheritance reason.
There is another class named exercise.
Sportsmen and trainer can create exercises.
So i want to use polymorphic. Exercises can be from trainer or sportsmen. Like in the example of the rails page, images can be from employee or a product.
Do i meet the best practise?
How do i implement a has_many :through with polymorphy?
It is not possible to use a habtm assoziation with polymorphic.
You have to define a additional class, but how exactly?
I think you want single table inheritance (STI) models, not a polymorphic relationship.
See this article http://www.alexreisner.com/code/single-table-inheritance-in-rails and these stackoverflow answers Rails - Single Table Inheritance or not for Applicant/Employee relationship Alternative to Rails Single Table Inheritance (STI)?
Just to make it clear, you should use polymorphic associations when you have a model that may belong to many different models on a single association.
Suppose, you want to be able to write comments for users and stories. You want both models to be commendable. Here's how this could be declared:
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :comment, as: :commentable
end
class Product < ApplicationRecord
has_many :comment, as: :commentable
end
To declare the polymorphic interface (commendable) you need to declare both a foreign key column and a type column in the model.
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.text :body
t.integer :commentable_id
t.string :commentable_type
t.timestamps
end
add_index :comments, :commentable_id
end
end
You can check more details about associations here.

Associations and (multiple) foreign keys in rails (3.2) : how to describe them in the model, and write up migrations

I have 3 models: Question, Option, Rule
Question has_many options;
Option needs a foreign key for question_id
Rule table consists of 3 foreign_keys:
2 columns/references to question_ids -> foreign keys named as 'assumption_question_id' and 'consequent_question_id'
1 column/reference to option_id -> foreign key named as option_id or condition_id
Associations for Rule:
Question has_many rules; and
Option has_one rule
I want to understand how to write up migrations for this, and how that associates to the 'has_many'/'belongs_to' statements I write up in my model, and the ':foreign_key' option I can include in my model.
I had this for my Option migration, but I'm not sure how the "add_index" statement works in terms of foreign keys, and how I can use it for my Rule migration: (my Question and Options models have appropriate has_many and belongs_to statements - and work fine)
class CreateOptions < ActiveRecord::Migration
def change
create_table :options do |t|
t.integer :question_id
t.string :name
t.integer :order
t.timestamps
end
add_index :options, :question_id
end
end
Thank you for the help!
Note: I have found this way to solve the problem.Kindness from China.
If you have RailsAdmin with you,you may notice that you can see all rules of one question as long as one field of both question fields(assumption_question_id,consequent_question_id) equals to id of the question.
I have done detailed test on this and found out that Rails always generates a condition "question_id = [current_id]" which make to_sql outputs
SELECT `rules`.* FROM `rules` WHERE `rules`.`question_id` = 170
And the reason that the following model
class Question < ActiveRecord::Base
has_many :options
# Notice ↓
has_many :rules, ->(question) { where("assumption_question_id = ? OR consequent_question_id = ?", question.id, question.id) }, class_name: 'Rule'
# Notice ↑
end
makes Question.take.rules.to_sql be like this
SELECT `rules`.* FROM `rules` WHERE `rules`.`question_id` = 170 AND (assumption_question_id = 170 OR consequent_question_id = 170)
Is that we have not yet get ride of the annoy question_id so no matter how we describe or condition properly, our condition follows that "AND".
Then,we need to get ride of it.How?
Click here and you will know how,Find sector 8.1,and you can see
Article.where(id: 10, trashed: false).unscope(where: :id)
# SELECT "articles".* FROM "articles" WHERE trashed = 0
Then lets do it:
class Question < ActiveRecord::Base
has_many :options
# Notice ↓
has_many :rules, ->(question) { unscope(where: :question_id).where("assumption_question_id = ? OR consequent_question_id = ?", question.id, question.id) }, class_name: 'Rule'
# Notice ↑
end
class Rule < ActiveRecord::Base
belongs_to :option
belongs_to :assumption_question, class_name: "Question", foreign_key: :assumption_question_id, inverse_of: :assumption_rules
belongs_to :consequent_question, class_name: "Question", foreign_key: :consequent_question_id, inverse_of: :consequent_rules
end
class Option < ActiveRecord::Base
belongs_to :question
has_one :rule
end
All done.
Finally
This is my first answer here at stackoverflow,and this method is never found anywhere else.
Thanks for reading.
add_index adds an index to column specified, nothing more.
Rails does not provide native support in migrations for managing foreign keys. Such functionality is included in gems like foreigner. Read the documentation that gem to learn how it's used.
As for the associations, just add the columns you mentioned in your Question to each table (the migration you provided looks fine; maybe it's missing a :rule_id?)
Then specify the associations in your models. To get you started
class Question < ActiveRecord::Base
has_many :options
has_many :assumption_rules, class_name: "Rule"
has_many :consequent_rules, class_name: "Rule"
end
class Rule < ActiveRecord::Base
belongs_to :option
belongs_to :assumption_question, class_name: "Question", foreign_key: :assumption_question_id, inverse_of: :assumption_rules
belongs_to :consequent_question, class_name: "Question", foreign_key: :consequent_question_id, inverse_of: :consequent_rules
end
class Option < ActiveRecord::Base
belongs_to :question
has_one :rule
end
Note This is just a (untested) start; options may be missing.
I strongly recommend you read
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
http://guides.rubyonrails.org/association_basics.html
Edit: To answer the question in your comment
class Option < ActiveRecord::Base
belongs_to :question
# ...
The belongs_to tells rails that the question_id column in your options table stores an id value for a record in your questions table. Rails guesses the name of the column is question_id based on the :question symbol. You could instruct rails to look at a different column in the options table by specifying an option like foreign_key: :question_reference_identifier if that was the name of the column. (Note your Rule class in my code above uses the foreign_key option in this way).
Your migrations are nothing more than instructions which Rails will read and perform commands on your database based from. Your models' associations (has_many, belongs_to, etc...) inform Rails as to how you would like Active Record to work with your data, providing you with a clear and simple way to interact with your data. Models and migrations never interact with one another; they both independently interact with your database.
You can set a foreign key in your model like this:
class Leaf < ActiveRecord::Base
belongs_to :tree, :foreign_key => "leaf_code"
end
You do not need to specify this in a migration, rails will pull the foreign key from the model class definition.

Ruby on Rails Associations clarification

I'm starting out with the whole (wonderful) idea of database associations in Rails but I'm having problems because I'm working with an existing database that does not conform to Rails standards and cannot figure out how to name the associations. There are a couple of similar posts, but I can't wrap my head around the naming for my particular situation which is as follows:
table book with book.formatId looks up values in book_format.id
So foreign key book.formatId
My models are named: Book and BookFormat (I read that you use camelCase when your tables are separated by underscore).
Under the Book model I have this:
has_one :bookFormat, :foreign_key => 'book_format.id' # not sure if this format table.field is correct or I have to use something else here. Also not sure about the bookFormat, should it be BookFormat or bookformat?
The BookFormat model has this:
belongs_to :book
But when I try to do
book = Book.first
book.bookFormat.empty?
I get an error of method not found for bookFormat. So obviously something's wrong, but I can't figure out where.
A second part of the question is the use of many to many relationships. Example:
Tables
book, book_subjects, book_subjects2title
book_subjects.id => book_subjects2title.pId
book.id => book_subjects2title.bookId
I'm reading the Beginning Rails 3 book from Apress (which is a great book) but it's not very clear on all this or I'm just not getting it.
Thanks.
Since the book stores the formatId on it, you should use belongs_to, and change the foreign key as such:
belongs_to :book_format, :class_name => 'BookFormat', :foreign_key => 'formatId'
For the table name, i did some quick searching, and found the following method: set_table_name
So you should be able to add it at the top of your model, like so:
class Book < ActiveRecord::Base
set_table_name 'book'
# the rest of book model code
end
class BookFormat < ActiveRecord::Base
set_table_name 'book_format'
# rest of book_format model code
end
Normally rails uses plural table names, so hence why you need to specify it there.
agmcleod put me on the right track, so here's the full answer in the hopes that this helps other people with similar problems:
I created the model with a different name for easier reading. So model Books will have:
class Books < ActiveRecord::Base
set_table_name 'bookpedia'
belongs_to :format, :foreign_key => 'formatId' # we need to specify the foreign key because it is not the rails default naming
has_many :author2title, :foreign_key => 'bookId'
has_many :author, :through => :author2title
end
model Format:
class Format < ActiveRecord::Base
set_table_name 'book_format'
has_many :books
end
Then an instance of the class will have the format method:
#book = Books.first
#book.format # Hardcover
For many to many relationships I'll paste the syntax here as well since that took me a while to figure out:
class Author < ActiveRecord::Base
set_table_name 'book_author'
has_many :author2title, :foreign_key => 'pId' # junction table
has_many :books, :through => :author2title # establishing the relationship through the junction table
end
This is the actual junction table:
class Author2title < ActiveRecord::Base
set_table_name 'book_author2title'
belongs_to :author, :foreign_key => 'pId' # foreign key needs to be here as well
belongs_to :books, :foreign_key => 'bookId'
end
The book model above has the necessary entries for this many to many relationship already in it.
If anyone needs clarification on this I'd be happy to oblige since I struggled for more than a day to figure this out so would be glad to be of help.
Cheers.

Resources