ActiveRecord has_many :through association through multiple sources - ruby-on-rails

I have a self referencing has_many :through model that has another has_and_belongs_to_many with another model. Basically something like this:
class Foo << ActiveRecord::Base
has_and_belongs_to_many :bars
has_many :foo_links
has_many :foo_parents, :through => :foo_links, :foreign_key => :foo_parent_id, :class_name => "Foo"
has_many :foo_children, :through => :foo_links, :foreign_key => :foo_child_id, :class_name => "Foo"
end
I'd like to be able to have a foo_child item be able to belong to any bars to which it is assigned, as well as any bars to which one of its foo_ancestors (foo_parents and their foo_parents, etc) is assigned. I was basically hoping to put together something like this:
has_many :inherited_bars, :through => :foo_parents, :source => [:bars, :inherited_bars]
I've never seen such an example, but I was wondering if it is possible to have an association that is a merger of associations from a through association.

I think has_many association always tied to have an id somewhere to indicate the relationship, and allows you to modify this. Eg. you can add a new element to a has_many array, and the result is persisted back to the database. If you can merge two sources together, you lose the ability to link the rows by this.
A possible approach is this, readonly way:
has_many :a
has_many :b
def sum
a + b
end

Related

Related model can be one of two other models

I currently have essentially 3 models at the moment. I have Project, User and Contact.
I'm trying to assign users or contacts as a sort of 'member' to each project. I initially thought that a linking table here would suffice, for example ProjectMembers but i'm currently hitting a brick wall in my thought process when it comes to a project member only being allowed to be either a Contact or a User and whether to handle this via a relationship or by code in the model or the controller by checking which between user_id or contact_id was not null.
I had a look at polymorphic associations which looked promising, but somehow I ended up with the association backwards (Projects were being entered into the ProjectMember table as the type, rather than User or Contact) and confused myself even more.
The final output I would pretty much like would be simply to have the ability to run something like Project.first.project_members and have those members return with the role in that project. It'd be even nicer if I could run User.first.projects/Contact.first.projects and get those too, but that's something I can figure out down the line.
Any pointers would be greatly appreciated.
Polymorphic association is something that you should go for here.
First: Project has a many to many relationship with User and Contact, hence you need a join table.
Second: Since you need to call something like Project.first.project_members, you should have a polymorphic association.
For this scenario, you should create a polymorphic join table.
Let's say you have the polymorphic table ProjectMember with memberable_id, memberable_type and project_id (well I have a hang of railcasts).
class ProjectMember < ActiveRecord::Base
belongs_to :memberable, :polymorphic => true
belongs_to :project
end
and then in your User model,
class User < ActiveRecord::Base
has_many :project_members, :as => :memberable
has_many :projects, through: :project_members
end
and in Contact model
class Member < ActiveRecord::Base
has_many :project_members, :as => :memberable
has_many :projects, through: :project_members
end
and in your Project model
class Project < ActiveRecord::Base
has_many :users, :through => :project_members, :source => :memberable, :source_type => 'User'
has_many :contacts, :through => :project_members, :source => :memberable, :source_type => 'Contact'
has_many :project_members
end
Now you can call your desired associations, Project.first.project_members, User.first.projects or Contact.first.projects. Hope this helps.
Thanks

Querying through a self join reference in rails

I have a model of users and things
Users can have one leader and many followers which are other users. User can also have things
So I have these definitions which use a self join relationship:
class User < ActiveRecord::Base
has_many :things, dependent: :destroy
has_many :followers, :class_name => 'User', :foreign_key => 'leader_id'
belongs_to :leader, :class_name => 'User', :foreign_key => 'leader_id'
end
class Thing < ActiveRecord::Base
belongs_to :user
end
So I can query ask for a list of things that a user has by asking, for example User.first.things. I can also get a list of followers of a user with User.first.followers.
How do I get a list of things that a user's followers have. I think I might need to use a has_many through relationship but I can't seem to figure it out as I'm not sure how to deal with the fact that a Leader can have things through a 'follower' but also directly themselves
Thanks
Something like this:
def all_things
Thing.where(:user_id => followers.map(&:id).push(id))
end
It returns a scope so you should be able to continue the chain, for example:
User.first.all_things.visible
Update
If you are only interested in the followers' things without adding the user's things to the batch it is better is you do it directly with a has_many through:
has_many :followers_things, :through => :followers, :source => :things
Check this other SO thread
How about:
all_things = (your_user.followers.each { |f| f.things }).flatten
This should return an array containing things that belong to all your_user's followers.

How to access referenced table in a n:m relation in Ruby on Rails?

I have got a Database like this:
users
-id
user_cars
-user_id
-car_id
cars
-id
-model
-color
So a user can have multiple cars, and detailed information about the cars are in a big cars table. I also created the models with the relationships.
class User
has_many :user_cars
class User_car
belongs_to :user
belongs_to :cars
class Car
has_many :user_cars
Now I want to access the information for all the cars for one user. My first approach would be to get at least one information (i.e. color) from the cars table.
I tried this one, just as an example for accessing the middle table:
#user_id = current_user.user_cars.find(1).user_id
works! But when I try to access the cars table I always get an error.
#color = current_user.user_cars.cars.find(1).color
undefined method `cars' for #<ActiveRecord::Relation:0xaf92e8c>
So I think Im doin something easy very wrong...
When I know how to get access to the third table, I have to do it in that fashion, that I only get results for the user and not just only the first entry, maybe you guys can help me with that aswell. Thanks!
The issue in your example by the way is that belongs_to should be singular. Also, your order was wrong.
#color = current_user.user_cars.find_by_car_id(1).car.color
You should rewrite this to use a has_many through association:
class User
has_many :user_cars
has_many :cars, :through => :user_cars
class UserCar
belongs_to :user
belongs_to :car
You can then access the cars by doing:
current_user.cars
And the color by doing:
#color = current_user.cars.find_by_car_id(1).color
EDIT
After some debugging, it turns out that the Car model has a class property. Class is a reserved word in ruby. Be careful with naming your attributes!
Without has_many :through associations:
#color = current_user.user_cars.where(:car_id => 1).first.color
With them:
class User < ActiveRecord::Base
has_many :user_cars, :foreign_key => :user_id, :class_name => "UserCar", :inverse_of => :user
has_many :cars, :through => :user_cars
end
class UserCar < ActiveRecord::Base
belongs_to :user
belongs_to :car
end
class Car < ActiveRecord::Base
has_many :user_cars, :foreign_key => :car_id, :class_name => "UserCar", :inverse_of => :car
has_many :cars, :through => :user_cars
end
#color = current_user.cars.find(1).color
http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
:through defines a shortcut
:inverse_of defines a method (association) which represents current model in :class_name model
:class_name defines which model should be represented by :user_cars
:foreign_key tells which column in the target model's table represents current model (in table user_cars (or users_cars, depends on how you define the association, i think it should be user_cars in this example.. and users_cars for has_and_belongs_to_many)
Details on those are in the link above.

Right way to force uniqueness on a join model? (has_many :through)

I have a parent/child relationship via our users table, with models as such:
class User < ActiveRecord::Base
# Parents relationship
has_many :children_parents, :class_name => "ParentsChild", :foreign_key => "child_id", :dependent => :destroy
has_many :parents, :through => :children_parents
# Children relatiopnship
has_many :parents_children, :class_name => "ParentsChild", :foreign_key => "parent_id", :dependent => :destroy
has_many :children, :through => :parents_children
...
end
And in parents_child.rb:
class ParentsChild < ActiveRecord::Base
belongs_to :parent, :class_name => "User"
belongs_to :child, :class_name => "User"
end
Right now, it is possible in our "add children" form (just using vanilla nested attributes) to add the same user as a child multiple times for parents. I am not sure what the 'right' way to go about forcing uniqueness in the ParentsChild relationship, although I am leaning towards a unique index on (parent_id, child_id) at the database layer (using a migration of course).
I am sure I could also enforce uniqueness constraints in the UsersController::update method, but would prefer to avoid changing that code (right now it doesn't reference parents/children at all, thanks to nested attributes in the form/model) if possible. I am most concerned with making sure we use the "proper" solution. What is the 'right' or 'rails' way to do this?
Using has_many :through, you can specify :uniq as an option, like this:
has_many :parents, :through => :children_parents, :uniq => true

How can I have two columns in one table point to the same column in another with ActiveRecord?

I run the risk of palm-to-forehead here, but I can't quite figure out how to do this with Rails' ActiveRecord sugar.
I have a tickets table that has two columns (submitter_id and assignee_id) that should each reference a different user from the users table (specifically the id column in the users table). I'd like to be able to do things like ticket.submitter.name and ticket.assignee.email using ActiveRecord's associations. Submitter and Assignee are simply user objects under different associative names.
The only thing I've found that comes close to what I am doing is using polymorphic associations, but in the end I'm fairly certain that it's not really what I need. I'm not going to have multiple types, both submitter and assignee will be users, and very well could be two different users.
Any help would be fantastic. Thanks!
class Ticket < ActiveRecord::Base
belongs_to :submitter, :class_name => "User"
belongs_to :assignee, :class_name => "User"
end
Should work.
Edit: Without trying it out, I'm not sure whether you need the :foreign_key parameter or not. My instinct is not, but it couldn't hurt.
Edit again: Sorry, left off the User -> Ticket associations. You didn't mention using them, and I typically will only add associations in one direction if I don't plan on using them in the other direction.
Anyway, try:
class User < ActiveRecord::Base
has_many :assigned_tickets, :class_name => "Ticket", :foreign_key => "assignee_id"
has_many :submitted_tickets, :class_name => "Ticket", :foreign_key => "submitter_id"
end
Something like this should work
class Ticket < ActiveRecord::Base
belongs_to :submitter, :class_name => 'User', :foreign_key => 'submitter_id'
belongs_to :assignee, :class_name => 'User', :foreign_key => 'assignee_id'
end
class User < ActiveRecord::Base
has_many :tickets, :class_name => 'Ticket', :foreign_key => 'submitter_id'
has_many :tickets_assigned, :class_name => 'Ticket', :foreign_key => 'assignee_id'
end
Yes, PreciousBodilyFluids is right we don't need to specify the foreign_key in the Ticket class as rails can infer it from the column name, i.e. submitter_id and assignee_id
But if your association name is different from the column_name_{id} then you will have to specify it, i.e. the User class case

Resources