Product has many product attributes:
class Product < ApplicationRecord
has_many :product_attributes
end
class ProductAttribute < ApplicationRecord
belongs_to :product
end
I can sort it with sort_by
:
@products.includes(:product_attributes).to_a.sort_by do |product|
product.product_attributes.find_by(title: "Volume").value.to_i
end
Is it possible to make the same sort with order
method?
I don’t understand how to order by particular attribute title (like "Volume", etc).
@products = Product.includes(:product_attributes).order( ??? )
Here is similar question:
Rails - Sort by join table data
Maybe I don't see the obvious, but I think it doesn't answer my question. I select item not by attribute name, but by attribute value, like "Volume".
In other words, I find_by
by attribute's value with title "Volume" (look at the code above).
And I don't understand how to make such selection with order
.
CodePudding user response:
Try with following
class Product < ApplicationRecord
has_many :product_attributes
end
class ProductAttribute < ApplicationRecord
belongs_to :product
end
Product.includes(:product_attributes)
.where(product_attributes: {title: "Volume" })
.order('product_attributes.title ASC')
Reference for Order by the associations two levels deep.
Rails order by association field
Reference for Rails, querying associated records with conditions
Rails, querying associated records with conditions
You can also use Scope With Argument like below
class Product < ApplicationRecord
has_many :product_attributes
scope :sort_by_product_attribute_title, ->(title){
includes(:product_attributes)
.where(product_attributes: {title: title })
.order('product_attributes.title ASC') }
end
class ProductAttribute < ApplicationRecord
belongs_to :product
end
Product.sort_by_product_attribute_title("Volume")
CodePudding user response:
There are a few different possible solutions - one them is to do a custom join with an additional restiction in the ON clause:
class Product < ApplicationRecord
has_many :product_attributes
def self.order_by_volume
joins(join_on_product_attribute('Volume'))
.order("volumes.value desc")
end
private
def self.join_on_product_attribute(title, join_alias = title.downcase.pluralize)
products, attrs = Product.arel_table, ProductAttribute.arel_table.alias(join_alias)
products.join(attrs, Arel::Nodes::OuterJoin)
.on(
products[:id].eq(attrs[:product_id])
.and(attrs[:title].eq(title))
)
.join_sources.first
end
end
irb(main):084:0> Product.order_by_volume
Product Load (0.5ms) SELECT "products".* FROM "products" LEFT OUTER JOIN "product_attributes" "volumes" ON "products"."id" = "volumes"."product_id" AND "volumes"."title" = 'Volume' ORDER BY volumes.value desc
Other possible solutions are a subquery or a lateral join to select the rows off the product_attributes table that just correspond to the volume attribute.