开发者

Parameters not getting saved in Rails 3 app

开发者 https://www.devze.com 2023-03-16 16:02 出处:网络
I am a Rails 3 beginner working on an application that allows the user to enter monetary values. I am using a jQuery plugin (http://github.com/plentz/jquery-maskmoney) to display decimal values as mon

I am a Rails 3 beginner working on an application that allows the user to enter monetary values. I am using a jQuery plugin (http://github.com/plentz/jquery-maskmoney) to display decimal values as monetary values on the edit page. All the decimal attributes are manipulated as currency when editing. When saving the, purchase_price and capital_reserves are saved correctly. The Property before_save function is called and the currency values ($123.45) get converted to decimal values (123.45).

The problem is that the associated rent values are never saved if I just edit the rent prices. I can see the correct values being sent in the parameters but the before_save code in the Rent model is never triggered. If I edit the apartment number value and a rent price, then the rent price is saved correctly. Also, after that I can just edit the price for the apartment number I previously modified. However, any other rent price will not be updated.

I am using MySQL and Rails 3.0.9

Steps to reproduce: Non-Issue

  1. Edit a property
  2. Modify the property's purchase price and/or capital reserves value
  3. Click Update
  4. These values are converted from currency values ($1,234.56) to decimal (1234.56) by the before_save code in Property

Issue

  1. Edit a property
  2. Modify the property's rent current price and/or market price values
  3. Click Update
  4. These values do not get saved. The before_save code is not called in the Rent model.

Issue

  1. Edit a property
  2. Modify the property's purchase price and/or capital reserves value, but also edit the apartment number
  3. Click Update
  4. These values are saved correctly.
  5. Now you can edit the price values for the row previously saved and those prices are saved. Why?

I made a small project to showcase this if anyone is interested (https://github.com/michaelklem/Money-Test).

Here are my data models.

Property class

class Property < ActiveRecord::Base
  before_save :handle_before_save
  has_many :rents, :dependent => :destroy
  accepts_nested_attributes_for :rents, :allow_destroy => true

  def handle_before_save
    if new_record?
      generate_default_rent_data
    end

    remove_currency_formatting
  end

  def generate_default_rent_data
    10.times do |i|
      self.rents.build(:apartment_number => i+1)
    end
  end

  def remove_currency_formatting    
    if self.capital_reserves.to_s != self.capital_reserves_before_type_cast.to_s
      self.capital_reserves = Property.remove_currency_format(self.capital_reserves_before_type_cast)
     end

     if self.purchase_price.to_s != self.purchase_price_before_type_cast.to_s
       self.purchase_price = Property.remove_currency_format(self.purchase_price_before_type_cast)
     end
  end

  #
  # handles removing all characters from currency objects
  # except for 0-9 and .
  #
  def self.remove_currency_format(currency_at开发者_StackOverflow中文版tribute)
    currency_attribute.gsub(/[^0-9.]/, "")
  end
end

Rent class:

class Rent < ActiveRecord::Base
  belongs_to :property
  before_save :handle_before_save

  def handle_before_save
    remove_currency_formatting
  end

  def remove_currency_formatting    
    if self.current_price.to_s != self.current_price_before_type_cast.to_s
      self.current_price = Property.remove_currency_format(self.current_price_before_type_cast)
     end

     if self.market_price.to_s != self.market_price_before_type_cast.to_s
       self.market_price = Property.remove_currency_format(self.market_price_before_type_cast)
     end
  end

end

Not sure if I am seeing a bug or missing something obvious. Thanks for looking into this.

Update

After I posted this I found this SO question Stripping the first character of a string that helped me figure this out. It still seems to me that my original issue is a bug.

I was able to simplify my code to the following and everything works.

class Property < ActiveRecord::Base

  before_save :handle_before_save

  has_many :rents, :dependent => :destroy
  accepts_nested_attributes_for :rents, :allow_destroy => true

  def handle_before_save
    if new_record?
      generate_default_rent_data
    end
  end

  def purchase_price=(data)
      if data.is_a?(String)
        data = Property.remove_currency_format(data)
        write_attribute(:purchase_price, data)
      end
  end

  def capital_reserves=(data)
      if data.is_a?(String)
        data = Property.remove_currency_format(data)
        write_attribute(:capital_reserves, data)
      end
  end

  #
  # generate some default data
  #
  def generate_default_rent_data
    10.times do |i|
      self.rents.build(:apartment_number => i+1) # provide a default value for apartment numbers
    end
  end

  def self.remove_currency_formatting(data)
    if data.is_a?(String)
      data = Property.remove_currency_format(data)
    end
    return data
  end

  #
  # handles removing all characters from currency objects
  # except for 0-9 and .
  #
  def self.remove_currency_format(currency_attribute)
    currency_attribute.gsub(/[^0-9.]/, "")
  end

  def purchase_price=(data)
      _write_attribute(:purchase_price, data)
  end

  def capital_reserves=(data)
      _write_attribute(:capital_reserves, data)
  end

  private 
  def _write_attribute(attribute, data)
    write_attribute(attribute, Property.remove_currency_formatting(data))
  end

end


class Rent < ActiveRecord::Base
  belongs_to :property

  def current_price=(data)
    _write_attribute(:current_price, data)
  end

  def market_price=(data)
      _write_attribute(:market_price, data)
  end

  private 
  def _write_attribute(attribute, data)
    write_attribute(attribute, Property.remove_currency_formatting(data))
  end
end


This may be helpful, and might clean up a lot of your write_attribute code if you chose to use it.

In our app, we run into this issue frequently, we have TONS of amount fields and it would be hellish to add this logic to each model (almost every table has decimal fields for amounts). So in an initializer, I added the following:

ActiveRecord::ConnectionAdapters::Column.class_eval do

  def type_cast_with_commas_removed(value)
    if type == :decimal && value.is_a?(String)
      value = value.delete(',')
    end
    type_cast_without_commas_removed(value)
  end

  alias_method_chain :type_cast, :commas_removed

end

class BigDecimal
  alias :old_to_s :to_s

  def to_s(s=nil)
    if s.nil?
      parts = ("%.2f" % self).split('.')
      parts[0].gsub!(/(\d)(?=(\d{3})+(?!\d))/, "\\1,")
      parts.join('.')
    else
      old_to_s(s)
    end
  end

end

This achieves 2 things: First, on insertion to the database, commas are stripped from all decimal field entries if value is a string (you could change to use your regex of only grabbing numbers and period). Second, when a BigDecimal is converted to string the commas are re-inserted (this was just a business requirement in our case as we wanted to display the proper formatting at all times, you may not need).

Anyway, I know you solved your problem, but just wanted to give another approach. :)

Cheers!

0

精彩评论

暂无评论...
验证码 换一张
取 消