需要帮助重构以使用 .where 子句 - ruby on rails

Need help refactoring to use .where clause - ruby on rails

我有以下代码是遗留代码,我需要将其重构为 .where 子句,但我在重构它时遇到了问题,并且找到了最好的方法。

这是代码

# legacy
@debit_transactions = FinancialTransaction.legacy_find(
        :all,
        :include => [:project, :department, :stock, :debit_account, :credit_account, :transaction_type],
        :conditions => ["dr_account_id = ?", @customer.id],
        :order => 'financial_transactions.id desc',
        :limit => 20)

# refactor attempt
@debit_transactions = FinancialTransaction.where(dr_account_id: @dr_account_id, customer: @customer.id).order('financial_transactions.id desc').limit(20)

# legacy
contact_or = ''
contact_or = ' OR contact_id IN(?) ' if  @customer.contacts.present?

@customer_complaints = Event.legacy_find(:all, { :order => 'id desc', :limit => 10, :conditions => ["complaint is true and (customer_account_id = ? #{contact_or} )", @customer.id].push_if(@customer.contacts.to_a.map(&:id), @customer.contacts.present?) })

# refactor attempt
@customer_complaints = Event.where(complaints: true, customer_account_id: [@customer_account_id], customer: @customer.id).order('id desc').limit(10)

如有任何帮助,我们将不胜感激

编辑一些可能有助于理解 legacy_find

的方法
def self.legacy_find(type, args=nil)
  # need to add capability to handle array of id's
  if type.kind_in?([Numeric, String, Array]) && !args
    result =  self.find(type)
  elsif !args
    result =  self.send(type.to_s)
  else
    result = self

    if type.kind_of?(Array)
      if !args[:conditions]
         args[:conditions] = ["1 = 1"]
      elsif args[:conditions].kind_of?(String)
        args[:conditions] = [args[:conditions]]
      end

      args[:conditions][0] = '(' + args[:conditions][0] + ')'

      args[:conditions][0] += " AND `" + self.table_name + "`.`id` in(" + type.join(',') + ")"
    elsif type && !type.kind_of?(Symbol)
      if !args[:conditions]
         args[:conditions] = ["1 = 1"]
      elsif args[:conditions].kind_of?(String)
        args[:conditions] = [args[:conditions]]
      end

      args[:conditions][0] = '(' + args[:conditions][0] + ')'

      args[:conditions][0] += " AND `" + self.table_name + "`.`id` = ?"
      args[:conditions].push(type)
    end

    result      = self.legacy_conditions(args)

    if type && ((type.kind_of?(String) && type.to_i.to_s == type) || type.kind_of?(Numeric))
      result = result.first
    elsif type && !type.kind_of?(Symbol)
      result = result.to_a
    else
      result =  type ? result.send((type == :all ? 'to_a' : type).to_s) : result
    end
  end

  new_result = result

  return new_result
end

def self.legacy_count(args=nil)
  new_result        = self.legacy_conditions(args).count
end

def self.legacy_sum(col, args=nil)
  new_result        = self.legacy_conditions(args).sum(col.to_s)
end

def self.legacy_conditions(args)
  return self if !args

  args[:conditions] = [] if args[:conditions] && args[:conditions][0].kind_of?(String) && args[:conditions][0].size == 0

  result = self
  result = result.where(args[:conditions]) if (args.has_key?(:conditions) && args[:conditions] && args[:conditions].size > 0)
  result = result.select(args[:select]) if args.has_key?(:select) && args[:select]
  result = result.includes(args[:include]) if args.has_key?(:include) && args[:include]
  result = result.includes(args[:include_without_references]) if args.has_key?(:include_without_references) && args[:include_without_references]
  result = result.references(args[:include]) if args.has_key?(:include) && args[:include]
  result = result.joins(args[:joins]) if args.has_key?(:joins) && args[:joins]
  result = result.order(args[:order]) if args.has_key?(:order) && args[:order]
  result = result.group(args[:group]) if args.has_key?(:group) && args[:group]
  result = result.limit(args[:limit]) if args.has_key?(:limit) && args[:limit]
  result = result.offset(args[:offset]) if args.has_key?(:offset) && args[:offset]
  result = result.from(args[:from]) if args.has_key?(:from) && args[:from]
  result = result.lock(args[:lock]) if args.has_key?(:lock) && args[:lock]
  result = result.readonly(args[:readonly]) if args.has_key?(:readonly) && args[:readonly]
  result
end

老实说,这个代码存在的原因超出了我的理解范围,所以我正在尝试逐步淘汰它。

编辑 2 根据下面的答案,我得出以下结论

@debit_transactions = FinancialTransaction
  .includes(:project, :department, :stock, :debit_account, :credit_account, :transaction_type)
  .where(dr_account_id: @dr_account_id)
  .order(id: :desc)
  .limit(20)

@credit_transactions = FinancialTransaction
  .includes(:project, :department, :stock, :debit_account, :credit_account, :transaction_type)
  .where(cr_account_id: @cr_account_id)
  .order(id: :desc)
  .limit(20)

contact_or = ''
contact_or = ' OR contact_id IN(?) ' if  @customer.contacts.size > 0

