rails 4个简单表单嵌套属性更新时多个模型出错
rails 4 simple form nested attributes multiple models error during update
我正在与嵌套属性错误作斗争,同时尝试修复 cop 错误。所以这是演练。优惠券代码可以使用可能影响工作价格的嵌套属性与表单一起提交。仅当优惠券代码有效时才会发生这种情况。在这种情况下,优惠券代码已经分配,因此第一个 if coupon_code && coupon.nil?
被触发。当表单返回时,flash 消息可以正常工作,但简单表单不显示值。我可以调整简单的形式来获得一个实例变量的值,但我开始觉得我的逻辑有点不对劲。另外,Assignment Branch Condition
的味道开始让我担心了。我可以继续这样做,但用户希望看到代码。我也会。
Cop 错误:
app/controllers/payments_controller.rb:9:3: C: Assignment Branch Condition size for update is too high. [17.97/15]
控制器:
class PaymentsController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :route_not_found_error
Numeric.include CoreExtensions::Numeric::Percentage
def update
@job = Job.find(params[:job_id])
coupon_code = params[:job][:coupon_attributes][:code]
coupon = validate_coupon(coupon_code)
if coupon_code && coupon.nil?
@coupon_code = coupon_code
flash.now[:error] = t('flash_messages.coupons.id.not_found')
render 'payments/new', layout: 'nested/job/payment'
else
update_job(@job, coupon)
update_coupon(coupon, @job) if coupon
redirect_to @job.vanity_url
end
end
def new
@job = Job.find(params[:job_id])
return if reroute?(@job)
render 'payments/new', layout: 'nested/job/payment'
end
private
def update_job(job, coupon)
job.start_at = DateTime.now
job.end_at = AppConfig.product['settings']['job_active_for_day_num'].days.from_now
job.paid_at = DateTime.now
job.price = price_job(coupon)
# job.save
end
def validate_coupon(coupon_code)
return nil unless coupon_code.present?
coupon = Coupon.active.find_by_code(coupon_code)
return nil unless coupon.present?
coupon
end
def price_job(coupon)
price = AppConfig.product['settings']['job_base_price']
return price unless coupon
price = coupon.percent_discount.percent_of(price)
price
end
def update_coupon(coupon, job)
coupon.job_id = job.id
coupon.executed_at = DateTime.now
coupon.save
end
end
查看:
ruby:
content_for :body_id_class, 'PaymentNew'
content_for :js_instance, 'viewPaymentNew'
content_for :browser_title, 'Payment'
job_base_price = AppConfig.product['settings']['job_base_price']
coupon_code = @coupon_code ||= ''
= simple_form_for(@job, url: job_payment_path, html: { id: 'payment-processor-form' }) do |j|
div[class='row']
div[class='col-md-12']
div[class='panel panel-default']
div[class='panel-heading']
h3[class='panel-title']
|Total Cost
div[class='panel-body']
h2[class='job-cost' data-initial = "#{job_base_price}"]
= number_to_currency(job_base_price)
div[class='panel-heading']
h3[class='panel-title']
|Have a coupon?
div[class='panel-body']
div[class='row-inline']
div[class='row-block row-block-one']
= j.simple_fields_for :coupon_attributes, @job.coupon do |c|
= c.input_field :code, maxlength: 50, id: 'coupon-code', class: 'form-control', data: { 'initial' => 0 }, value: coupon_code
div[class='row-block']
button[type='button' class='btn btn-primary' id='coupon-verify' ]
|Verify
p[class='help-hint']
= t('simple_form.hints.coupon.code')
div[class='row']
div[class='col-md-12']
= j.button :button, type: 'button', class: 'btn-primary text-uppercase', id: 'purchase-job' do
= job_posting_button_step_label
更新
- 重构此代码以与下面的 post 一起使用。工厂固定
你在那个又胖又旧的控制器中有不少代码味道。
他们中的大多数似乎是模型层上的一切都不好并且您没有很好地建模域的症状。
您可能想考虑这样的事情:
class Job < ActiveRecord::Base
has_many :payments
end
class Payment < ActiveRecord::Base
belongs_to :job
belongs_to :coupon
end
class Coupon < ActiveRecord::Base
validates_uniqueness_of :code
end
这将使我们的计数器专注于对单个资源进行 CRUD,而不是试图赶走一群猫。
那么让我们看看执行优惠券的业务逻辑。
class Payment < ActiveRecord::Base
belongs_to :job
belongs_to :coupon
validate :coupon_must_be_active
attr_writer :coupon_code
def coupon_code=(code)
coupon = Coupon.find_by(code: code)
@coupon_code = code
end
private
def coupon_must_be_active
if coupon
errors[:coupon] << "must be active." unless coupon.active?
elsif @coupon_code.present?
errors[:coupon_code] << "is not valid."
end
end
end
自定义属性编写器从代码加载优惠券。验证设置了我们的业务逻辑规则。
在工作定价方面,我们确实应该这样做:
class Job < ActiveRecord::Base
after_initialize :set_price
def set_price
self.price ||= AppConfig.product['settings']['job_base_price']
end
end
class Payment < ActiveRecord::Base
after_initialize :set_price
validates_presence_of :job
def net_price
return job.price unless coupon
job.price * (coupon.percent_discount * 00.1)
end
# ...
end
然后我们可以这样写我们的控制器:
class PaymentsController
before_action :set_job
# GET /jobs/:job_id/payments/new
def new
@payment = @job.payments.new
end
# POST /jobs/:job_id/payments
def create
@payment = @job.payments.create(payment_params)
end
# PATCH /jobs/:job_id/payments/:id
def update
@payment = @job.payments.find(params[:id])
end
private
def set_job
@job = Job.find(params[:job_id])
end
def payment_params
params.require(:payment)
.permit(:coupon_code)
end
end
然后我们可以简单地设置表单:
= simple_form_for([@job, @payment]) do |f|
= f.input :coupon_code
= f.submit
请注意,除非您打算实施 the honor system,否则您不想从用户那里获取价格 - 您应该通过设置关联回调从您的模型中获取它。
我正在与嵌套属性错误作斗争,同时尝试修复 cop 错误。所以这是演练。优惠券代码可以使用可能影响工作价格的嵌套属性与表单一起提交。仅当优惠券代码有效时才会发生这种情况。在这种情况下,优惠券代码已经分配,因此第一个 if coupon_code && coupon.nil?
被触发。当表单返回时,flash 消息可以正常工作,但简单表单不显示值。我可以调整简单的形式来获得一个实例变量的值,但我开始觉得我的逻辑有点不对劲。另外,Assignment Branch Condition
的味道开始让我担心了。我可以继续这样做,但用户希望看到代码。我也会。
Cop 错误:
app/controllers/payments_controller.rb:9:3: C: Assignment Branch Condition size for update is too high. [17.97/15]
控制器:
class PaymentsController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :route_not_found_error
Numeric.include CoreExtensions::Numeric::Percentage
def update
@job = Job.find(params[:job_id])
coupon_code = params[:job][:coupon_attributes][:code]
coupon = validate_coupon(coupon_code)
if coupon_code && coupon.nil?
@coupon_code = coupon_code
flash.now[:error] = t('flash_messages.coupons.id.not_found')
render 'payments/new', layout: 'nested/job/payment'
else
update_job(@job, coupon)
update_coupon(coupon, @job) if coupon
redirect_to @job.vanity_url
end
end
def new
@job = Job.find(params[:job_id])
return if reroute?(@job)
render 'payments/new', layout: 'nested/job/payment'
end
private
def update_job(job, coupon)
job.start_at = DateTime.now
job.end_at = AppConfig.product['settings']['job_active_for_day_num'].days.from_now
job.paid_at = DateTime.now
job.price = price_job(coupon)
# job.save
end
def validate_coupon(coupon_code)
return nil unless coupon_code.present?
coupon = Coupon.active.find_by_code(coupon_code)
return nil unless coupon.present?
coupon
end
def price_job(coupon)
price = AppConfig.product['settings']['job_base_price']
return price unless coupon
price = coupon.percent_discount.percent_of(price)
price
end
def update_coupon(coupon, job)
coupon.job_id = job.id
coupon.executed_at = DateTime.now
coupon.save
end
end
查看:
ruby:
content_for :body_id_class, 'PaymentNew'
content_for :js_instance, 'viewPaymentNew'
content_for :browser_title, 'Payment'
job_base_price = AppConfig.product['settings']['job_base_price']
coupon_code = @coupon_code ||= ''
= simple_form_for(@job, url: job_payment_path, html: { id: 'payment-processor-form' }) do |j|
div[class='row']
div[class='col-md-12']
div[class='panel panel-default']
div[class='panel-heading']
h3[class='panel-title']
|Total Cost
div[class='panel-body']
h2[class='job-cost' data-initial = "#{job_base_price}"]
= number_to_currency(job_base_price)
div[class='panel-heading']
h3[class='panel-title']
|Have a coupon?
div[class='panel-body']
div[class='row-inline']
div[class='row-block row-block-one']
= j.simple_fields_for :coupon_attributes, @job.coupon do |c|
= c.input_field :code, maxlength: 50, id: 'coupon-code', class: 'form-control', data: { 'initial' => 0 }, value: coupon_code
div[class='row-block']
button[type='button' class='btn btn-primary' id='coupon-verify' ]
|Verify
p[class='help-hint']
= t('simple_form.hints.coupon.code')
div[class='row']
div[class='col-md-12']
= j.button :button, type: 'button', class: 'btn-primary text-uppercase', id: 'purchase-job' do
= job_posting_button_step_label
更新
- 重构此代码以与下面的 post 一起使用。工厂固定
你在那个又胖又旧的控制器中有不少代码味道。 他们中的大多数似乎是模型层上的一切都不好并且您没有很好地建模域的症状。
您可能想考虑这样的事情:
class Job < ActiveRecord::Base
has_many :payments
end
class Payment < ActiveRecord::Base
belongs_to :job
belongs_to :coupon
end
class Coupon < ActiveRecord::Base
validates_uniqueness_of :code
end
这将使我们的计数器专注于对单个资源进行 CRUD,而不是试图赶走一群猫。
那么让我们看看执行优惠券的业务逻辑。
class Payment < ActiveRecord::Base
belongs_to :job
belongs_to :coupon
validate :coupon_must_be_active
attr_writer :coupon_code
def coupon_code=(code)
coupon = Coupon.find_by(code: code)
@coupon_code = code
end
private
def coupon_must_be_active
if coupon
errors[:coupon] << "must be active." unless coupon.active?
elsif @coupon_code.present?
errors[:coupon_code] << "is not valid."
end
end
end
自定义属性编写器从代码加载优惠券。验证设置了我们的业务逻辑规则。
在工作定价方面,我们确实应该这样做:
class Job < ActiveRecord::Base
after_initialize :set_price
def set_price
self.price ||= AppConfig.product['settings']['job_base_price']
end
end
class Payment < ActiveRecord::Base
after_initialize :set_price
validates_presence_of :job
def net_price
return job.price unless coupon
job.price * (coupon.percent_discount * 00.1)
end
# ...
end
然后我们可以这样写我们的控制器:
class PaymentsController
before_action :set_job
# GET /jobs/:job_id/payments/new
def new
@payment = @job.payments.new
end
# POST /jobs/:job_id/payments
def create
@payment = @job.payments.create(payment_params)
end
# PATCH /jobs/:job_id/payments/:id
def update
@payment = @job.payments.find(params[:id])
end
private
def set_job
@job = Job.find(params[:job_id])
end
def payment_params
params.require(:payment)
.permit(:coupon_code)
end
end
然后我们可以简单地设置表单:
= simple_form_for([@job, @payment]) do |f|
= f.input :coupon_code
= f.submit
请注意,除非您打算实施 the honor system,否则您不想从用户那里获取价格 - 您应该通过设置关联回调从您的模型中获取它。