Thanks everyone for your responses. Felix, your before_save
suggestion sounds promising. But will before_save be able to skip the
save operation if the "do some stuff" fails?
What I want to do is this: always do "some stuff" whenever the x
attribute is updated. But if "some stuff" returns nil which means it
has failed, do not update x, and then also return nil to the call to
update x to let the caller know that the update operation of x has
failed.
I looked up before_save on the API guide and online but still don't
know the answer.
Right now the way I am implementing this is:
def set_x(val)
catch :some_stuff_has_failed do
do_some_stuff # throw :some_stuff_has_failed if it fails
self.x = val
save!
end
end
I try to make the setter "x=" private so that anybody using my class
has to use "set_x" to set a value for x. However, I am getting the
"attempt to call private method" error which causes me to post this
thread.
Please let me know if my description of the problem is still
incomplete.
Thanks all for your help.
That sounds a lot like a validation, apart from saving the model in the setter, which you shouldn't do. From what I've gathered till now, rails assumes you always work on a model as a whole, not on a single attribute of the model. Another thing that you must always take care of: If for whatever reason you decide to override any default method or whatever of any API you use, make sure to always have the same return value. Rails assumes a setter for an attribute of an ActiveRecord object returns the attribute, heck even ruby assumes a setter returns the object being set (or fail) for _whatever_ object:
"""
$ irb
>> x = 3
=> 3
>> x = :some_funky_symbol
=> :some_funky_symbol
>> x = some_variable_not_set
NameError: undefined local variable or method `some_variable_not_set' for main:Object
from (irb):3
"""
Anyway, if your some_stuff is a validation, i.e. doesn't modify the value of x, try this:
=== In the model ===
validate :some_validation
private
def some_validation
# do some stuff with self.x
# return true or whatever you want if it validates, return false or an exception if not
end
=== In the controller ===
@my_model.x = "some_value"
@my_model.valid? # returns false if not all validations pass
@my_model.save # also fails (can't remember if it returns false) if not all validations pass, saves the model to the db if they do
(you obviously don't need the valid? step, as it is called in save too, but may this will help you in some way)
This separation between the data (model), and the business logic, i.e. what you do with the data (controller) is just a consequence of the strong MVC separation in rails.
One thing that worries me, is that I still don't know if the some_stuff you want to do with x also changes x. If it does, you should separate the x processing part from the validation, put the processing part in the public setter x=(val), and make it return the processed x value, and put the validation as explained above.
For completeness' sake: if some part of before_save fails, save will also fail, although I'm not sure if a false return value will work, or if you need to throw an exception.
In the end, I would urge you to rethink your MVC model: a setter should return the object that gets to the internal attribute/object, a setter should never save the object in the process (I'd even go as far as saying that no function in the model should ever call save or save!, because in 99% of the cases, it might bite your dog), the validation of the attributes, be it sanity, existence or compliance to some format, should occur on save, and the handling of failing validations or a failing save should happen in the controller, not in the model (maybe have a look at the defaults in a scaffolded object and model: the update method in the controller for example fetches the object, writes the parameters it got in the attributes of the object, and the does if @the_object.save do some_happy_stuff else be_sad end).
I hope I was clear enough, but if there still is something unclear to you, feel free to ask 
HTH,
Felix