I'm in a model callback (after_save) and one of the attributes is BigDecimal type. So when I change another attribute and check dirty attributes with changes method I have this:
{"amount"=>[#<BigDecimal:7f86aa3ac900,'-0.4E3',9(18)>, #<BigDecimal:7f86aa3ac838,'-0.4E3',9(18)>], "description"=>["vvvv", "ccc"]}
It instantiates amount as BigDecimal and takes object_id as part of the changes.
Has anyone an idea of how to avoid this behaviour?
If in after_save you need to check if a particular BigDecimal field is really changed, you need to reload rails-created method attr_name_changed? (in your case amount_changed?):
def amount_changed?
if amount_change.present?
amount_change[0].to_f != amount_change[1].to_f
end
end
What it does it compares before (amount_change[0]) and after (amount_change[1]) values in float form.
So then in after_save callback you can do:
after_save :do_something_if_amount_changed
def do_something_if_amount_changed
if amount_changed?
do_something
end
end
Related
I have an instance variable in an active record class called hash_value. It's a serialized hash.
I want to display the hash as XML. Is it right to call hash_value.to_xml? Many nodes are numbers, and XML tags are not allowed to be only number (or start with a number).
I want to override the to_xml method of hash_value. I don't want to override on all hashes, just the hash that's in this record.
class ProductVersion < ActiveRecord::base
serialize :hash_value, Hash
def hash_value.to_xml
end
end
I tried the answer here redefining a single ruby method on a single instance with a lambda
but it doesn't seem to be applicable. I suspect because when I load the record, it creates a new hash_value object and thus the singleton adjustment on the original is moot.
Any thoughts would be great.
I know I could write a function hash_value_to_xml, but I'd rather avoid doing something like that.
Thanks to the first comment, I came up with a solution. Not a good one, but one that works. I'd love to see if there's a better way, because this one smells a bit.
class MyHash < Hash
def to_xml
1/0 #to see if it's run.
end
end
def hash_value
MyHash.new().merge( attributes['hash_value'] );
end
I would personally go for hash_value_to_xml route. But since you insist, here's an idea that might work (haven't tested that)
class ProductVersion < ActiveRecord::base
serialize :hash_value, Hash
alias_method :old_hash_value, :hash_value
def hash_value
h = old_hash_value
h.define_singleton_method(:to_xml) do
# your custom xml logic here
end
h
end
end
The idea is that you intercept value returned from hash_value and patch it on the fly.
I have a price attribute in my model.
Can I use attribute-getter, which is named just like the attribute
def price
... logic logic ..
return something
end
in order to override the attribute itself ?
Currently it doesn't work. If I call model.price it works, but when it somes to saving the object via model.save, it stores the default value.
Can it be done in a painless way, or should I make a before_save callback?
If you set a value in Ruby you access the setter method. If you want to override the setter you have to do something like this:
def price=(_price)
# do some logic
write_attribute(:price, _price)
end
This is of course a discussion point. Sometimes you can better use a callback. Something like this:
before_save :format_price
private
def format_price
# Do some logic, for example make it cents.
self.price = price * 100
end
Since you seem to want the "real" value stored in the database, what you probably want to do is modify the setter. This way the actual value is stored, and the price getter can just return it unmodified.
You can do this via the lower level write_attribute method. Something like:
def price=(value)
# logic logic
self.write_attribute(:price, value)
end
If you want to manipulate the attribute's value right before it's saved then using a callback would be a better way, since this is what callbacks are for.
So I've implemented a hack and I want to know what the "proper" way is to do it.
The issue is that I have an *_attributes=() method that uses an instance variable. The reason this is a problem is that at the time the method is called, that instance variable hasn't been set. Here is the method in question:
def proposed_times_attributes=(attributes)
attributes.each do |key,value|
value[:timezone] = timezone
end
assign_nested_attributes_for_collection_association(:proposed_times, attributes)
end
The timezone is in the params hash after proposed_times_attributes. Therefore my hack is to delete it from params, then add it back, thus moving it to the end of the line.
def create
p = params[:consultation]
a = p.delete(:proposed_times_attributes)
p[:proposed_times_attributes] = a
#consultation = current_user.advised_consultations.new(p)
...
end
What is the proper way that I should be doing this?
new() calls load() where the loop is that goes through each key/value pair.
Thankfully I'm using Ruby 1.9.2 which keeps the order, but it would be nice to know how to do this so that it wouldn't depend on this fact.
If the next operation after new will always be a save operation, you can store the attributes in an accessor, and use a before_validation callback to operate on them as you wish.
class Consultations < ActiveRecord::Base
attr_accessor :proposed_times_attributes
before_validation :assign_proposed_times
def assign_proposed_times
proposed_times_attributes.each do |key,value|
value[:timezone] = timezone
end
assign_nested_attributes_for_collection_association(:proposed_times, attributes)
end
end
Now in your controller you simply have:
def create
#consultation = current_user.advised_consultations.new(params[:consultation])
...
end
If you wish to do other operations before calling save, then pulling out the param as you did in your example, then passing it to an appropriate method after calling new would be the way to go.
I have the following classes in my ActiveRecord model:
def Property < ActiveRecord::Base
# attribute: value_type (can hold values like :integer, :string)
end
def PropertyValue < ActiveRecord::Base
belongs_to property
# attribute: string_value
# attribute: integer_value
end
A PropertyValue object is intended to hold only a string value or an integer value, depending on the type, specified in the value_type attribute of the associated Property object. Obviously, we shouldn't bother the user of the PropertyValue class with this underlying string_value/integer_value mechanism. So I'd like to use a virtual attribute "value" on PropertyValue, that does something like this:
def value
unless property.nil? || property.value_type.nil?
read_attribute((property.value_type.to_s + "_value").to_sym)
end
end
def value=(v)
unless property.nil? || property.value_type.nil?
write_attribute((property.value_type.to_s + "_value").to_sym, v)
end
end
I want to offer the user a view to fill in a bunch of property values, and when the view is posted, I'd like to have PropertyValue objects instantiated based on the list of attributes that is passed in from the view. I'm used to using the build(attributes) operation for this. However, the problem now occurs that I don't have any control over the order in which the attribute initialization takes place. Thus the assignment of the value attribute will not work when the association with the Property attribute has not yet been made, since the value_type cannot be determined. What is the correct "Rails" way to deal with this?
BTW, as a workaround I have tried the following:
def value=(v)
if property.nil? || property.value_type.nil?
#temp_value = v
else
write_attribute((property.value_type.to_s + "_value").to_sym, v)
end
end
def after_initialize
value = #temp_value
end
Apart from the fact that I think this is quite an ugly solution, it doesn't actually work with the "build" operation. The #temp_value gets set in the "value=(v)" operation. Also, the "after_initialize" in executed. But, the "value = #temp_value" does not call the "value=(v)" operation strangely enough! So I'm really stuck.
EDIT: build code
I indeed realized that the code to build the Property objects would be handy. I'm doing that from a Product class, that has a has_many association with Property. The code then looks like this:
def property_value_attributes=(property_value_attributes)
property_value_attributes.each do |attributes|
product_property_values.build(attributes)
end
end
At the same time I figured out what I did wrong in the after_initialize operation; it should read:
def after_initialize
#value = #temp_value
end
The other problem is that the property association on the newly built property_value object will never be set until the actual save() takes place, which is after the "after_initialize". I got this to work by adding the value_type of the respective property object to the view and then having it passed in through the attributes set upon post. That way I don't have to instantiate a Property object just to fetch the value_type. Drawback: I need a redundant "value_type" accessor on the PropertyValue class.
So it works, but I'm still very interested in if there's a cleaner way to do this. One other way is to make sure the property object is attached first to the new PropertyValue before initializing it with the other attributes, but then the mechanism is leaked into the "client object", which not too clean either.
I would expect some sort of way to override the initializer functionality in such a way that I could affect the order in which attributes get assigned. Something very common in languages like C# or Java. But in Rails...?
One option is to save the Property objects first, and then add the PropertyValue objects afterwards. If you need to you could wrap the whole thing in a transaction to ensure that the Properties are rolled back if their corresponding PropertyValues could not be saved.
I don't know what your collected data from the form looks like, but assuming it looks like the following:
#to_create = { :integer => 3, :string => "hello", :string => "world" }
You could do something like this:
Property.transaction do
#to_create.keys.each do |key|
p = Properties.create( :value_type => key.to_s )
p.save
pval = p.property_value.build( :value => #to_create[key] )
pval.save
end
end
That way you don't have to worry about the nil check for Property or Property.value_type.
As a side note, are you sure you need to be doing all this in the first place? Most database designs I've seen that have this kind of really generic meta-information end up being highly non-scalable and are almost always the wrong solution to the problem. It will require a lot of joins to get a relatively simple set of information.
Suppose you have a parent class Foo that holds the property/value pairs. If Foo has ten properties, that requires 20 joins. That's a lot of DB overhead.
Unless you actually need to run SQL queries against PropertyValues (e.g. "get all Foos that have the property "bar"), you could probably simplify this a lot by just adding an attribute called "properties" to Foo, then serializing your Properties hash and putting it in that field. This will simplify your code, your database design, and speed up your application as well.
Oh jeeezzzz... this is insanely simple, now that I puzzled on it a little more. I just need to override the "initialize(attributes = {})" method on the PropertyValue class like so:
def initialize(attributes = {})
property = Property.find(attributes[:property_id]) unless attributes[:property_id].blank?
super(attributes)
end
Now I'm always sure that the property association is filled before the other attributes are set. I just didn't realize soon enough that Rails' "build(attributes = {})" and "create(attributes = {})" operations eventually boil down to "new(attributes = {})".
Probably you should try to use ActiveRecord get/set methods, i.e.:
def value
send("#{property.value_type}_value") unless property || property.value_type
end
def value=(v)
send("#{property.value_type}_value=", value) unless property || property.value_type
end
Mostly in rails if you write my_obj.attr it looks up attr in the database and reports it back. How do you create a custom def attr method that internally queries the database for attr, modifies it and returns? In other words, what's the missing piece here:
# Within a model. Basic attr_reader method, will later modify the body.
def attr
get_from_database :attr # <-- how do I get the value of attr from the db?
end
Something like that:
def attr
value = read_attribute :attr
value = modify(value)
write_attribute :attr, value
save
value
end
Neutrino's method is good if you want to save a modified value back to the database each time you get the attribute. This not recommended since it will execute an extra database query every time you try to read the attribute even if it has not changed.
If you simply want to modify the attribute (such as capitalizing it for example) you can just do the following:
def attr
return read_attribute(:attr).capitalize #(or whatever method you wish to apply to the value)
end