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
Related
I do a first_or_create statement, followed by a update_attributes:
hangout = Hangout.where(tour: tour, guide: current_user).first_or_create
hangout.update_attributes(priority: current_user.priority)
If the record already existed, it updates the priority. If it doesn't exist previously, there is no update. Why?
Thanks!
update_attributes (aka update) returns a boolean indicating if there was an error, if you do not check it - use bang-version update! so that exception will not be ignored.
Most probably record does not get created due to validation. Also when you're updating new record just after create - better use first_or_create(priority: current_user.priority) or first_or_initialize(with subsequent update) to spare extra DB write.
def update_attributes!(attributes)
self.attributes = attributes
save!
end
update attribute with bang call the save with bang.
def save!(*args, &block)
create_or_update(*args, &block) || raise(RecordNotSaved.new("Failed to save the record", self))
end
Inside the save! RecordNotSave error will be raise if it cannot save the record.
so you can customize the error handing from your code.
begin
hangout.update_attributes!(priority: current_user.priority)
rescue RecordNotSaved => e
#do the exception handling here
end
I am trying to send a notification email in my rails app only if the value of my column status was modified by the current update. I tried using Active Model Dirty as was suggested in some post and the status_changed? method. Unfortunately my email is never sent because #partnership.status_changed? constantly returns false even though the value of status was indeed changed during the last update. Here's my controller code :
def update
authorize #partnership
if #partnership.update(partnership_params)
send_notification_email
render json: {success: "partnership successfully updated"}, status: 200
else
render_error(nil, #partnership)
end
end
private
def send_notification_email
PartnershipMailer.partnership_status_change(#partnership).deliver_now if #partnership.status_changed?
end
I have also included Active Model Dirty in my model :
class Partnership < ActiveRecord::Base
include ActiveModel::Dirty
What am I doing wrong ?
.update also saves the model after updating it's data, therefore resetting the dirty-values. Try using .assign_attributes. It will just assign the attributes, then you can check for changes, and finally remember to save the model.
As #Thounder pointed out, the ActiveModel::Dirty method <attribute>_changed? is reset whenever you save a record. Thus, it only tracks changes between saves.
For your use case, what you want to use is the previous_changes method, which returns a hash with the key being the attribute changed and the value being an array of 2 values: old and new.
person = Person.new(name: "Bob")
person.name_changed? # => true
person.save
person.name_changed? # => false (reset when save called)
person.previous_changes # => { name: [nil, "Bob"] }
person.previous_changes[:name] # => returns a "truthy" statement if :name attribute changed
My pseudo-code may be wrong, but the principle works. I've been bitten by this "gotcha" before, and I wish the Rails core team would change it.
I understand their reasoning, but it makes more sense to me to track <attribute>_changed? after a save as well, because that seems the common use case to me.
You can try this method to check the changed attributes for the active record.
#partnership.changed.include?("status")
If it returns true then we have status attribute which was changed in this record.
Use #partnership.saved_change_to_status? or #partnership.saved_change_to_attribute(:status) as per docs.
Here is a one line method you can into the model which is the best for your case :
after_commit :send_notification_email, if: Proc.new { |model| model.previous_changes[:status]}
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.
I am maintaining someone's code base and they have something like this:
if #widget_part.destroy
flash[:message] = "Error deleting widget part"
else
flash[:message] = "Widget part destroyed successfully"
end
What does destroy return? Is it ok to test like this? The reason I'm asking is that I tried to use
flash[:message] = "Error deleting widget part : #{#widget_part.errors.inspect}"
and there are no error messages so I am confused. It gives something like
#<ActiveModel::Errors:0x00000103e118e8 #base=#<WidgetPart widget_id: 7, ...,
id: 67>, #messages={}>
If you're unsure, you can use destroyed? method. Return value of destroy is undocumented, but it returns just freezed destroyed object (you cannot update it). It doesn't return status of destroy action.
Although generally destroying object should always succeed, you can listen for ActiveRecordError. For example Optimistic Locking can raise ActiveRecord::StaleObjectError on record destroy.
As some people mentioned above, that destroy does not return a boolean value, instead it returns a frozen object. And additionally it does update the state of the instance object that you call it on. Here is how I write the controller:
#widget_part.destroy
if #widget_part.destroyed?
flash[:success] = 'The part is destroyed'
else
flash[:error] = 'Failed to destroy'
end
According to the Ruby on Rails API documentation, the destroy method will return the object that you destroyed, but in a frozen state.
When an object is frozen, no changes should be made to the object since it can no longer be persisted.
You can check if an object was destroyed using object.destroyed?.
Note that while #destroyed? works in the OP’s case, it only works when called on the same model instance as #destroy or #delete; it doesn’t check the database to see if the underlying record has been deleted via a different instance.
item1 = Item.take
# => #<Item:0x00000001322ed3c0
item2 = Item.find(item1.id)
# => #<Item:0x00000001116b92b8
item1.destroy
# => #<Item:0x00000001322ed3c0
item1.destroyed?
# => true
item2.destroyed?
# => false
item2.reload
# => raises ActiveRecord::RecordNotFound
If you need to check whether another process has deleted the record out from under you (e.g. by another user, or in a test where the record is deleted via a controller action), you need to call #exists? on the model class.
Item.exists?(item2.id)
# => false
Have a form that has 1 field, an email address.
When submitted the model calls :before_save
Checks to see if the email address exists.
If it does, it creates a new record.
If it does not, it updates a record in another model AND NO record should be created.
Using return false to cancel the before_save for 2.2 but it rolls back the update and cancels the creation where I just want the record not to be created.
Am I on the right path? Is there a better way?
It is strange that you got user A's object, but update user B's row......
Maybe you could find the correct user object in the controller first:
#user = User.find_by_email(params[:email]) || User.new(params[:email])
User.find_by_xxx would return nil if it cannot find the corresponding object (or return the first object if there are two or more objects matched).
You could just make your own before_save method equivalent and call that instead of object.save
eg.
def custom_save
if email_address_exists? # this would be a method you create
self.save
else
# update record in other model
end
end
Then in your controller use this instead of save (ie. model.custom_save)