Say I have two tables, a master list of students containing personal info, and a list of student enrollments in classes. The two tables share a common column, which is a string uniquely identifying the student, but it is not the primary key.
Say I want to display all the enrollments on a page, along with some of the personal data from the student (say perhaps hometown).
I understand that it would be a has_many relationship. The master list record has many enrollments. An enrollment belongs to a student.
class Student < ActiveRecord::Base
has_many :enrollments
end
class Enrollment < ActiveRecord::Base
belongs_to :student
end
Is this the correct relationship between the two, and if so, how do I do a join query against the shared column?
Yes ActiveRecord will manage the relationships for you, but you can also specify the join when searching for a condition in the relationship. For example:
User.find(:all, :joins => :phone_numbers, :conditions => { :phone_numbers => {:name => 'business'} })
Note though using a hash for the conditional declaration is only in Rails 2.2
But most of the time just using the ActiveRecord relationships with has_many should be just fine and as mentioned in above answers you would call the object using as if it was directly on the model... #student.enrollments
Ideally, Rails is expecting the following columns:
Student table:
- id
Enrollment table:
- student_id
In doing so, your relationship should work.
Then to try it out, drop into the Rails console and play around:
#student = Student.first
#student.enrollments
Take a look at this great Rails reference on using associations:
http://guides.rubyonrails.org/association_basics.html
What mwilliams says plus make sure you want a many to many association when you follow the guide.
The joined query is done automatically by association through ActiveRecord.
Related
I have two models and the association between them is has_and_belongs_to_many.
I need to refactor the code as it causes n + 1 queries.
Problem: n+1 queries
1. Task
class Task < ActiveRecord::Base
has_and_belongs_to_many :user_groups
end
2. UserGroup
class UserGroup < ActiveRecord::Base
has_and_belongs_to_many :tasks
end
Requirement:
Assign Tasks to user_groups
Previous Controller Code:
task_ids.each do |task_id|
find(task_id).update_attributes! user_group_ids: params[:user_group_ids], release_date: params[:release_date]
end
My Attempt:
tasks = Task.where(id: task_ids)
tasks.update_all(user_group_ids: params[:user_group_ids], release_date: params[:release_date])
Error:
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column
"user_group_ids" of relation "tasks" does not exist
Query generated:
SQL (0.6ms) UPDATE "tasks" SET "user_group_ids" = 4,
"release_date" = '2017-04-27 07:40:26.525357' WHERE "tasks"."deleted_at" IS NULL AND "tasks"."id" = 47394
Please let me know how to do it with update_all
Why did you have to create the UserGroup model? That's not the way we use a habtm relationship.
And your model Task, in the middle of it all, kind of confused me. What do you need to do? I suspect that you don't have a habtm relationship in Task.
For the error you're getting, it's totally understandable. You do not have an attribute (column) called user_group_ids in the tasks table. And also, for a habtm relationship, this attribute user_group_ids seems misspelled to me.
Anyway, for a habtm relationship, if your User has many Groups and a Group can have many Users, you're in the right path, however for Ruby on Rails there's two ways you can accomplish that.
If you don't need to manage other data (relationship attributes) in the relationship table:
Then you'll need only the table groups_users, in your case. Rails will look for habtm relationship table in alphabetic order.
If your groups_users relationship holds any other data than model's reference ids, then it's better for you to use a has_many :through relationship.
In this case your UserGroup (mind the singularity) table will hold the additional data you need in the relationship. Then you declare as follows:
User model will have declared: has_many :groups, through: :user_groups
Group model will have declared: has_many :users, through: :user_groups
UserGroup model: belongs_to :user and belongs_to :group. Be careful not to index any of those as unique, database or application level.
For more details check http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
Hope you get your results!
Your solution (without causing an N+1) is activerecord-import
You can't mass-assign the association using update_all. update_all does a single UPDATE query but for association you actually need an INSERT query to insert the associated models. You could use a gem like bulk_insert to insert all the join objects with one INSERT query https://github.com/jamis/bulk_insert
TL;DR: How do I use the ID of the respective parent object in a has_many SQL clause to find child objects?
Long version:
I have the following example code:
class Person < AR::Base
has_many :purchases, -> {
"SELECT * from purchases
WHERE purchase.seller_id = #{id}
OR purchase.buyer_id = #{id}"
}
This was migrated from Rails 3 which worked and looked like
has_many :purchases, :finder_sql => proc { #same SQL as above# }
I want to find all purchases associated with a Person object in one association, no matter whether the person was the one selling the object or buying it.
Update: I corrected the SQL, it was inside out. Sorry! Also: The association only needs to be read-only: I am never going to create records using this association, so using id twice should be OK. But I do want to be able to chain other scopes on it, e.g. #person.purchases.paid.last_5, so creating the sum of two associations in a method does not work (at least it didn't in Rails 3) since it doesn't return an AM::Relation but a simple Array.
When using this above definition in Rails 4.2, I get
> Person.first.purchases
undefined method `id' for #<Person::ActiveRecord_Relation:0x...>
The error is clear, but then how do I solve the problem?
Since this is only an example for much more complicated SQL code being used to express has_many relationships (e.g. multiple JOINS with subselects for performance), the question is:
How do I use the ID of the parent object in a has_many SQL clause?
I don't think your code will work at all. You are defining an association with two foreign keys ... that'd mean that in case you want to create a new Person from a present Purchase, what foreign key is to be used, seller_id or buyer_id? That just don't make sense.
In any case, the error you are getting is clear: you are calling a variable id which is not initialized in the block context of the SQL code.
A better approach to the problem I understand from your question would be to use associations in the following way, and then define a method that gives you all the persons, both buyers and sellers that a product has. Something like this:
class Purchase < ActiveRecord::Base
belongs_to :buyer, class_name: 'Person'
belongs_to :seller, class_name: 'Person'
def persons
ids = (buyer_ids + seller_ids).uniq
Person.where(ids: id)
end
end
class Person < ActiveRecord::Base
has_many :sold_purchases, class_name: 'Purchase', foreign_key: 'buyer_id'
has_many :buyed_purchases, class_name: 'Purchase', foreign_key: 'seller_id'
end
Im my approach, buyer_id and seller_id are purchase's attributes, not person's.
I may have not understood correctly, in that case please clarify.
Sorry, I am not very advanced in mapping out databases
I have a model similar to this: A teacher can have many students, and a student can have many teachers. So How would I make this? If a student could have only one teacher I know I would set an attribute like: teacher_id: integer, then when I want to create a student it would be similar to this
Student.create(:teacher_id => id)
or query similar to this:
Student.where(teacher_id: id)
Teacher.find(student.teacher_id)
But I am unsure of how to accomplish this if both are has_many relationships
You can use rails has_and_belongs_to_many relationship for your requirement. check this link for reference: http://guides.rubyonrails.org/association_basics.html#has-and-belongs-to-many-association-reference
you need to use this relationship like this:
teacher.rb
has_and_belongs_to_many :students
student.rb
has_and_belongs_to_many :teachers
then you need to add a migration to create a join table containing teacher_id and student_id
you should create name of your migration in alphabetical order like this:
rails g migration create_join_table_for_students_teachers student_id:integer teacher_id:integer
and then rake db:migrate
then you can access students of a single teacher like teacher.students etc.,
Hope this might help you in some way please go through the reference link once
You're describing a has-and-belongs-to-many (HABTM) relation, which Rails supports in two ways: has_and_belongs_to_many or has_many :through. You can read about them in the Active Record Associations Rails Guide, which also gives you tips and how to choose which one is appropriate for your application.
Better use has_many:through relation.
I'm using some open gov data in MYSQL which I've imported into my rails App.
I've extended the database with some of my own tables, and use the rails provided column ids in my tables.
However, the tables of the original database are linked through a unique 'ndb' id.
I thought that by cross-referencing two :foreign_key=>'ndb' between the models I would get the tables linked properly, but through :foreign_key, it appears to be linking the id from one table to the ndb column of another.
My models look like this
class Food < ActiveRecord::Base
has_many :weights, :foreign_key=>'ndb'
has_many :food_names
end
class Weight < ActiveRecord::Base
belongs_to :food, :foreign_key=>'ndb'
Is there a way to specify that the 'ndb' column is the link between the food and weights table, and not the food_id to ndb?
Have you tried set_primary_key 'ndb' in your AR classes?
set_primary_key docs.
What I've done as a possibly temporary solution, is to used the :finder_sql to link the tables together.
My Food model now has:
class Food < ActiveRecord::Base
has_many :weights, :finder_sql =>'Select weights.* FROM weights LEFT JOIN foods ON foods.ndb=weights.ndb WHERE foods.id=#{id}'
I'm not sure if this is the best solution or not, but it seems to be working.
I have the following entities:
User
Company
Organization
Users need to be able to add Users, Companies, Organizations, and future party objects to their friend list.
My original idea involved using a Friendship object with a polymorphic relationship to the friend like so:
Simplified Friendship schema:
user_id
friendable_id
friendable_type
User
- has_many :businesses, :through => :friendships, :conditions => ['friendable_type=?', 'Business'], :source => :friendable
My problem is that Ruby on Rails ActiveRecord does not support has_many through relationships through polymorphic relationships on the join table such.
In the end I want to have a single join table to be able to iterate through to get a list of friends of all types, certain types, etc. like below:
john = User.find(5)
john.friendships.map {|friendship| friendship.friendable }
john.businesses (has_many :through scoped to only the businesses)
john.organizations (has_many :through scoped only to the organizations)
I've considered making the User, Business, and Organization inherit from a generic Party class and making the association point to the base Party class, but in the context of an ORM that leads to a bunch of junk fields and it just seems dirty.
What I'd like to know is how others would approach a situation like this where you want to create one-to-many relationships to similar objects through a common join table using ActiveRecord while avoiding this error: :)
Cannot have a has_many :through association 'User#businesses' on the polymorphic object 'Friendship#friendable'.
Any advice greatly appreciated.
Thanks!
Do not use :through, use :as=>friendable instead in polymorphic has_many relationship
It appears that the Rails plugin 'has_many_polymorphs' allows me to do exactly what I want.
http://github.com/fauna/has_many_polymorphs/tree/master
Adding this line to my User model gave me the functionality I wanted:
has_many_polymorphs :friendables, :from => [:businesses, :organizations], :through => :friendships