I have a question about the Has Many Through relationship.
I have 3 models : Artist, Skill, and Mastery
skill.rb
class Skill < ActiveRecord::Base
has_many :masteries
has_many :artists, through: :masteries
end
mastery.rb
class Mastery < ActiveRecord::Base
belongs_to :artist
belongs_to :skill
end
artist.rb
class Artist < ActiveRecord::Base
has_many :masteries
has_many :skills, through: :masteries
end
Everytime I try to attach a skill to an Artist, using artist.skills << skill, a mastery is created, but the artist_id is nil. Same thing the other way around. skill.artists << artist gives me a Mastery with a nil skill_id.
Does this mean that I have to execute both every time ? Or did I miss something ?
So basically, here's why it failed : it appears that you cannot set it in rails console under certain unclear circumstances. However, if you got the same model setup as me, using the model.attributes << attribute syntax will work just fine. Obviously, if you need to create a fake relationship in your rails console, all you have to do is create a new Mastery, and set its creator_id and skill_id accordingly, then save it.
Related
Below given are the model in my application. I am trying to find all the companies that matches the keyskills. For example. If I type java in my search-box it should get me all the companies that are matching and keyskills.
class User < ActiveRecord::Base
has_one :company
end
class Company < ActiveRecord::Base
has_many :jobs
belongs_to :user
end
class Job < ActiveRecord::Base
belongs_to :company
has_many :key_skills, dependent: :destroy
accepts_nested_attributes_for :key_skills, reject_if: :all_blank, allow_destroy: true
end
class KeySkill < ActiveRecord::Base
belongs_to :job
end
The steps that I am following are,
step1: Find all the keyskills matching the entered word. (ex: java)
#matched_keyskills = KeySkill.where('name like ?','java')
Since I have association between jobs and keyskills, that is jobs has_many key_skills and key_skills belongs_to job. I can iterate over
#matched_keyskill.each do |k|
k.job.company
end
and get the company records. But, when I tried this method it results in n+1 query also the company name is getting repeated.
Is there a way through which I can get only the company name shown on show page then by clicking on company it shows the jobs associate to it.
also kindly let me know is the db model and association are correct inorder to achieve it.
You can use "joins" and "inclue" to remove the n+1 query:
Try this, it will give you the list of all companies as You required.
Company.joins(key_skill: :job).where('key_skill.name like ?','java')
you can also use eager loading.
http://railscasts.com/episodes/22-eager-loading
Sorry, this one is hard to phrase in the title. So here's what I'm trying to do. A workshop has many districts. Each district has exactly one district_contact (actually a district_contact_id). How can I use ActiveRecord to model the relationship between workshop and district_contact? I want to be able to do this:
Workshop.district_contacts
And get a collection of the actual user objects. Right now, I've done it using a short function:
def district_contacts
district_ids = []
self.districts.each do |district|
if district.contact_id
district_ids << district.contact_id
end
end
User.find(district_ids)
end
Define associations in the Workshop model:
has_many :districts
has_many :district_contacts, through: disctricts
Your model associations should look something like this.
class Workshop < ActiveRecord::Base
has_many :districts
has_many :district_contacts, through: disctricts
end
class District < ActiveRecord::Base
belongs_to :workshop
has_one :district_contract
end
I am trying to create an association between two tables. A student table and a computer table.
A computer can only ever be assigned to one student (at any one time) but a student can be assigned to multiple computers.
This is what I currently have in mind. Setting up a has-many through relationship and modifying it a bit.
class Student < ActiveRecord::Base
has_many :assignemnts
has_many :computers, :through => :assignments
end
class Computer < ActiveRecord::Base
has_one :assignment
has_one :student, :through => :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :student
belongs_to :computer
end
Does this seem like the best way to handle this problem? Or something better sound out quickly to the experts here. Thanks!
You need first to decide if a simple one-to many relationship is enough for you.
If yes, it gets a lot easier, because you can get rid of the Assignment-class and table.
Your database-table "computers" then needs a student_id column, with a non-unique index
Your models should look like this:
class Computer < ActiveRecord::Base
belongs_to :student
end
class Student < ActiveRecord::Base
has_many :computers, :dependent => :nullify
end
"dependent nullify" because you don't want to delete a computer when a student is deleted, but instead mark it as free.
Each of your computers can only be assigned to a single student, but you can reassign it to a different student, for example in the next year.
Actually your approach is fine, as one offered by #alexkv. It is more discussion, than question.
Another thing if you want to use mapping table for some other purposes, like storing additional fields - then your approach is the best thing. In has_many :through table for the join model has a primary key and can contain attributes just like any other model.
From api.rubyonrails.org:
Choosing which way to build a many-to-many relationship is not always
simple. If you need to work with the relationship model as its own
entity, use has_many :through. Use has_and_belongs_to_many when
working with legacy schemas or when you never work directly with the
relationship itself.
I can advise you read this, to understand what approach better to choose in your situation:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
http://blog.hasmanythrough.com/2006/4/20/many-to-many-dance-off
You can also use has_and_belongs_to_many method. In your case it will be:
class Student < ActiveRecord::Base
has_many :assignemnts
has_and_belongs_to_many :computers, :join_table => 'assignments',
end
class Computer < ActiveRecord::Base
has_one :assignment
has_and_belongs_to_many :student, :join_table => 'assignments',
end
or you can rename assignments table to computers_students and remove join_table
class Student < ActiveRecord::Base
has_many :assignemnts
has_and_belongs_to_many :computers
end
class Computer < ActiveRecord::Base
has_one :assignment
has_and_belongs_to_many :student
end
Trying to figure out the best way to set up my models. Here's what I've going going on...
Models: Dog, Video, Photo, User
class Dog < ActiveRecord::Base
has_many :videos
has_many :photos
belongs_to :user
end
class Video < ActiveRecord::Base
has_many :dogs
belongs_to :user
end
class Photo < ActiveRecord::Base
has_many :dogs
belongs_to :user
end
class User < ActiveRecord::Base
has_many :dogs
has_many :videos
has_many :photo
end
Should I do has_many :through and a polymorphic?
In my previous iteration of this, I had DogPhoto and DogVideo models, but seems like I could have a single DogItem model that's polymorphic.
Thoughts?
Yes, a polymorphic association would apply here, and it would be a good practice. However, i think that you should have a new model like DogMedia or so, that would be the polymorphic association.
This way a user has a dog and a dog has many DogMedias. A DogMedia is a polymorphic association that can either be a photo, video or anything else you like :)
You could use has many through to get dogmedia for a user's dog directly yes. Something like :
User has many dog_medias through dog (plain language)
or just traverse it through dog :
user.dog.dog_media
If you do the latter, you can even create a Media instead of DogMedia class, create a delegate and execute the neat :
user.dog_media
directly ( The law of demeter : http://en.wikipedia.org/wiki/Law_of_Demeter )
I have the following model:
class Advisor < ActiveRecord::Base
belongs_to :course
end
class Course < ActiveRecord::Base
has_many :advisors
has_many :sessions
has_many :materials, :through=>:sessions
end
class Session < ActiveRecord::Base
belongs_to :course
has_many :materials
end
class Material < ActiveRecord::Base
belongs_to :session
end
I.e., every advisor teaches one course, every course has sessions, and every session has materials.
I want to traverse from an advisor to all the associated materials, i.e. something like: Advisor.first.materials
I tried to do:
class Advisor < ActiveRecord::Base
belongs_to :course
has_many :sessions, :through=>:course
has_many :materials, :through=>:sessions
end
But it didn't work as it treated sessions as a many-to-many table: Unknown column 'sessions.advisor_id' in 'where clause': SELECT 'material'.* FROM 'materials' INNER JOIN 'sessions' ON 'materials'.session_id = 'sessions'.id WHERE (('sessions'.advisor_id = 1))
I then tried to do:
class Advisor < ActiveRecord::Base
belongs_to :course
has_many :materials, :through=>:course
end
In an attempt to have the association use the "materials" association in the "Course" model, but received:
ActiveRecord::HasManyThroughSourceAssociationMacroError: Invalid source reflection on macro :has_many :through for has_many :materials, :through=>:sessions. Use :source to specify the source reflection.
Tried to use "sessions" as the source which was a nice try but made me receive only the sessions rather than the materials.
Any ideas if this is at all possible?
I'm using Rails 2.3.8 (perhaps time to upgrade?)
Thanks!
Amit
I want to traverse from an advisor to
all the associated materials
Unless I'm missing something, using the associations specified in your first example, you can simply call materials on the associated course:
a = Advisor.first
materials = a.course.materials
Using a has_many,through association for another has_many,:through relation will not work in rails
instead of creating a association you can just create a method to access all the materials by a Advisor
def materials
sessions.collect(&:materials).flatten
end
This will work, but you wont be able to chain find queries to this method. If you want to be able to chain find methods something like #advisor.materials.find.. Then inside this method use Material.find() with appropriate conditions