英文:
building a class method combining two classes with same parent class
问题
以下是翻译好的部分:
"Two classes are related via a same parent" 双方通过相同的父类相关联
class Promoitem
belongs_to :shop
belongs_to :article, optional: true
class Cartitem
belongs_to :article
belongs_to :cart
class Cart
belongs_to :shop
class Article
has_many :cartitems
has_many :promoitems
"An existing controller method executes logic as follows:" 现有的控制器方法执行如下逻辑:
valid_promo = Promoitem.where('shop_id = ? AND date_start <= ? AND date_end >= ? AND article_id = ?', @shop.id, Date.today, Date.today, cartitem.article_id ).first
if valid_promo
api_price = valid_promo.price_promo
else
api_price = cartitem.article.sell_price
end
"but that makes the controller fat and this logic could reside in the cartitem
model definition." 但这使得控制器变得臃肿,这个逻辑可以存在于cartitem
模型定义中。
"the goal is to write efficient model methods." 目标是编写高效的模型方法。
class Cartitem
# scope is bad
scope :price_promo, lambda { joins(:promoitems).where("date_start <= ? AND date_end >= ? AND article_id = ?", Date.today, Date.today, self.article_id) }
def valid_promo
self.promoitems.price_promo
end
def api_price
if self.valid_promo
self.promoitems.price_promo
else
self.article.sell_price
end
"The scope definition is wrong, as the relationship is via the article class." 作用域定义是错误的,因为关系是通过文章类的。
"Thus each class could have" 因此每个类都可以有
has_many :promoitems, through: :articles
has_many :cartitems, through: :articles
"But how does that translate into a proper scope that is effective at querying as the controller method - or is there a better way to do this?" 但如何将其转化为有效的查询作用域,类似于控制器方法 - 或者是否有更好的方法来实现这一目标?
英文:
Two classes are related via a same parent
class Promoitem
belongs_to :shop
belongs_to :article, optional: true
class Cartitem
belongs_to :article
belongs_to :cart
class Cart
belongs_to :shop
class Article
has_many :cartitems
has_many :promoitems
An existing controller method executes logic as follows:
valid_promo = Promoitem.where('shop_id = ? AND date_start <= ? AND date_end >= ? AND article_id = ?', @shop.id, Date.today, Date.today, cartitem.article_id ).first
if valid_promo
api_price = valid_promo.price_promo
else
api_price = cartitem.article.sell_price
end
but that makes the controller fat and this logic could reside in the cartitem
model definition.
the goal is to write efficient model methods.
class Cartitem
# scope is bad
scope :price_promo, lambda { joins(:promoitems).where("date_start <= ? AND date_end >= ? AND article_id = ?", Date.today, Date.today, self.article_id) }
def valid_promo
self.promoitems.price_promo
end
def api_price
if self.valid_promo
self.promoitems.price_promo
else
self.article.sell_price
end
end
The scope definition is wrong, as the relationship is via the article class. Thus each class could have
has_many :promoitems, through: :articles
has_many :cartitems, through: :articles
But how does that translate into a proper scope that is effective at querying as the controller method - or is there a better way to do this?
答案1
得分: 1
你的关系非常尴尬,需要进行一些重组,因为这个陈述“...因为关系是通过文章类建立的”在技术上并不正确。实际上,这个关系是通过“article”和“cart => shop”建立的,使得直接查询非常尴尬。
话虽如此,在你目前的情况下,你应该能够使用以下内容:
class Cartitem < ApplicationRecord
scope :with_promo_pricing, -> {
promo_table = PromoItem.arel_table
promo_join = Arel::Nodes::OuterJoin.new(promo_table,
promo_table
.create_on(
promo_table[:article_id].eq(arel_table[:article_id])
.and(promo_table[:shop_id].eq(Cart.arel_table[:shop_id])
))
joins(:cart, :article)
.joins(promo_join)
.select(arel_table[Arel.star],
Arel::Nodes::NamedFunction.new('ISNULL',
[promo_table[:price_promo],
Article.arel_table[:sell_price]]
).as('final_price')
)
.where(promo_items: {start_date: ..Date.today, end_date: Date.today..})
}
def api_price
attributes['final_price'] || self.class.where(id: self.id).with_promo_pricing.final_price
end
end
这应该会产生以下SQL:
SELECT
cartitems.*,
ISNULL(promoitems.price_promo,articles.sell_price) AS final_price
FROM
cartitems
INNER JOIN carts ON carts.id = cartitems.cart_id
INNER JOIN articles ON articles.id = cartitems.article_id
LEFT OUTER JOIN promoitems ON promoitems.article_id = cartitems.article_id
AND carts.shop_id = promoitems.shop_id
WHERE
promoitems.start_date <= '2023-03-07' AND
promoitems.end_date >= '2023-03-07'
你也可以在一个Cart
上使用这个,我假设这应该是你的意图,例如:
@cart.cartitems.with_promo_pricing
希望这有所帮助。
英文:
Your relationship is very awkward and would greatly benefit from some restructuring because this statement "...as the relationship is via the article class." is not technically true. The relationship is actually via the "article" and the "cart => shop" making it very awkward to query directly.
That being said under your current circumstances you should be able to use the following:
class Cartitem < ApplicationRecord
scope :with_promo_pricing, -> {
promo_table = PromoItem.arel_table
promo_join = Arel::Nodes::OuterJoin.new(promo_table,
promo_table
.create_on(
promo_table[:article_id].eq(arel_table[:article_id])
.and(promo_table[:shop_id].eq(Cart.arel_table[:shop_id])
))
joins(:cart, :article)
.joins(promo_join)
.select(arel_table[Arel.star],
Arel::Nodes::NamedFunction.new('ISNULL',
[promo_table[:price_promo],
Article.arel_table[:sell_price]]
).as('final_price')
)
.where(promo_items: {start_date: ..Date.today, end_date: Date.today..})
}
def api_price
attributes['final_price'] || self.class.where(id: self.id).with_promo_pricing.final_price
end
end
This should produce the following SQL
SELECT
cartitems.*,
ISNULL(promoitems.price_promo,articles.sell_price) AS final_price
FROM
cartitems
INNER JOIN carts ON carts.id = cartitems.cart_id
INNER JOIN articles ON articles.id = cartitems.article_id
LEFT OUTER JOIN promoitems ON promoitems.article_id = cartitems.article_id
AND carts.shop_id = promoitems.shop_id
WHERE
promoitems.start_date <= '2023-03-07' AND
promoitems.end_date >= '2023-03-07'
You could also use this with a Cart
which I assume has to be the intent e.g.
@cart.cartitems.with_promo_pricing
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论