我有以下关联:
Order has_many Row
Row belongs_to RowStatus
我希望Order能够知道所有关联的行何时具有特定状态。所以,在Row模型中,我有以下回调:
before_save bubble_up_status
def bubble_up_status
if self.row_status_changed?
self.order.n_reviewed_rows += 1 if self.row_status.name == 'reviewed'
self.order.n_failed_rows += 1 if RowStatus::failed_names.include?(self.row_status.name)
self.order.n_pending_review_rows +=1 if self.row_status.name == 'pending_review'
self.order.check_all_statuses
end
end
然后在Order模型中我有
def check_all_statuses
if n_reviewed_rows == rows_count
update(order_status: OrderStatus.find_by(name: 'reviewed'))
end
if n_failed_rows == rows_count
update(order_status: OrderStatus.find_by(name: 'failed'))
end
if n_pending_review_rows == rows_count
update(order_status: OrderStatus.find_by(name: 'pending_review'))
end
end
我发现这有问题,因为我在before_save上不断更新父模型(Order)中的属性,所以我可能有误报。
我可以使用哪种模式来解决这个问题?
答案 0 :(得分:0)
只查看rowstatus不等于所需rowstatus的所有订单。如果有任何行没有该行状态,则您知道所有行都没有该行状态。
ideal_status = RowStatus.find_by(name: 'Completed')
order.rows.where.not(row_status_id: ideal_status.id)
如果您需要查看所有行是否具有几种状态之一,只需传递所需状态ID的数组。
ideal_status_ids = RowStatus.where(name: ['Completed', 'Shipped', 'Received']).pluck(:id)
order.rows.where.not(row_status_id: ideal_status_ids)
因此,对于您的情况,如果您想要在每行“已完成”时执行某些操作,则使用
if order.rows.where.not(row_status_id: RowStatus.find_by(name: 'Completed').id).empty?
# do something
end
至于在何处使用它,我认为after_save
回调最有意义,因为如果您对订单进行了更改但由于某种原因导致行保存失败会发生什么?此外,如果当前行的rowstatus是所需的rowstatus之一,您可能只想在bubble_up_status
中进行任何顺序更改,因此每次保存行时都不会进入数据库检查所有行。
class Row < ActiveRecord::Base
after_save :bubble_up_status
def bubble_up_status
ideal_status_ids = RowStatus.where(name: ['Completed', 'Shipped', 'Received']).pluck(:id)
if ideal_status_ids.include?(row_status.id) && order.rows.where.not(row_status_id: ideal_status_ids).empty?
# do something
end
end
end
替代解决方案
如果订单的行数非常多,那么每次订单行中的一个行更改状态时,到达数据库以检查与订单相关的每个行的行状态可能不是最有效的方式。因此,我建议为rows_count
使用计数器缓存,然后添加另一列,计算具有所需行状态的行数。
为了举例,我假设您对状态已标记为已完成的行数感兴趣。首先,我们会将rows_count
和completed_rows_count
列添加到orders
表中。这将是一个迁移文件。
class AddCountsToOrders< ActiveRecord::Migration
def change
add_column :orders, :rows_count, :integer
add_column :orders, :completed_rows_count, :integer
end
end
运行rake db:migrate
后,我们需要调整Order
模型,以便计数器缓存行数,并计算已完成的行。此外,我们还需要更新我们的bubble_up_status
回调以使用新列。
class Order < ActiveRecord::Base
has_many :rows, counter_cache: true
end
class Row < ActiveRecord::Base
# here I'm using after_update because I'm assuming that
# new rows can't be completed when first created
after_update :update_completed
# Needs to be after update_completed
after_save :bubble_up_status
def update_completed
if persisted?
if row_status.name == 'Complete' && row_status.name_was != 'Complete'
order.increment!(:completed_rows_count)
elsif row_status.name != 'Complete' && row_status.name_was == 'Complete'
order.decrement!(:completed_rows_count)
end
end
end
def bubble_up_status
# Check if all order rows are completed
if order.rows_count == order.completed_rows_count
# do something
end
end
end
附加说明
绝对有可能用一列而不是两列来实现这个逻辑,但我认为使用两列将更容易实现并且更容易理解。