How to update a model's attribute with a virtu

2020-03-29 01:31发布

问题:

I have a model named UserPrice which has the attribute :purchase_date(a date_select) in its table. With my form I can create multiple user_prices at once but for user convenience I made a virtual attribute inside of my UserPrice model called :all_dates that's also a date_select field and its job is to be the replacement of the :purchase_dates so users only have to select the :all_dates field for the date.


Problem & Question

The :all_dates field is not updating the :purchase_date fields of my user_prices that are being created. What do I need to do in order to get my :all_dates field to update the :purchase_date fields of my new UserPrices?

Does anyone have any tips on how to do this?


Parameters

Parameters: 
"user_price"=> { 
"all_dates(2i)"=>"10", 
"all_dates(3i)"=>"27", 
"all_dates(1i)"=>"2011"
}, 
"user_prices"=>
{
"0"=>{"product_name"=>"Item1", "store"=>"Apple Store","price"=>"6"}, 
"1"=>{"product_name"=>"Item2", "store"=>"Apple Store", "price"=>"7"}
}, 
"commit"=>"Submit"}

Code

  class CreateUserPrices < ActiveRecord::Migration
    def self.up
       create_table :user_prices do |t|
          t.decimal :price
          t.integer :product_id
          t.date :purchase_date
          t.timestamps
        end
     end
  end

I took out the :purchase_date field so it isn't inside of the user_price loop.

<%= form_tag create_multiple_user_prices_path, :method => :post do %>
 <%= date_select("user_price", "all_dates" )  %>
   <% @user_prices.each_with_index do |user_price, index| %>
      <%= fields_for "user_prices[#{index}]", user_price do |up| %>
          <%= render "user_price_fields", :f => up %>
      <% end %>
   <% end %>
<% end %>




class UserPrice < ActiveRecord::Base
  attr_accessible :price, :product_name, :purchase_date, :all_dates, :store
  attr_accessor :all_dates
  after_save :save_all_dates_to_user_prices
  composed_of :all_dates, :class_name => "DateTime",
    :mapping => %w(Time to_s),
    :constructor => Proc.new { |item| item },
    :converter => Proc.new { |item| item }

  def user_prices
    @user_prices = Array.new() { UserPrice.new }
  end

  protected

  def save_all_dates_to_user_prices 
     if !self.all_dates.nil?       
      self.user_prices.each {|up| up.purchase_date = self.all_dates if up.new_record?}
     end
  end


class UserPricesController < ApplicationController

 def new
    @user_prices = Array.new(5) { UserPrice.new }
 end

 def create_multiple
   @user_prices = params[:user_prices].values.collect { |up| UserPrice.new(up) }
   if @user_prices.all?(&:valid?)
     @user_prices.each(&:save!)
     redirect_to :back, :notice => "Successfully added prices."
   else
     redirect_to :back, :notice => "Error, please try again."
   end
end

回答1:

This is a case of trying to do in a model what is better left to the controller. All you're trying to do here is to auto-assign a certain attribute on creation from a parameter not directly tied to your model. But you're not even passing that extra parameter to the model anywhere - you're creating your model instances from the user_prices parts of the parameter hash, but the user_price sub-hash is not used anywhere. In any case, this is behavior that is more closely related to the view and action taken than the model, so keep it in the controller.

Try this:

  1. Throw out the virtual attribute, and get rid of the whole after_save callback stuff
  2. Throw away the user_prices method in your model
  3. Change the all_dates attribute name back to purchase_date in the form

Then your parameter hash should look like this:

{"user_price"=> { 
  "purchase_date(2i)"=>"10", 
  "purchase_date(3i)"=>"27", 
  "purchase_date(1i)"=>"2011"
}, 
"user_prices"=>
{
  "0"=>{"product_name"=>"Item1", "store"=>"Apple Store","price"=>"6"}, 
  "1"=>{"product_name"=>"Item2", "store"=>"Apple Store", "price"=>"7"}
}}

All that's left to do is to merge the single user_price attributeS into each user_prices sub-hash in your create_multiple action. Replace the first line in that action with this:

@user_prices = params[:user_prices].values.collect do |attributes| 
  UserPrice.new(attributes.merge(params[:user_price])) 
end


回答2:

I'm not sure why you are even using that virtual attribute is there more to this implementation? If you are just trying to save an associated model, you might simply want a accepts_nested_attributes_for :user_prices in your User model

This works great and many developers use this method, so it's nice to know for working on other projects as well as for the people who might end up maintaining yours.

http://railscasts.com/episodes/196-nested-model-form-part-1

http://railscasts.com/episodes/197-nested-model-form-part-2