has_many through children of self - ruby-on-rails

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

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

Has_one relation with a specific foreign_key

In my Task model, I would like to create two has_one relation with the pricer model. To do that, I added the value :client_pricer_id and :presta_pricer_id to my Task table. And now, I want to create an has_one for each of them
My code :
Migration file :
class AddPricerToTasks < ActiveRecord::Migration[6.0]
def change
add_column :tasks, :client_pricer_id, :integer, foreign_key: true
add_column :tasks, :presta_pricer_id, :integer, foreign_key: true
end
end
Task model
has_one :client_pricer, :class_name => 'Pricer', :foreign_key => 'client_pricer_id'
has_one :presta_pricer, :class_name => 'Pricer', :foreign_key => 'presta_pricer_id'
View :
#task.client_pricer
Error :
SQLException: no such column: pricers.presta_pricer_id
I certainly forgot to specify a variable in my has_one line. But I don't know which one :)
You want to use belongs_to not has_one.
class Task < ApplicationRecord
belongs_to :client_pricer, class_name: 'Pricer', inverse_of: :client_pricer_tasks
belongs_to :presta_pricer, class_name: 'Pricer', inverse_of: :presta_pricer_tasks
end
class Pricer < ApplicationRecord
has_many :client_pricer_tasks, class_name: 'Task', foreign_key: :client_pricer_id
has_many :presta_pricer_tasks, class_name: 'Task', foreign_key: :presta_pricer_id
end
This is a really common mixup due to the confusing semantics.
belongs_to places the foreign key on this models table. has_one places it on the other end.
The inverse of has_one/has_many is always a belongs_to. Otherwise the relations just point at each other in a circle.
I finally found my error have to change the has_one to belongs_to (because the key is into my Task model )
My code :
Task model :
belongs_to :client_pricer, :class_name => 'Pricer', optional: true
belongs_to :presta_pricer, :class_name => 'Pricer', optional: true

Rails. Association of the same model with many parents

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

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

Foreign Key class_name

So, this is my first time using foreign keys, and though I think they are working properly, I don't understand the class_name portion in the syntax. Is that the class_name that the table being referred to is in?
My code:
Game Model:
belongs_to :user, foreign_key: 'white_player_id', class_name: 'User'
belongs_to :user, foreign_key: 'black_player_id', class_name: 'User'
User Model:
has_many :games, foreign_key: 'white_player_id', class_name: 'Game'
has_many :games, foreign_key: 'black_player_id', class_name: 'Game'
I was looking at: http://ricostacruz.com/cheatsheets/rails-models.html and noitced that in their example they have the class name of both belongs_to and has_many pointing to the Folder class..
belongs_to :parent, :foreign_key => 'parent_id' class_name: 'Folder'
has_many :folders, :foreign_key => 'parent_id', class_name: 'Folder'
So, that leads me to believe that the class name is supposed to point to the class that contains the foreign_key?? A little insight would be much appreciated.
If you define multiple assocations with the same name your just overwriting the same assocation.
belongs_to :white_player, foreign_key: 'white_player_id', class_name: 'User'
belongs_to :black_player, foreign_key: 'black_player_id', class_name: 'User'
class_name is the class of the related object.
foreign_key refers to the table of the model class where you are defining the relationship when defining a belongs_to relationsship.
class Game < ActiveRecord::Base
belongs_to :white_player, foreign_key: 'white_player_id', class_name: 'User'
# foreign_key is game.white_player_id
end
So when we do game.white_player Active Record looks for:
User.find(game.white_player_id)
added:
In your second example foreign_key in has_many refers to the related table.
belongs_to :parent, :foreign_key => 'parent_id' class_name: 'Folder'
has_many :folders, :foreign_key => 'parent_id', class_name: 'Folder'
And you would not need to specify the foreign key and class name explicitly:
class Folder < ActiveRecord::Base
# ActiveRecord will infer that the class name is Folder
has_many :folders, foreign_key: 'parent_id'
# Rails will infer that the foreign_key is parent_id
belongs_to :parent, class_name: 'Folder'
end
As you can see ActiveRecord is one smart cookie and can infer class names and foreign keys.
Here is an easier to explain example of has_many and foreign_keys:
class User < ActiveRecord::Base
has_many :unread_messages, -> { where read: false },
foreign_key: 'recipient_id', # refers to messages.recipient_id
class_name: 'Message'
end
user.unread_messages will query the table message:
SELECT "messages".* FROM "messages" WHERE "messages"."recipient_id" # ...
class_name is for the class which is used in method you use, if ActiveRecord can not decide which class it is. In your example you dont need class_name because your method is user and class that will connect to is User, ActiveRecord can figure that out on its own.
You have another problem. You have two relations with same name user and user, that is not posible.
You could made it like this though:
Game Model:
belongs_to :white_player, foreign_key: 'white_player_id', class_name: 'User'
belongs_to :black_player, foreign_key: 'black_player_id', class_name: 'User'
UserModel:
has_many :white_games, class_name: 'Game'
has_many :black_games, class_name: 'Game'
You might want to create it with a new model, the simpliest way to do it :
Migration - 1/2 : console
rails g model Game white_player:references black_player:references
Migration - 2/2 : db/migrate/create_games.rb
In the migration file, delete the "foreign_key: true" entry, that would look like :
t.references :white_player, foreign_key: true
t.references :black_player, foreign_key: true
Run : rails db:migrate
Model files :
Game Model :
belongs_to :white_player, class_name: 'User'
belongs_to :black_player, class_name: 'User'
User Model :
has_many :white_player_games, class_name: 'Game', foreign_key: 'white_player_id'
has_many :black_player_games, class_name: 'Game', foreign_key: 'white_player_id'
Hope that helps.

Resources