I apologize if this is a big question. I used to be good at rails but it has been a long time.
I am working a rails project and am having trouble with the has_many relation.
I have the following tables:
User
SchoolClass
Question
UserClassQuestion
Inside the models i have:
user.rb
has_many :questions, :through => :user_class_questions
has_many :user_class_questions
school_class.rb
has_many :questions, :through => :user_class_questions
has_many :user_class_questions
question.rb
belongs_to :schoolclass
belongs_to :user
user_class_question.rb
belongs_to :question
So, what I want is to on a users home page, display let them view the questions they have asked with current_user.questions. This works.
But on the SchoolClass show page, if i say #school_class.questions, i get the following error:
!!<ActiveRecord::StatementInvalid: SQLite3::SQLException: no such colum:
user_class_questions.school_class_id: SELECT "questions".* FROM "questions"
INNER JOIN "user_class_questions" ON "questions"."id" =
"user_class_questions"."question_id" WHERE
"user_class_questions"."school_class_id" = ?>
#school_class is an object set by the params
def set_school_class
#school_class = SchoolClass.find(params[:id])
end
The columns in UserClassQuestion table are:
{ user_id: , schoolclass_id: , question_id }
So, do I have something set up wrong?
The error you're experiencing is most likely due to the variation in your column name. In Rails, school_class_id is not the same as schoolclass_id. I think a quick way to work around, is to specify the primary_key/foreign_key in your belongs_to option.
A more lasting solution is to run a migration to rename your column to school_class_id rather than schoolclass_id.
Let me know if I was able to help.
UPDATE
if the belongs_to :school_class is defined on question, then you have access to question.school_class and your school_class model should just have has_many :questions, no need of running to a join table to do that as that would confuse rails a bit. If however you don't want it this way you could comment out the belongs_to :school_class assoication and instead have a has_many :user_class_questions on question
class User
has_many :questions, through: :user_class_questions
has_many :user_class_questions
end
class SchoolClass
has_many :questions, through: :user_class_questions
has_many :user_class_questions
end
class Question
#belongs_to :school_class
belongs_to :user
has_many :user_class_questions
end
class UserClassQuestion
belongs_to :question
belongs_to :school_class
belongs_to :user
end
Your Data model needs a bit of a change, your UserClassQuestion is a join model and it should have data about the User, SchoolClass, and Question. So the relationship should be something like
UserClassQuestion
rails g model UserClassQuestion user:references school_class:references question:references
class UserClassQuestion < ActiveRecord::Base
belongs_to :user
belongs_to :school_class
belongs_to :question
end
school_class.rb
class SchoolClass < ActiveRecord::Base
has_many :user_class_questions
has_many :questions, through: :user_class_questions
end
user.rb
class User < ActiveRecord::Base
has_many :user_class_questions
has_many :questions, through: :user_class_questions
end
And then you can do user.questions and school_class.questions
Also try not to use has_many_and_belongs_to association, I feel it is a bad idea, it creates a table without a primary id, and later on if you want to do something else with this join table, then this will come and bite you. has_many, through is a bit extra step but it is worth it.
Related
I'm having problems with a Rails 4 join table. I have quite a simple setup which is working elsewhere in my application using a non-conventionally named table for users, groups and usergroupmemberships. I'm trying to set it up this time using the proper conventional naming and it's just not working.
Models involved are User, ManagementGroup and ManagementGroupsUser
db tables: management_groups_user, management_groups, users
app/models/user.rb
Class User < ActiveRecord::Base
...
has_many :management_groups, through: management_groups_users
has_many :management_groups_users
....
app/models/management_group.rb
class ManagementGroup < ActiveRecord::Base
has_many :users, through: :management_groups_users
has_many :management_groups_users
app/models/management_groups_user.rb
class ManagementGroupsUser < ActiveRecord::Base
belongs_to :users
belongs_to :management_groups
The association appears to work from with #user.management_groups_users but nothing else. I'm fairly sure this is a problem with naming / plurality but I can't figure it out.
This is the model which joins the remaining models user.rb and management_group
#app/models/management_groups_user.rb
belongs_to :user
belongs_to :management_group
Since we are going to use model above to access another model management_group then
#app/models/user.rb
has_many :user_management_groups #This should come first
has_many :management_groups, through: user_management_groups
Since we are going to use model above to access another user model then
app/models/management_group.rb
has_many :user_management_groups
has_many :users, through: :user_management_groups
Now should work
Do it this way.
app/models/user.rb
has_many :user_management_groups
has_many :management_groups, through: user_management_groups
app/models/management_group.rb
has_many :user_management_groups
has_many :users, through: :user_management_groups
app/models/management_groups_user.rb
belongs_to :user
belongs_to :management_group
I hope these associations will help you.
This is another way if you pass foreign key and class name.
app/models/user.rb
has_many :user_management_groups, :foreign_key => "key", :class_name => "ClassName"
has_many :management_groups, through: user_management_groups, :foreign_key => "key", :class_name => "ClassName"
app/models/management_group.rb
has_many :user_management_groups
has_many :users, through: :user_management_groups
app/models/management_groups_user.rb
belongs_to :user, class_name: "ClassName"
belongs_to :management_group, class_name: "ClassName"
This is another way around.
It's important to realize there is a convention rails uses for HABTM and has many through. HABTM does not have a model, so it needs to infer the table name which, as others point out, is both plural and alphabetical order.
If you are doing has many through and have a model, the convention is that it wants singular first word, plural second. See examples
User has and belongs to many groups.
HABTM: table name should be groups_users
Has Many Through: table name should be user_groups (flip order is more intuitive)
Model for the latter would be UserGroup. Has many through would specify it as through: :user_groups
I'm trying to query on ActiveRecord multiple nested eager loaded associations with conditions like so:
user.books.includes(slot: [room: :school]).where("books.slot.room.school.id = 1")
Obviously this query is wrong, but basically what I'm trying to reach is a relation of user.books for a certain school.
My model structure is:
class User < ActiveRecord::Base
has_many :books
has_many :slots, through: :books
has_many :rooms, through: :slots
has_many :schools
end
class Book < ActiveRecord::Base
belongs_to :user
belongs_to :slot
end
class Slot < ActiveRecord::Base
has_many :books
belongs_to :room
end
class Room < ActiveRecord::Base
belongs_to :school
has_many :slots
end
class School < ActiveRecord::Base
has_many :users
has_many :rooms
has_many :slots, through: :rooms
has_many :books, through: :slots
end
Any ideas? Thanks in advance.
includes eager loads association records, while joins does what you want to do.
This question has been asked here numerous times. Please make sure that you look and try to find a similar question here before you ask one.
So you want to change your code like this:
user.books.joins(slot: [room: :school]).where(schools: { id: 1 })
Rails 4.2 newbie:
2 Questions;
1) Is the first has_many redundant? Since its name is a plural of Save Class?
can I have only:
has_many :savers, through: :saves, source: :saver
Or even better;
has_many :savers, through: :saves
If the answer is yes, where can I set "dependent: :destroy"?
class Post < ActiveRecord::Base
belongs_to :user
has_many :saves, class_name: "Save", foreign_key: "saver_id", dependent: :destroy
has_many :savers, through: :saves, source: :saver
end
class Save < ActiveRecord::Base
belongs_to :saver, class_name: "User"
validates :saver_id, presence: true
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
...
end
2) This is the typical blog model, where user can 'save' posts posted by another user to their timeline. Does this model make use best practices? Specially in db performance, doing a Join to get posts saved by a User. The 'Save' table that will have 100MM rows?
Lets first alter your example a bit to make the naming less confusing:
class User
has_many :bookmarks
has_many :posts, through: :bookmarks
end
class Post
has_many :bookmarks
has_many :users, through: :bookmarks
end
class Bookmark
belongs_to :user
belongs_to :post
end
Lets have a look at the query generated when we do #user.posts
irb(main):009:0> #user.posts
Post Load (0.2ms) SELECT "posts".* FROM "posts" INNER JOIN "bookmarks" ON "posts"."id" = "bookmarks"."post_id" WHERE "bookmarks"."user_id" = ? [["user_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy []>
Now lets comment out has_many :bookmarks and reload:
class User
# has_many :bookmarks
has_many :posts, through: :bookmarks
end
irb(main):005:0> #user.posts
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :bookmarks in model User
So no, the first has_many is not redundant - in fact its the very core of how has_many through: works. You setup a shortcut of sorts through another relation.
Note in has_many :posts, through: :bookmarks :bookmarks is the name relation we are joining through. Not the table which contains the joins.
To fix your original code you would need to do:
class Post < ActiveRecord::Base
has_many :saves, dependent: :destroy
has_many :savers, through: :saves
end
class Save < ActiveRecord::Base
belongs_to :saver, class_name: "User"
belongs_to :post # A join table with only one relation is pretty worthless.
validates :saver_id, presence: true
end
class User < ActiveRecord::Base
has_many :posts
has_many :saves, dependent: :destroy
has_many :posts, through: :saves
end
Note that you don't need half the junk - if you have has_many :savers, through: :saves ActiveRecord will look for the relation saver by itself. Also you only want to use dependent: destroy on the join model - not on the post relation as that would remove all the posts a user has "saved" - even those written by others!
Teaching Rails myself, I want to learn the professional way to use the framework and following Rails guidelines best practices. That's not easy, because I usually find answers that 'just works'
I'll try to answer myself and maybe it could be useful for Rails Newbies:
Using has_many through, association, Rails firstly infers the association by looking at the foreign key of the form <class>_id where <class> is the lowercase of the class name, in this example; 'save_id'.
So, if we have the column name 'save_id', we will have the following simplified model:
class Post < ActiveRecord::Base
belongs_to :user
has_many :saves, through: :saves
end
class Save < ActiveRecord::Base
belongs_to :savers, class_name: "User"
validates :save_id, presence: true
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
...
end
I searched for quite a long time and couldnt find that problem.
user.erb
has_many :workouts
has_many :result_units
workout.erb
belongs_to :user
has_many :sets
set.erb
belongs_to :workout
has_one :result_unit
result_unit.erb
belongs_to :user
belongs_to :set
1 possible Solution is that ResultUnit dont belong to User. But the question is then how much performance it will cost to query User.workouts.all.sets.all.resultunits.all
How could i create a new ResultUnit for User and Set?
This is a case for using a has_many :through association.
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Running User.workouts.all.sets.all.resultunits.all will result in numerous queries being executed. A has_many :through however, will execute only a single query and allow the database to optimize the joins between tables.
class User < ActiveRecord::Base
has_many :workouts
has_many :sets, through: :workouts
has_many :result_units, through: :sets
end
Ok, I didn't understand your problem 100%.. but I'm gonna have a stab at it and feel free to downvote if it's not right.
class User
has_many :workouts
has_many :result_units
has_many :sets, through: :workouts
# User.first.workouts
# User.first.result_units
# User.first.sets
end
class Workout
belongs_to :user
has_many :sets
# Workout.first.user
# Workout.first.sets
end
class ResultUnit
belongs_to :user
belongs_to :set
# ResultUnit.first.user
# ResultUnit.first.set
end
class Sets
belongs_to :workout
has_one :result_unit
# Set.first.workout
# Set.first.result_unit
end
I'm stuck on this:
class Worker < ActiveRecord::Base
has_many :skills
has_many :jobs, through: :skills
..
end
class Skill < ActiveRecord::Base
belongs_to :worker
has_many :jobs
..
end
class Job < ActiveRecord::Base
has_many :skills
has_many :workers, through: :skills
..
end
What I'm trying to do is set up a many to many between Skill and Job inside of the `has_many' through relationship?
My question has three parts
Is this possible - using the has_many jobs rather than belongs_to jobs.
If it can be done and the code is wrong, how do I fix it?
How can I create Worker, Skill and Job records? (looking for syntax)
This is a picture (of sorts) of what I'm trying to do, hope it helps... :(
You're not giving active record enough information about your relationships. Every :has_many should have a corresponding :belongs_to so that active record knows which table holds the foreign key for each association. Notice that you only have one :belongs_to for three relationships. That smells.
As for fixing the problem, you have at least 2 options:
add :has_and_belongs_to_many associations
use explicit join tables
My preference is for the latter option. Being forced to name join tables often clarifies the nature of a relationship. On the flip side, I've found that :has_and_belongs_to_many is often too implicit and ends up making my designs more obscure.
Explicit join tables
You might setup your relationships like this (untested):
class Assignment < ActiveRecord::Base
belongs_to :worker
belongs_to :job
end
class Qualification < ActiveRecord::Base
belongs_to :worker
belongs_to :skill
end
class Worker < ActiveRecord::Base
has_many :qualifications
has_many :skills, through: :qualifications
has_many :assignments
has_many :jobs, through: :assignments
..
end
class Skill < ActiveRecord::Base
has_many :qualifications
has_many :workers, through: :qualifications
has_many :jobs
..
end
class Job < ActiveRecord::Base
has_many :skills
has_many :workers, through: :assignments
..
end
By making the relationships more explicit I think the model is clearer. It should be easier to troubleshoot from here.
EDIT:
If you need to do a traversal like Job.find(1).qualified_workers try making the following adjustment to the above model:
class Job
has_many :required_competencies
has_many :skills, through: :required_competencies
has_many :qualifications, through: :skills
has_many :qualified_workers, through: qualifications, class_name: :workers
end
class RequiredCompetency
belongs_to :job
belongs_to :skill
end
This is explicit about each traversal and names it. If you find these paths through your system are getting really long, I'd consider that a smell. There might be a more direct way to fetch your data or perhaps a better way to model it.