Rails: Create Model with multiple belongs_to - ruby-on-rails

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

Related

Is the first has_many redundant? - Ruby on Rails Active records associations

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

Ruby: specify has_many :through association when several has_many :through associations are given

i have a simple data set-up with a model for Users and a model for Tasks.
Between these two models i have two has_many :through associations: Fellowships and Assignements. In total i want to specify for a task several followers and several assignees.
I now want to display for a specific task all assignees and all followers.
If there would only be one association I simply could do #task.users. As i have two associations i want to specify by which association i want to get all users.
See my code:
class User < ActiveRecord::Base
has_many :assignments
has_many :tasks, through: :assignments
has_many :fellowships
has_many :tasks, through: :fellowships
end
class Task < ActiveRecord::Base
has_many :assignments
has_many :users, through: :assignments
has_many :fellowships
has_many :users, through: :fellowships
end
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
class Fellowship < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
Let's assume i have a task as
#task = Task.first
I now want to have all assignees and all followers with something like
#assignees = #task.users "over association assignment"
#followers = #task.users "over association followship"
but i don't know how to do this.
Thanks for the help!
You can write in following way.
has_many :assignment_tasks ,through: :assignments ,source: :task
has_many :fellowship_tasks, through: :fellowships, source: :task

Rails multi level association

New to Ruby, Rails, and OOP in general. I've been through the "Ruby on Rails Tutorial: Learn Web Development with Rails" and about 80% through "Agile Web Development with Rails 4" but I can't visually figure this out. I was hoping someone can help me understand, maybe I'm wording it wrong.
Models: User, Team, Membership, Order, OrderLine
It's a pretty basic setup. (short hand, if you will...)
Team belongs_to :user (owner, not terribly relevant here)
Users has_many :teams through: :memberships
Team has_many :users through: :memberships
OrderLine belongs_to :team
Note an OrderLine is assigned to a team, not a whole Order.
I'm trying to display all OrderLines which are associated with the currently logged in user (current_user). While this doesn't actually work, I feel it's close to producing. It also feels super dirty.
def index
#memberships = current_user.memberships.ids
membership_list = #memberships.join(", ")
#OrderLines = OrderLine.where("team_id IN (?)", membership_list)
end
Cheers!
# Model
class User < ActiveRecord::Base
has_many :owned_team, :class_name => "Team"
has_many :teams
has_many :memberships
has_many :order_lines, through: :memberships
class Team < ActiveRecord::Base
belongs_to :faction
belongs_to :region
belongs_to :owner, :class_name => "User"
has_many :users, through: :memberships
has_many :order_lines
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :team
has_many :order_lines, through: :team
# Controller
#OrderLines = current_user.order_lines
Some times all you need is a break.

Get all relations to members in a relation

Let's say I have three models (models changed, I wouldn't use a has_many :through for this kind of relationship normally):
class User << ActiveRecord::Base
has_many :image_users
has_many :images, through: :image_users
end
class ImageUser << ActiveRecord::Base
belongs_to :user
belongs_to :image
end
class Image << ActiveRecord::Base
has_many :image_users
has_many :users, through: :image_users
has_many :comments, through: :image_comments
end
class ImageComment << ActiveRecord::Base
belongs_to :image
belongs_to :comment
end
class Comment << ActiveRecord::Base
has_many :image_comments
has_many :images, through: :image_comments
end
Given a user, how do I select all comments on their images? Ideally I'd do this with ActiveRecord or SQL to avoid loading lots of objects into memory. Hopefully this hasn't been asked before, it was pretty difficult to google for.
You should be able to do this and let Rails handle the SQL:
#user.images.includes(:comments).map(&:comments)
User
has_many :images
has_many :comments
Image
belongs_to :user
has_many :comments
Comment
belongs_to :user
belongs_to :image
and it's gonna be something like this
#user.images.each {|e| e.comments}
If you take the relationships from #railsr's answer, you can do it like this:
Comment.where(image_id: #user.images.pluck(:id))
Granted, this takes two queries, but you could use some raw SQL and make it into one.

Rails 4 has_many through many

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.

Resources