简体   繁体   中英

Rails 3.2 Simplify Controller with Model Methods on has_many :through

Most questions I've seen with skinnying up controllers deals with a simple model relationships. My question is if you're updating a many to many form with multiple parameters and their associated arrays, how do you simplify the task by moving it all to a model method? For example take a look at the ridiculously large controller below. What's the simplest way of dealing with this unholy mess? I'm not looking for a syntactically perfect answer but need a general consensus on a direction to go with this.

def update
  @shipment = Shipment.joins(:products).find(params[:id], :readonly => false)
  @shipment.update_attributes(params[:shipment])
  @shipment_products = params[:product_shipments]
  @product_shipment_array= array_from_hash(@shipment_products)


  @shipment.product_shipments.each do |product_shipment|
    product_shipment.update_attributes(:qty_shipped => params[:product_shipments][product_shipment.id.to_s][:qty_shipped], :pickup_item => params[:product_shipments][product_shipment.id.to_s][:pickup_item])
  end
  @product_shipment_array.each do |p|
    if  p['old_pickup_item'] == "true" and p['pickup_item'].to_i==0
      @difference = (p['qty_shipped'].to_i)
      Product.mark_open_shipment(@difference, p['product_id'])

    elsif p['old_pickup_item'] == "false" and p['pickup_item'].to_i==1
      @difference = -(p['old_qty_shipped'].to_i)
      Product.mark_open_shipment(@difference, p['product_id'])
    else
      @difference = -(p['old_qty_shipped'].to_i - p['qty_shipped'].to_i)
      Product.mark_open_shipment(@difference, p['product_id'])
    end

  end

  respond_with @shipment, :location => shipments_url
end

and in my model I want to declare a model method something like this

Class Shipment < ActiveRecord::Base
  .
  .
  .      
  def update_shipment_attributes
    #all business logic
  end

end

In the hopes of getting my controller down to something like this or something similar:

def update
  @shipment = Shipment.joins(:products).find(params[:id], :readonly => false)
  @shipment.update_attributes(params[:shipment])
  @shipment_products = params[:product_shipments]

  Shipment.update_shipment_attributes

  respond_with @shipment, :location => shipments_url     
end

Here are a few approaches you can take to achieve what you want:

  1. Look into using nested forms and accepts_nested_attributes_for . With nested attributes, your controller edit method will only need to share one @shipment instance variable with a nested collection of shipment_products.
  2. Consider having your update action only call update_attributes . When you're using nested attributes properly, this should be the the only model call you need because it will implicitly populate the attributes for its shipment_products .
  3. In your shipment_products model, use an after_save callback to mark the open shipments. But beware, your shipment_products model should not use the word Product (capitalized). Instead, it should rely on its belongs_to relation to call product.mark_open_shipment(difference) . difference should be refactored into an instance method on product_shipments. The callbacks and update_attributes will all run in one transaction, which will ensure atomicity.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM