Rails. Association of the same model with many parents - ruby-on-rails

I stuck with this. I have a model Position and I need to set an association where a position can be a compounded position (has related positions) and an ingredient position (has parent(s) position) at the same time.
So I created a table related_positions with :copmound_id and :ingredient_id.
To be clear what I need as an output:
related_positions
compound_id | ingredient_id|
pos_1 | pos_2
pos_1 | pos_3
pos_1 | pos_4
pos_5 | pos_2
pos_5 | pos_6
pos_5 | pos_7
pos_1.ingredients = [pos_2, pos_3, pos_4]
pos_5.ingredients = [pos_2, pos_6, pos_7]
pos_2.compounds = [pos_1, pos_5]
It might be kind of self join but with multiple parents
UPDATE:
I found this How to model a many self-referential relationship with many parents?. Which is very close. But I still can't get it work

From the description shared association can be something mentioned below:
class Position
has_many :related_positions, class_name: "RelatedPosition", foreign_key: "ingredient_id", :source => :relate_position
has_many :compounds, class_name: "RelatedPosition", foreign_key: "compound_id", :source => :compound_position
end
class RelatedPosition
belongs_to :relate_position, class_name: "Position"
belongs_to :compound_position, class_name: "Position"
end

Looks like what you want to do is a self-reference
Something like this might work
class Position
has_many :children, class_name: 'Position', foreign_key: 'parent_id'
belongs_to :parent, class_name: 'Position'
end

Thanks to that post http://blog.hasmanythrough.com/2007/10/30/self-referential-has-many-through I've got what I needed.
So, if someone has a similar case here is my solution:
class Position < ApplicationRecord
has_many :parents, class_name: 'RelatedPosition', foreign_key: 'ingredient_id', dependent: :destroy
has_many :children, class_name: 'RelatedPosition', foreign_key: 'compound_id', dependent: :destroy
has_many :compounds, through: :parents
has_many :ingredients, through: :children
end
class RelatedPosition < ApplicationRecord
belongs_to :ingredient, class_name: 'Position'
belongs_to :compound, class_name: 'Position'
end

Related

Self-Referential Has Many Through with Custom Foreign Keys in Rails

I have a User model and a relationship table called ParentsChildren.
I'm trying to create two relationships on the User model so that User#children returns all of a users children and User#parents returns all of a users parents.
I've managed to get this working before, but I'm doing something wrong right this time, and I'm not sure what it is exactly.
class ParentsChildren < ApplicationRecord
self.table_name = 'parents_children'
belongs_to :parent_user, class_name: 'User'
belongs_to :child_user, class_name: 'User'
end
class User
has_many :parent_relationships, class_name: 'ParentsChildren', foreign_key: :parent_user_id
has_many :child_relationships, class_name: 'ParentsChildren', foreign_key: :child_user_id
has_many :children, through: :parent_relationships, class_name: 'User', source: :child_user
has_many :parents, through: :child_relationships, class_name: 'User', source: :parent_user
end
# => uninitialized constant ParentsChildren::ChildUser
Figured it out. The key was to drop 'User' as the class name for has_many :parents and has_many :users. It's inferred through the given sources.
class User
has_many :parent_relationships, foreign_key: :child_user_id,
class_name: 'ParentsChildren'
has_many :children, through: :parent_relationships,
source: :parent_user
has_many :child_relationships, foreign_key: :parent_user_id,
class_name: 'ParentsChildren'
has_many :parents, through: :child_relationships,
source: :child_user
end

why does column trades.item_id not exist?

I have a relationship model in which two Users can enter into a Trade for the exchange of two Items.
class User < ActiveRecord::Base
has_many :owned_items, class_name: "Item"
has_many :trades_received, class_name: "Trade", through: :owned_items, source: :trades
has_many :trades
has_many :wanted_items, class_name: "Item", through: :trades, source: :item
end
class Item < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: :user_id
has_many :trades, dependent: :destroy
has_many :trade_requesters, through: :trades
has_many :trade_recipients, through: :trades
end
class Trade < ActiveRecord::Base
belongs_to :trade_requester, class_name: "User"
belongs_to :trade_recipient, class_name: "User"
belongs_to :wanted_item, class_name: "Item", foreign_key: :wanted_item_id
belongs_to :collateral_item, class_name: "Item", foreign_key: :collateral_item_id
end
The migration on my Trades table looks like this:
create_table :trades do |t|
t.belongs_to :trade_requester
t.belongs_to :trade_recipient
t.belongs_to :wanted_item
t.belongs_to :collateral_item
end
The stack trace leads to a helper method I'm using to list all Trade requests. That line says #trades = current_user.trades_received.requested.count, and then on down to the model association on User where has_many :owned_items, class_name: "Item". From my understanding, it looks like the trades_received method, which is called through: :owned_items and source: :trades should be referencing the :wanted_item_id foreign key in the migration. But it is not. It works if I create a migration to add item_id, but a Trade needs two items, and so I've split it up into the two wanted_item and collateral_item associations. How do I set that User association up so that it references the Item that is being requested by another User? Should Items has_many :trades, the way I have it, or should Items belongs_to :trades?
Full error:
PG::UndefinedColumn: ERROR: column trades.item_id does not exist
LINE 1: ...LECT COUNT(*) FROM "trades" INNER JOIN "items" ON "trades"."...
^
: SELECT COUNT(*) FROM "trades" INNER JOIN "items" ON "trades"."item_id" = "items"."id" WHERE "items"."user_id" = $1 AND "trades"."approved" IS NULL
tldr: I need to track a bunch of complex has_many :through associations, I don't think my data model is correct, and need help understanding why. Thank you.
You're setting up two has_many :through relationship between User and Item, with Trade as the join table for both. You got some relationship confused. Here is a setup based on your migration:
class User < ActiveRecord::Base
has_many :received_trades, class_name: "Trade", foreign_key: "trade_recipient"
has_many :requested_trades, class_name: "Trade", foreign_key: "trade_requester"
has_many :collateral_items, through: :received_trades
has_many :wanted_items, through: :requested_trades
end
class Item < ActiveRecord::Base
has_many :collateral_items, class_name: "Trade", foreign_key: "collateral_item"
has_many :wanted_items, class_name: "Trade", foreign_key: "wanted_item"
has_many :trade_requesters, through: :wanted_items
has_many :trade_recipients, through: :collateral_items
end
class Trade < ActiveRecord::Base
belongs_to :trade_requester, class_name: "User"
belongs_to :trade_recipient, class_name: "User"
belongs_to :wanted_item, class_name: "Item"
belongs_to :collateral_item, class_name: "Item"
end
##migration
create_table :trades do |t|
t.belongs_to :trade_requester
t.belongs_to :trade_recipient
t.belongs_to :wanted_item
t.belongs_to :collateral_item
end
Some explanation:
Item has_many :collateral_item ## item_id in table collateral_items
Item has_many :collateral_item, class_name: "Trade", foreign_key: "collateral_item"
##collateral_item_id in trades table.
Ok, well. The problem is here:
has_many :trades, dependent: :destroy
And in your Trade model:
belongs_to :wanted_item, ...
belongs_to :collateral_item, ..
Rails cannot handle this automatically.
You need to do one of this steps (depending on what you need in your app):
If you need separate associations:
class User < ActiveRecord::Base
has_many :trades_received, class_name: "Trade", through: :owned_items, source: :wantable_trades
end
class Item < ActiveRecord::Base
has_many :wanted_trades, class_name: 'Trade', inverse_of: :wanted_item, dependent: :destroy
has_many :collateral_trades, class_name: 'Trade', inverse_of: :collateral_item, dependent: :destroy
end
If you need all trades as single association:
Well, you will have a pain in the ass :) In this case you should either select associations manually, or rethink your data model.

Associates the same model twice in mongoid

I have two class Intern::Question and Intern::Answer, and the standard association look like :
class Intern::Question
has_many :intern_answers, class_name: 'Intern::Answer'
end
class Intern::Answer
belongs_to :intern_question, class_name: 'Intern::Question'
end
And now I want to reference twice answer belongs_to question, answer can store parent question and next question, something like :
class Intern::Question
has_many :intern_answers, class_name: 'Intern::Answer'
has_many :node_for_answers, class_name: 'Intern::Answer'
end
class Intern::Answer
belongs_to :intern_question, foreign_key: :intern_question_id, class_name: 'Intern::Question'
belongs_to :next_question, foreign_key: :next_question_id, class_name: 'Intern::Question'
end
But I have try that and get this error :
Mongoid::Errors::AmbiguousRelationship
Found solution here, using inverse_of
class Intern::Question
has_many :intern_answers, class_name: 'Intern::Answer', inverse_of: :intern_question
has_many :node_for_answers, class_name: 'Intern::Answer', inverse_of: :next_question
end
class Intern::Answer
belongs_to :intern_question, foreign_key: :intern_question_id, class_name: 'Intern::Question', inverse_of: :intern_answers
belongs_to :next_question, foreign_key: :next_question_id, class_name: 'Intern::Question', inverse_of: :node_for_answers
end

