如何知道所有has_many关联何时具有特定属性?

时间:2014-06-25 21:09:44

标签: ruby-on-rails associations

我有以下关联:

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)中的属性,所以我可能有误报。

我可以使用哪种模式来解决这个问题?

1 个答案:

答案 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_countcompleted_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

附加说明

绝对有可能用一列而不是两列来实现这个逻辑,但我认为使用两列将更容易实现并且更容易理解。