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
- Edit a property
- Modify the property's purchase price and/or capital reserves value
- Click Update
- These values are converted from currency values ($1,234.56) to decimal (1234.56) by the before_save code in Property
Issue
- Edit a property
- Modify the property's rent current price and/or market price values
- Click Update
- These values do not get saved. The before_save code is not called in the Rent model.
Issue
- Edit a property
- Modify the property's purchase price and/or capital reserves value, but also edit the apartment number
- Click Update
- These values are saved correctly.
- 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_attribute)
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