I have two models. Users and Jobs. I have a join table to indicate a User's interest in a Job. I was using a has_and_belongs_to_many relationship for Users and Jobs. I now realize I need to be using a has_many :through relationship. Because of that, I need to have a model for my join table or I get undefined Constant errors. My join table is called jobs_users. What should I name the .rb file for the model and what should the class name be in order to get Rails magic to work? I have found SO questions that imply the class should be JobUser, but nothing on what the .rb file should be named.
Naming convention for Rails models based on existing tables
Model class names use CamelCase. These are singular, and will map automatically to the plural database table name.
> 'JobsUser'.underscore
=> "jobs_user"
This means your file should be named jobs_user.rb. And class will look like below
class JobsUser < ApplicationRecord
...........
end
For fresh migration, you can rollback and destroy current table migration e.g jobs_users and then run this command
rails g model UserJob user:references job:references
db/migrate/TIMESTAMP_create_user_jobs.rb file looks like below
class CreateUserJobs < ActiveRecord::Migration[5.1]
def change
create_table :user_jobs do |t|
t.references :user, foreign_key: true
t.references :job, foreign_key: true
t.timestamps
end
end
end
then migrate this and created model file name is user_job.rb
has_many :through relationship will looks like this
#user.rb
class User < ApplicationRecord
has_many :user_jobs, dependent: :destroy
has_many :jobs, through: :user_jobs
end
#job.rb
class Job < ApplicationRecord
has_many :user_jobs, dependent: :destroy
has_many :users, through: :user_jobs
end
#user_job.rb
class UserJob < ApplicationRecord
belongs_to :user
belongs_to :job
end
That's it
Related
My :users table is successfully self-joined with all the necessary confusing (to this newbie) code and tables necessary to do that. :users's two groups are :teachers and :students.
I need to make the group :teachers join one-to-many with the :bands table (a band may have only one teacher) while at the same time joining :students many-to-many with the :bands table (a band may have many students and vice versa).
What's tripping me up is :students and :teachers are both :users. Therefore, if for a moment I pretend that there's only one kind of user and go for a one-to-many (teacher) relationship, then the Band model belongs_to :user and the User model has_many :bands
But if I go for the many-to-many (student) relationship instead, the Band model has_many :users, through :user_bands join table and the User model has_many :bands, through :user_bands join table. (UserBands model has belongs_to :user and belongs_to :band in this case)
But I need both relationships at the same time. I haven't actually tried putting has_many :bands in the User model while simultaneously having has_many :users (through join table) and belongs_to :users in the Bands model because, unless Rails is more magic than I give it credit for, it won't differentiate that teachers get the single-to-many while the students get the many-to-many.
I have not attempted my best guess (below) because I'm admittedly skittish: my database already has a sprawling number of many-to-many relations that are intact and functioning properly. The one time I attempted to make a complicated alteration to it earlier in the process, it thoroughly messed things up so badly that rolling back and undoing model alterations didn't get me back to where I'd started somehow, so I had to go back to rebuilding everything from scratch after pulling out enough hair for a tonsure. I do have github this time, so I should be able to revert the project if it blows up like before, but git is its own fussy minefield.
So if some folks could take a look at my guess first, I'd deeply appreciate it. Does it look right? Do I need to make changes before updating the database schema?:
In User model, add has_many :bands.
In Band model, add has_many :students, through :user_bands ; add belongs_to :teacher
In the create_bands migration, add t.belongs_to :teacher
In UserBands model, add belongs_to :teacher and add t.belongs_to :teacher in the create_user_bands migration.
The needed associations are not self-joining. Self-joins are primarily used to build tree like hierarchies from a single table - see the guides for a good example.
You just need multiple associations between two tables - the key thing here to remember is that you must use unique names for each association. If you declare multiple associations with the same name the later just overwrites the former without error.
Teachers
class AddTeacherIdToBands < ActiveRecord::Migration[5.2]
def change
add_reference :bands, :teacher, foreign_key: { to_table: :users }
end
end
class User < ApplicationRecord
has_many :bands_as_teacher,
class_name: 'Band',
foreign_key: 'teacher_id'
end
class Band < ApplicationRecord
belongs_to :teacher,
class_name: 'User'
end
We name the association bands_as_teacher to avoid conflict and confusion. This requires us to explicitly set the class_name and foreign_key options as they cannot be deduced from the name.
This is kind of where you tripped up and overcomplicated your solution. If the association is one to many you don't need to involve a join table.
Students
To create the association between a band and its members you need a m2m association through a join table:
class CreateBandMemberships < ActiveRecord::Migration[5.2]
def change
create_table :band_memberships do |t|
t.references :band, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
end
end
end
class Band < ApplicationRecord
# ...
has_many :band_memberships
has_many :users, through: :band_memberships
end
class BandMembership < ApplicationRecord
belongs_to :band
belongs_to :user
end
class User < ApplicationRecord
# ...
has_many :band_memberships
has_many :bands, through: :band_memberships
end
You can improve the naming by providing the source option which tells Rails which association to use on the model its joining through.
class Band < ApplicationRecord
# ...
has_many :band_memberships
has_many :members, through: :band_memberships, source: :user
end
class User < ApplicationRecord
# ...
has_many :band_memberships
has_many :bands_as_member, through: :band_memberships, source: :band
end
I have a parent model called Quote. which has an attribute called final_quote and has a child model called QuoteBoms, which has attributes called quote_final_quote and quantity and total_quote (=quote_final_quote * quantity)
class Quote < ActiveRecord::Base
has_many :quote_boms, dependent: :destroy
accepts_nested_attributes_for :quote_boms, :reject_if => :all_blank, :allow_destroy => true
class QuoteBom < ActiveRecord::Base
belongs_to :quote
has_many :quotes
end
Now in the nested model, I am selecting the quote with the association "belongs_to :quote" but has_many :quotes does not work as I have only one quote_id column (I suppose this is the problem). I see that i need to define a third class as quotebom_quote_id but cannot figure out how exactly!
Any help will be greatly appreciated!
class Image < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
class Profile < ActiveRecord::Base
has_many :images, :as => :imageable
end
class Article < ActiveRecord::Base
has_many :images, :as => :imageable
end
This is how we have made a single Image model and it is accessed by one or more than one model
Please refer this
Link
From what I can tell, you wish to create a database structure containing the models Quote and QuoteBom where a Quote has many QuoteBom and QuoteBom belongs to many Quotes.
That being the case, you will want to use a has_and_belongs_to_many association.
This will require adding to your models
class Quote < ActiveRecord::Base
has_and_belongs_to_many :quote_boms
end
class QuoteBom < ActiveRecord::Base
has_and_belongs_to_many :quotes
end
...and the following migration (assuming Quote and QuoteBom already exist)
class CreateQuotesAndQuoteBoms < ActiveRecord::Migration
def change
create_table :quote_quote_boms, id: false do |t|
t.belongs_to :quote, index: true
t.belongs_to :quote_bom, index: true
end
end
end
By having the associations above in the model and this table in your database, rails will automagically handle the associations between quote and quote_doms. As a result, you will also be able to access quote_dom.quotes which you said you weren't able to do in your question.
This is NOT a polymorphic association. A polymorphic association allows a model to belong to more than one type of other model in a single association.
I'm not sure how to create this, I'd like to create a many-to-many polymorphic association.
I have a question model, which belongs to a company.
Now the question can has_many users, groups, or company. Depending on how you assign it.
I'd like to be able to assign the question to one / several users, or one / several groups, or the company it belongs to.
How do I go about setting this up?
In this case I would add a Assignment model which acts as an intersection between questions and the entities which are assigned to it.
Create the table
Lets run a generator to create the needed files:
rails g model assignment question:belongs_to assignee_id:integer assignee_type:string
Then let's open up the created migration file (db/migrations/...__create_assignments.rb):
class CreateAssignments < ActiveRecord::Migration
def change
create_table :assignments do |t|
t.integer :assignee_id
t.string :assignee_type
t.belongs_to :question, index: true, foreign_key: true
t.index [:assignee_id, :assignee_type]
t.timestamps null: false
end
end
end
If you're paying attention here you can see that we add a foreign key for question_id but not assignee_id. That's because the database does not know which table assignee_id points to and cannot enforce referential integrity*. We also add a compound index for [:assignee_id, :assignee_type] as they always will be queried together.
Setting up the relationship
class Assignment < ActiveRecord::Base
belongs_to :question
belongs_to :assignee, polymorphic: true
end
The polymorpic: true option tells ActiveRecord to look at the assignee_type column to decide which table to load assignee from.
class User < ActiveRecord::Base
has_many :assignments, as: :assignee
has_many :questions, through: :assignments
end
class Group < ActiveRecord::Base
has_many :assignments, as: :assignee
has_many :questions, through: :assignments
end
class Company < ActiveRecord::Base
has_many :assignments, as: :assignee
has_many :questions, through: :assignments
end
Unfortunately one of the caveats of polymorphic relationships is that you cannot eager load the polymorphic assignee relationship. Or declare a has_many :assignees, though: :assignments.
One workaround is:
class Group < ActiveRecord::Base
has_many :assignments, as: :assignee
has_many :questions, through: :assignments
def assignees
assignments.map(&:assignee)
end
end
But this can result in very inefficient SQL queries since each assignee will be loaded in a query!
Instead you can do something like this:
class Question < ActiveRecord::Base
has_many :assignments
# creates a relationship for each assignee type
['Company', 'Group', 'User'].each do |type|
has_many "#{type.downcase}_assignees".to_sym,
through: :assignments,
source: :assignee,
source_type: type
end
def assignees
(company_assignees + group_assignees + user_assignees)
end
end
Which will only cause one query per assignee type which is a big improvement.
I have a User model and a product model.
User has_many :products, :dependent => :destroy
Product belongs_to :user, :foreign_key => "user_id", touch: true
I want to create a wishlist for every user.
So i have to create a wishlist model with proper association.
But i don't know how to start.
I presume that the wishlist model contain an id, user_id and product_id field
Do i have to use has_many through association or a has_and_belongs_to_many ?
I also want that if a user is destroyed to destroy his wishlist.
What is the best way to do?
Many thanks!
As #JZ11 pointed out, you shouldn't be linking a Product directly to a User (unless a User actually 'owns' a product for some reason). However, what was missed is the model that makes up a Wishlist item:
class User < ActiveRecord::Base
has_many :wishlists # or has_one, depending on how many lists a User can have...
end
class Product < ActiveRecord::Base
has_many :wishlist_items
end
class Wishlist < ActiveRecord::Base
belongs_to :user
has_many :wishlist_items
has_many :products, :through => :wishlist_items
end
class WishlistItem < ActiveRecord::Base
belongs_to :product
belongs_to :wishlist
end
Naturally, you should be adding :dependent => :destroy where necessary.
You don't need the has_many :products relationship on User.
I don't think it makes sense for User and Product to be linked outside of a Wishlist.
class Wishlist < ActiveRecord::Base
has_many :products
belongs_to :user
end
class User < ActiveRecord::Base
has_one :wishlist, dependent: :destroy
end
class Product < ActiveRecord::Base
belongs_to :wishlist
end
To create your join table, do:
rails g migration create_products_users_table
Once you've done that, you need to add some code, below, to create the fields in the join table. Notice the :id => false, because you do not need an id in the join table:
class CreateProductsUsersTable < ActiveRecord::Migration
def change
create_table :products_users, :id => false do |t|
t.references :product
t.references :user
end
add_index :products_users, [:product_id, :user_id]
add_index :products_users, :user_id
end
end
The code above also creates some indexes and ensures that you don't have duplicates even at the database level.
Your models would then have to look like this:
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
end
When you destroy a user correctly, like user.destroy and not just delete it (there is a difference), then the related rows in the join table will be deleted as well. This is built in to ActiveRecord.
Notice though, that doing this will not really let you use the join table. It will accept code like user.products = [product1, product2] etc, and other goodies, but no real use of a wish list.
If you do want to use a wish list, you will have to create and use the middle join table differently, using has_many :through (I didn't check PinnyM's answer but that might be the way to do it).
In RoR3,
I have Users and Skills and each skill is created by a user. I wanted to record that, so I created a one to many relationship.
class User < ActiveRecord::Base
has_many :skills
end
class Skill < ActiveRecord::Base
belongs_to :user
end
However, each user also has many skills in the sense that, user "Bob" created skill "Kung Fu", user "Charlie" created skill "Karate" and user "Bob" both created and is able to do both "Kung Fu" and "Karate"
How should I represent this with ActiveRecord? Should I just create a new table "user_skills" which has_many :skills? and belong_to :user?
There are two different associations here. The first is a one-to-many association. An user can be the creator of any number of skills. The second one is a many-to-many association, an user can have many skills and a skill can have many users.
The first one is a simple belongs_to <-> has_many declaration. For the second one, you either need a has_and_belongs_to_many declaration in both models, and a related join table, or a dedicated join model, and a has_many :through declaration. Let's try the first one:
Method 1: HABTM
class User < ActiveRecord::Base
has_many :created_skills, :class_name => 'Skill', :inverse_of => :creator
has_and_belongs_to_many :skills
end
class Skill < ActiveRecord::Base
belongs_to :creator, :class_name => 'User', :inverse_of => :created_skills
has_and_belongs_to_many :users
end
This requires a join table called "skills_users" that has columns named user_id and skill_id
Method 2: Has many through (Join model)
The second one is similar, but adds a model that acts as the middleman. This has an added benefit that you can include additional columns in the join model, like for example a skill level.
class User < ActiveRecord::Base
has_many :created_skills, :class_name => 'Skill', :inverse_of => :creator
has_many :user_skills
has_many :skills, :through => :user_skills
end
class Skill < ActiveRecord::Base
belongs_to :creator, :class_name => 'User', :inverse_of => :created_skills
has_many :user_skills
has_many :users, :through => :user_skills
end
class UserSkill < ActiveRecord::Base
belongs_to :user
belongs_to :skill
end
Having those two models
class User < ActiveRecord::Base
has_and_belongs_to_many :skills
end
class Skill < ActiveRecord::Base
has_and_belongs_to_many :users
end
You would have to create an extra migration (without the model)
rails generate migration CreateSkillsUsersJoin
which will give you
class CreateSkillsUsersJoin < ActiveRecord::Migration
def self.up
create_table :skills_users, id => false do |t|
t.references "user"
t.references "skill"
end
add_index :skills_users,["user_id","skill_id"]
end
def self.down
drop_table :skills_users
end
end
The methods self.up and self.down you will have yo add them
You'd be well served using a gem like acts_as_taggable_on which you'd be able to simply setup and use in your User model, something like:
acts_as_taggable_on :skills
Honestly, they've figured all this stuff out, as it's not as simple as what you're trying to do, OR I should rephrase that and say, what you are trying to do is overtly 'complex' and this gem allows you to just keep on, keeping on after it's set up.
Read the Readme.