@customer_complaints = Event.where(customer_account_id: @customer_id, complaint: true).order(id: :desc).limit(10).or(Event.where(contact_id: @customer.contacts)) if @customer.contacts.present?
@customer_leads = Event.where(customer_account_id: @customer_id, lead: true).order(id: :desc).limit(10)
@customer_quotes = SalesQuote.where(customer_account_id: @customer_id).or(SalesQuote.where(contact_id: @contact_id)).order(id: :desc).limit(10)
@customer_orders = SalesOrder.where(customer_account_id: @customer_id).order(id: :desc).limit(10)
@customer_invoices = Invoice.where(customer_account_id: @customer_id).order(id: :desc).limit(10)
@customer_credits = CreditNote.where(customer_account_id: @customer_id).order(id: :desc).limit(10)
@customer_opportunities = Opportunity.where(customer_account_id: @customer_id).or(Opportunity.where(contact_id: @contact_id)).order(id: :desc).limit(10)
@customer_estimates = Estimate.where(customer_account_id: @customer_id).or(Estimate.where(contact_id: @contact_id)).order(id: :desc).limit(10)
@customer_support_tickets = SupportTicket.where(customer_account_id: @customer_id).order(id: :desc).limit(10)
@financial_matching_sets = FinancialMatchingSet.where(customer_account_id: @customer_id).order(id: :desc).limit(10)

但是,我得到以下信息

Mysql2::Error: Unknown column 'sales_orders.customer_account_id' in 'where clause': SELECT  `sales_orders`.* FROM `sales_orders` WHERE `sales_orders`.`customer_account_id` IS NULL ORDER BY `sales_orders`.`id` DESC LIMIT 10

第一个查询相对简单:

@debit_transactions = FinancialTransaction
 # you missed the includes
 .includes(
    :project, :department, :stock, :debit_account, 
    :credit_account, :transaction_type
  )
  .where(
    dr_account_id: @dr_account_id.id,
  )
  .order(id: :desc)
  .limit(20)

然后第二个查询有点难。

您只需传递一个数组即可创建 WHERE x IN (...) 子句:

@debit_transactions = FinancialTransaction.where(
  id: [1,2,3]
)

您还可以通过传递 ActiveRecord::Relation:

创建一个 WHERE x IN (subquery)
Event.where(contact_id: @customer.contacts)

使用 .map(:id).ids 更有效,因为您删除了到数据库的完整往返行程。

在 Rails 5 中添加了对 OR 的支持:

scope = Event.where(customer_account_id: @customer_id)
scope = scope.or(Event.where(contact_id: @customer.contacts)) if @customer.contacts.present?

所以它看起来像:

scope = Event.where(
  customer_account_id: @customer_id
  complaint: true
).order('id desc')
.limit(10)
scope = scope.or(Event.where(contact_id: @customer.contacts)) if @customer.contacts.present?

the best way to do it.

对此我没有具体的答案,但您可以尝试一些方法,例如:

  • 为旧实现的行为编写测试用例,从而确保它们仍然行为与新实现相同。 (也许您已经进行了一些这样的测试??!!)
  • 通过检查 query.to_sql 是否保持不变,为旧代码与新代码的 实现 编写测试用例?!
  • 尝试运行 在生产环境中同时使用这两个版本,假设您有良好的错误记录。例如,您能否逐渐将 10% 的用户切换为使用“新”实现,从而捕获任何错误而不会导致所有人出现大规模故障?

但无论如何......除了以[=​​60=]方式重写所有这些的痛苦之外:


第一次查询:

@debit_transactions = FinancialTransaction.legacy_find(
        :all,
        :include => [:project, :department, :stock, :debit_account, :credit_account, :transaction_type],
        :conditions => ["dr_account_id = ?", @customer.id],
        :order => 'financial_transactions.id desc',
        :limit => 20)

# refactor attempt
@debit_transactions = FinancialTransaction
  .where(dr_account_id: @dr_account_id, customer: @customer.id)
  .order('financial_transactions.id desc')
  .limit(20)

此重构忽略 include 参数。遗留方法说:

# ...
result = result.includes(args[:include]) if args.has_key?(:include) && args[:include]
result = result.references(args[:include]) if args.has_key?(:include) && args[:include]
# ...

所以,您的版本应该是:

@debit_transactions = FinancialTransaction
  .includes([:project, :department, :stock, :debit_account, :credit_account, :transaction_type])
  .references([:project, :department, :stock, :debit_account, :credit_account, :transaction_type])
  .where(dr_account_id: @dr_account_id, customer: @customer.id)
  .order('financial_transactions.id desc')
  .limit(20)

第二次查询:

# legacy
contact_or = ''
contact_or = ' OR contact_id IN(?) ' if  @customer.contacts.present?

@customer_complaints = Event.legacy_find(
  :all,
  :order => 'id desc',
  :limit => 10,
  :conditions => ["complaint is true and (customer_account_id = ? #{contact_or} )", @customer.id].push_if(@customer.contacts.to_a.map(&:id), @customer.contacts.present?)
)

# refactor attempt
@customer_complaints = Event
  .where(complaints: true, customer_account_id: [@customer_account_id], customer: @customer.id)
  .order('id desc')
  .limit(10)

您的重构忽略了条件中的 OR 子句;你把它写成了 3 AND 个子句。

认为可以这样写:

Event.where(customer_account_id: [@customer_account_id])
  .or(Event.where(customer: @customer.id))
  .merge(Event.where(complaints: true))
  .order('id desc')
  .limit(10)

...或者类似的东西。检查两种情况下生成的SQL。