Reputation: 3556
I have a Sub-Component model which can belong to other sub-components. My Model looks like this:
class SubComponent < ApplicationRecord
belongs_to :parent, class_name: "SubComponent", foreign_key: "parent_sub_component_id", optional: true
has_many :child_sub_components, class_name: "SubComponent", foreign_key: "parent_sub_component_id"
validates_presence_of :name
end
This model is fairly simple, it has a name
field and a parent_sub_component_id
which as the name suggests is an id
of another SubComponent
.
I'd like to generate a query that returns all of the SubComponents
(with their id
, name
, and parent_sub_component_id
) but also includes the actual name of it's parent_sub_component.
This seems like it should be pretty simple but for the life of me I can't figure out how to do it. I'd like for this query to be done in the database rather than doing an each loop in Ruby or something like that.
EDIT:
I'd like for the output to look something like this:
#<ActiveRecord::Relation [#<SubComponent id: 1, name: "Parent Sub", parent_sub_component_id: nil, parent_sub_component_name: nil created_at: "2017-07-07 00:29:37", updated_at: "2017-07-07 00:29:37">, #<SubComponent id: 2, name: "Child Sub", parent_sub_component_id: 1, parent_sub_component_name: "Parent Sub" created_at: "2017-07-07 00:29:37", updated_at: "2017-07-07 00:29:37">]>
Upvotes: 1
Views: 471
Reputation: 11035
This is untested, but should get you started in the right direction, you can do this in Arel by doing something like
def self.execute_query
parent_table = Arel::Table.new(:sub_component).alias
child_table = Arel::Table.new(:sub_component)
child_table.join(parent_table, Arel::Nodes::OuterJoin).on(child_table[:parent_sub_component_id].eq(parent_table[:id]).project(child_table[:id], child_table[:name], parent_table[:id], parent_table[:name])
end
This results in a query like
SELECT "sub_component"."id", "sub_component"."name", "sub_component_2"."id", "sub_component_2"."name" FROM "sub_component" LEFT OUTER JOIN "sub_component" "sub_component_2" ON "sub_component"."parent_sub_component_id" = "sub_component_2"."id"
this is just off the top of my head by looking at Rails/Arel and probably needs a some work, but the query looks about what I would expect and this should get you going.
Upvotes: 0
Reputation: 5967
You can do this efficiently using an each
loop if you use includes
:
SubComponent.all.includes(:parent).each do |comp|
comp.parent.name # this gives you the name of the parent
end
What includes
does is it pre-fetches the specified association. That is, ActiveRecord will query all subcomponents, and then in a single query also pull down all the parents of those subcomponents. When you subsequently access comp.parent
in the loop, the associated parent will already be loaded, so this will not result in a so-called N+1 query.
The queries that AR will generate for you automatically will look something like this:
SELECT `subcomponents`.* FROM `subcomponents`
SELECT `subcomponents`.* FROM `subcomponents` WHERE `subcomponents`.`id` IN (1, 3, 9, 14)
If you need to use the name of the parent in a where
condition, includes
will not work and you will have to use joins
instead to actually generate an SQL JOIN
.
Upvotes: 1