Trying to 'alias' a polymorphic has_many relationship

I have a User model:
class User < ActiveRecord::Base
has_many :tracks, dependent: :destroy
has_many :tracked_locations, through: :tracks, source: :tracking, source_type: 'Location'
and a Track model (think of it as 'following'):
class Track < ActiveRecord::Base
belongs_to :user
belongs_to :tracking, polymorphic: true
end
The idea here is I will have many models to track / follow so I am using polymorphism. For example I have a Location model:
class Location < ActiveRecord::Base
has_many :tracks, :as => :tracking, :dependent => :destroy
has_many :users, through: :tracks
Now in the console Location.first.users works fine along with User.first.tracked_locations.
Now I will be adding another polymorphic relationship along the lines of Flagged. The user can 'flag' another model with a note etc. So if I add has_many :users, through: :flagged to the Location model for example I need to differentiate between tracking users and flagged users.
I tried:
has_many :tracking_users, through: :tracks, source: :tracking, source_type: 'User'
but I get:
NoMethodError: undefined method `evaluators_for' for #<Location:0x007ff29e5409c8>
Can I even do this or am I missing something simple here?
UPDATE
Based on the answer below I figured it out:
has_many :tracking_users, through: :tracks, class_name: "User", foreign_key: "user_id", source: :user
I'm not 100% on this, but you could try:
has_many :tracking_users, through: :tracks, class_name: "User", foreign_key: "user_id", source: :user
Or you could also just create a class method and do it by hand.
def self.tracking_users
user_ids = tracks.collect(&:user_id)
User.where(id: user_ids)
end
edit: Had a brainfart, changed the "source" up there to :user. That tells what table to actually do the lookup in with the other attribute you've provided. of course it wouldn't be in :tracks

has_many through children of self

I have some model, it is self-referential. It contains somethings, which can be child or parent, or both.
Is it possible to do something like this?
class Class < ActiveRecord::Base
belongs_to :parent, class_name: 'Class', foreign_key: :parent_id
has_many :children, class_name: 'Class', foreign_key: :parent_id
has_many :somethings, foreign_key: :something_id
has_many :somethings, through: :children, foreign_key: :something_id
end
What I want to do is call something like parent.somethings and get whole list of them.
For now all I get is stack level too deep error.
Without last line (has_many :somethings, through: :children, foreign_key: :something_id) I can only get child.somethings and it works perfectly.
So basically I want to get list of somethings, that includes somethings of every child of particular parent.
Thanks in advance!
P.S. I need exactly ActiveRecord::Associations::CollectionProxy, so creating method to collect somethings, I think is not possible.
P.P.S. Sorry, if my English is not so good :p
Actually I've found answer myself, after about an hour of research on Active Record Query Interface.
It's pretty simple, so I've done this:
class SomeClass < ActiveRecord::Base
belongs_to :parent, class_name: 'Class', foreign_key: :parent_id
has_many :children, class_name: 'Class', foreign_key: :parent_id
has_many :somethings
def all_somethings(order)
Something.joins(some_class: :parent).where("some_class_id = :id OR parents_some_classes.id = :id", {id: self.id}).uniq.order(order)
end
end
And I get exactly what I wanted!
I'd try splitting it into an instance method that gets all the children's somethings like this:
class Class < ActiveRecord::Base
belongs_to :parent, class_name: 'Class', foreign_key: :parent_id
has_many :children, class_name: 'Class', foreign_key: :parent_id
has_many :somethings, foreign_key: :something_id
def all_somethings
return unless self.children.empty?
(self.children + self.children.map(&:all_somethings)).uniq
end
end
I am writing an updated answer of Jonathan to avoid multiple call to :all_somethings and making query to get all the results. I hope this will also give you the results.
class Class < ActiveRecord::Base
belongs_to :parent, class_name: 'Class', foreign_key: :parent_id
has_many :children, class_name: 'Class', foreign_key: :parent_id
has_many :somethings, foreign_key: :something_id
has_many :childrens_somethings, through: :children, foreign_key: :something_id,
class_name: "Something"
def all_somethings
return unless self.children.empty?
(self.children + self.children_somethings).uniq
end
end

Resources