Use regular attribute assign and save or use update_attribute? - ruby-on-rails

I recently 'discovered' the update_attribute method. So, I started changing sequences like
self.attribute = "foo"; save
in model or controller methods by
self.update_attribute(:attribute, "foo")
Now, the more I'm doing this, the more I'm wondering whether this is "good practice", and whether this method was intended to be used this way.
Any input from the "pro's" on this?

I would suggest using update_attribute for flags or any update operation that does not need validations since it does not fire validations. From rails documentation we can read:
Updates a single attribute and saves the record without going through
the normal validation procedure. This is especially useful for boolean
flags on existing records. The regular update_attribute method in Base
is replaced with this when the validations module is mixed in, which
it is by default.
Whereas update_attributes does:
Updates all the attributes from the passed-in Hash and saves the
record. If the object is invalid, the saving will fail and false will
be returned.
Let's look at the code now:
def update_attribute(name, value)
send(name.to_s + '=', value)
save(false)
end
def update_attributes(attributes)
self.attributes = attributes
save
end

It's always better to use update_attribute, or update_attributes if you need to update a single instance with simple data, as you can read "UPDATE" and know that you are "UPDATING".
You must know also that there is a method called update_column, that does 'kinda' the same stuff, but, update_column does NOT update the updated_at timestamp on the database.
Also, if you need to edit a large amount of instances/rows in the database with the same value, you have a method called update_all. Here is an example
#instances = Instance.all
#instances.update_all(:attribute, value)
and that will update all the attributes of that table. You will find this usefull after doing werid migrations.
Besides all of this, you can always use the 'save' way, I strongly recomend this when you have to calculate a lot of data to update a single instance. Here is an example:
#BAD
def updater_method
foo = Bar.first
foo.update_attributes(attr_one: some_calcule_method, attr_two: some_other_calcule_method, attr_three: some_more_calcule_method)
end
#GOOD
def saver_method
foo = Bar.first
foo.attr_one = some_calcule_method
foo.attr_two = some_other_calcule_method
foo.attr_three = some_more_calcule_method
etc
foo.save!
end
This will help you in debbuging, so if any method fails, you can see it clearly, with the line number and all that stuff.
Regards, Lucas.

Related

ActiveRecord::Base Class Not Mutable?

I have a class I've extended from ActiveRecord::Base...
class Profile < ActiveRecord::Base
and I collect the records from it like so...
records = #profile.all
which works fine, but it doesn't seem that I can successfully Update the attributes. I don't want to save them back to the database, just modify them before I export them as JSON. My question is, why can't I update these? I'm doing the following (converting date formats before exporting):
records.collect! { |record|
unless record.term_start_date.nil?
record.term_start_date = Date.parse(record.term_start_date.to_s).strftime('%Y,%m,%d')
end
unless record.term_end_date.nil?
record.term_end_date = Date.parse(record.term_end_date.to_s).strftime('%Y,%m,%d')
end
record
}
At first I had just been doing this in a do each loop, but tried collect! to see if it would fix things, but no difference. What am I missing?
P.S. - I tried this in irb on one record and got the same results.
I suggest a different way to solve the problem, that keeps the logic encapsulated in the class itself.
Override the as_json instance method in your Profile class.
def as_json(options={})
attrs = super(options)
unless attrs['term_start_date'].nil?
attrs['term_start_date'] = Date.parse(attrs['term_start_date'].to_s).strftime('%Y,%m,%d')
end
unless attrs['term_end_date'].nil?
attrs['term_end_date'] = Date.parse(attrs['term_end_date'].to_s).strftime('%Y,%m,%d')
end
attrs
end
Now when you render the records to json, they'll automatically use this logic to generate the intermediate hash. You also don't run the risk of accidentally saving the formatted dates to the database.
You can also set up your own custom option name in the case that you don't want the formatting logic.
This blog post explains in more detail.
Try to add record.save! before record.
Actually, by using collect!, you just modifying records array, but to save modified record to database you should use save or save! (which raises exception if saving failed) on every record.

rails active record callback that trigger after values are retrieved from database

Is there an active record callback that would let me set a non-db field right after some db fields have their values being retrieved? I need to do it after the database retrieval because I need the values to compute for the non-db field.
I welcome other ways to do this as well. Specifically, I have a days_ago field that is non-db, then I want to create it only after created_at becomes available, and I have to do this inside the model class.
You should use after_find:
after_find :set_days_ago
def set_days_ago
self.days_ago = DateTime.now - self.created_at.to_datetime if self.created_at
end
It sounds like you might want a 'lazy-loaded' days_ago field. You should only calculate the days_ago value when you actually need it, by adding a method like this to your model:
def days_ago
return nil unless created_at?
#days_ago ||= ... # (calculation involving created_at)
end
The first time you call days_ago, it will calculate the value, and cache the value in the #days_ago variable. The second time you call it, it will return the cached value.

Rails 3 - check that object is valid with params before update

