需要帮助重构以使用 .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。
我有以下代码是遗留代码,我需要将其重构为 .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。