I have very newbie question. How can i check that object of model is valid with new params BEFORE updating it?
I want transform that:
def update
#obj = SomeModel.find( params[:id] )
if #obj.update_attributes( params[:obj] )
# That have been updated
else
# Ups, errors!
end
end
To something like that:
def update
#obj = SomeModel.find( params[:id] )
if #obj.valid_with_new_params( params[:obj] )
#obj.update_attributes( params[:obj] )
else
# Ups, errors!
end
end
To update the attributes without saving them, you can use
#obj.assign_attributes( params[:obj] )
Then to check if the object is valid, you can call
#obj.valid?
If the object is not valid, you can see the errors (only after calling .valid?) by calling
#obj.errors
If the object is valid, you can save it by calling
#obj.save
However, all of this usually isn't necessary. If the object isn't valid, then ActiveRecord won't save the object to the database, so all of the attribute changes are forgotten when you leave the controller action.
Also, since an invalid record won't be saved to the database, you can always just call Object.find() again to get the original object back.
You can call the valid? method to run the validations.
This doesn't guarantee that a subsequent save will succeed if some of your validations depend on the state of other objects in the database. Te save could also fail for reasons unconnected to validations (eg a foreign key constraint)
I'm not sure why you'd want this pattern
The object won't be saved if the passed argument doesn't produce a valid object, so you can use your way just fine. You can see the errors (if any) using the #obj.errors array.
update_attributes method validate object and return false if object is invalid. So, you can just write:
if #obj.update_attributes( params[:obj] )
# That have been update
else
# Ups, errors!
end
The answer is that you can define a method
def valid_with_new_params(hash)
self.attributes = hash
valid?
end
But that would be unnecessary because #obj.update_attributes(params[:obj]) returns true if the obj was successfully updated and false otherwise. Note also that internally the update_attributes method runs all validations on the #obj so that you have #obj.errors available if the update failed.
To update the attributes without saving them
#obj.attributes = params[:obj] 0r
#obj.attributes = {:name => “Rob”}
To then check if the object is valid
#obj.valid?
To check if there is any error
#obj.errors

Existing Rails model without fetching it from the database

Does anyone know if its possible to create a model instance and apply the ID and any other attributes without having to load it from the database? I tried doing this, but the associations are not fetched from the database :( Any ideas?
EDIT
What I want to accomplish is simply this:
Fetch an existing record from the database.
Store as "hashed" output of the record into redis or some other memory store.
Next time when that record is fetched, fetch the cached store first and if it is not found then goto step 1.
If there is a cache hit, then load all the cached attributes into that model and make that model instance behave as if it were a model fetched from the database with a finite set of columns.
This is where I am stuck, what I've been doing is creating a Model.new object and setting each of the params manually. This works, but it treats the instantiated model object as a new record. There has got to be an intermediate subroutine in ActiveRecord that does the attribute setting.
I solved the problem by doing the following.
Create a new model class which extends the model class that I want to have cached into memory.
Set the table_name of the new class to the same one as the parent class.
Create a new initialize method, call the super method in it, and then allow a parameter of that method to allow for a hash variable containing all the properties of the parent class.
Overload the method new_record? and set that to false so that the associations work.
Here's my code:
class Session < User
self.table_name = 'users'
METHODS = [:id, :username] # all the columns that you wish to have in the memory hash
METHODS.each do |method|
attr_accessor method
end
def initialize(data)
super({})
if data.is_a?(User)
user = data
data = {}
METHODS.each do |key|
data[key] = user.send(key)
end
else
data = JSON.parse(data)
end
data.each do |key,value|
key = key.to_s
self.send(key+'=',value)
end
end
def new_record?
false
end
end
The memcached gem will allow you to shove arbitrary Ruby objects into it. This should all get handled for you transparently, if you're using it.
Otherwise, take a look at ActiveRecord::Base#instantiate to see how it's done normally. You're going to have to trace through a bunch of rails stack, but that's what you get for attempting such hackery!

Simple boolean data update with mongdb?

I am using Rails and mongoid to work with mongodb.
Usually in rails when working with Active:Record, you have access to the method .toggle! which simply allows you to invert the value of a boolean field in your db.
Unfortunately this method is not available for mongoDB:
user = User.first
user.toggle!(:admin)
NoMethodError: undefined method `toggle!' for #<User:0x00000100eee700>
This is unfortunate... and stupidly enough I don't see how to get around without some complicated code...
Any suggestion on how to achieve the same result concisely ?
Thanks,
Alex
ps: also one of the problems is that when I want to modify the field, it goes through validation again... and it's asking for the :password which I don't save in the db, so:
User.first.admin = !User.first.admin
won't even work :(
The issue here is specifically mongoid, not mongodb. toggle! is a part of ActiveRecord::Base, but fortunately it's not hard to replicate.
def toggle!(field)
send "#{field}=", !self.send("#{field}?")
save :validation => false
end
Add that into your model (or add it into a module, and include it in your model), and your Mongoid models will gain functionality equivalent to what you're used to in AR. It will read the field's value, invert it, write it (through the setter, per the toggle! documentation), and then save the document, bypassing validation.
# Get object's boolean field and toggle it
# #param [Object] mongoid object
# #param [String, Symbol] flag
# #example
# foo = User.find('123')
# toggle_flag!(object: foo, flag: :bar)
def toggle_flag!(object:, flag:)
object.update(flag => !object[flag])
object.save!
end
Ok the validation did not work because of a type, the code should be:
save :validate => false (not :validation)

Resources