Accoding to this stackoverflow post, I can use the following to detect attribute changes:
self.changes
And it does work when I use it in the model, such in an after_update callback:
def check_activity
changes = self.changes
puts changes
end
Unfortunately, it is not working in my controller update action. I tried to use it before the task was saved and after the task was saved:
def update
changes = #task.changes
if #task.update_attributes(task_params)
changes2 = #task.changes
flash[:notice] = "Successfully updated task."
redirect_to polymorphic_path([#taskable, #task])
else
render :edit
end
end
Unfortunately in both cases, it is empty:
Empty ActiveSupport::HashWithIndifferentAccess
What might I be doing wrong?
There aren't any changes to show after you've made the change (IE update_attributes runs)
You could try using assign_attributes instead of update_attributes, checking that everything is valid (object.valid?) getting your changes and then saving
Just check out ActiveModel::Dirty
Changes method does not returns changes after save.you should use it in before save, if you want to check the value after save use these ones previous_changes etc. for detail checkout http://api.rubyonrails.org/classes/ActiveModel/Dirty.html.
Related
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]}
So i have my form and in my controller i have my update method as follows
def update
#student = Student.find(params[:id])
if #student.update_attributes!(student_params)
#student.read_notes = true
#here i check if the records changed or not?
ap #student.name_changed?
end
end
def student_params
params.require(:student).permit(:name, :email, :age, :class)
end
This fails as i always get the false response each time even though i have actually made changes to the name record.
How do i actually track my changes in my record if i am updating via this way?
When you save the record (which update_attributes!, update!, and update will all do), Rails' "dirty tracking" resets and you lose the ability to easily tell if anything changed. What you could do instead is use assign_attributes, like so:
def update
#student = Student.find(params[:id])
#student.assign_attributes(student_params)
if #student.name_changed?
# ...
end
#student.save!
end
There's also an ActiveRecord method called previous_changes, which stores changes made after a save. This article goes into detail on how to use that.
You could also simply track if the name parameter differs from the record's name, or store the value prior to the update and compare it afterward, depending on your needs.
Suppose i have a model User and a controller UsersController,
in my create actions, i can write
def create
#user = User.new(user_params)
#user.save
redirect_to root_path
end
or
#user = User.new(uer_params)
if #user.save
redirect_to users_path
else
render :new
end
Replicate above 2 actions for Update and destroy also
My question is related to 2nd create action,
Is is necessary to add if else end. what worse could happen i just have create actions like 1st one.
Note: Please ignore the validations part for now.
Just suppose I do not any validations.
What are the other possible conditions in which create/update/destroy will fail apart from validations and which one is the good practice.
Given that you don't want to perform any validations or any checks on the status of the save, then there's no reason for the conditional. In fact, in that case there's also no reason for the #user instance variable. This is all you would need:
def create
User.create(user_params)
redirect_to root_path
end
The conditional is just to do different things based on the status of the save. The instance variable is only to pass the User object to the view. But if you're always doing a redirect then you can't utilize the instance variable anyway, so no need.
What's "right" here is up to the needs of your application. Do the minimum necessary until you have a problem and then fix it.
This:
if User.create(user_params)
is always true. create returns on active reocrd object regardless whether it was successfully created or not. This is why we usually do:
#user = User.new(uer_params)
if #user.save
redirect_to users_path
else
render :new
end
Also note that we are ot redirecting to a new action. The reason is that we already has an #user variable, which 1) holds all the attributes entered by user 2) has all the validation errors attached to it. All we need to do is to render :new template and let Rails do its magic.
Note: If we ignore the validation, then there is no difference which option you will use. You don't need if/else statement neither as it will throw an exception if save fails for any other reason than validation (unless you have after/before_save hooks).
Difference between create & save ?
From the docs:
create
Tries to create a new record with the same scoped attributes defined
in the relation. Returns the initialized object if validation fails
save
.... By default, save always run validations. If any of them fail the
action is cancelled and save returns false. However, if you supply
validate: false, validations are bypassed altogether.
What about validations?
Well,
Create will try saving and returns the initialized object anyway (successful or failed save after validations)
Save will try saving and returns true for successful save and false otherwise
Note that you can skip validations by passing false to save
#user.save(false)
So, what about Conditions?
If you chose to skip validations, using Create or Save(false) then you don't need conditions, while if you need validations then you probably need to check how things went then give user some feedback, hence the conditions
The correct custom params are being displayed in my debug function after the form is submitted but the default params are displayed when I enter console.
Controller
def update
current_user.update_attributes(params[:user])
flash[:success] = "Your settings have been saved!"
render new_status_update_path
end
Model
attr_accessible :deficit_pct,
:target_bf_pct,
:activity_factor
Notes:
The closest question I could find to this on SO is a question that changes the attributes of an object that exists through an association.
I've tried using the Object.update method although I get an error that says:
private method `update' called for #
Any ideas?
Thanks!
Try the code :-
def update
if current_user.update_attributes(params[:user])
flash[:success] = "Your settings have been saved!"
render new_status_update_path
else
p 111111111111
p current_user.errors.inspect
end
end
after check your log for any errors.exist for that active record
After playing around with in the console I've found out that even if I change the attributes manually the attributes don't 'stick' after I exit the console.
So I'll enter console, change the users attributes, test them, and they'll be changed. If I exist and re-enter, THEN test them, they'll have reverted back to their default values.
This leads me to believe that the 'after_initialize' method within the user model which sets its default values is running after each save. I though that it would only run after the object had been saved for the first time alone but now I know it run each time it is saved.
after_initialize :default_values
def default_values
self.goal = "Cut"
self.measurement = "US"
self.bmr_formula = "katch"
self.fat_factor = 0.655
self.protein_factor = 1.25
self.deficit_pct = 0.10
self.target_bf_pct = 0.10
self.activity_factor = 1.3
end
Once I remove all these values and the after_initialize method, it saves permanently.
You should make sure that you don't have any validation errors. You can check them using:
active_record_model.errors
In your case, it would be
current_user.errors
Also, you should print the return value from update_attributes to see if it's true or false. If you get false, the save was cancelled. This was most likely caused by validation errors or a callback returning false.
Something like:
def update
if current_user.update_attributes(params[:user])
flash[:success] = "Your settings have been saved!"
render new_status_update_path
else
some_error_handling_code
end
end
Would not display success when the save fails. As a general rule, you should check whether a save, or any other back end operation, fails, before reporting success to the end user.
I have a model named Post and I created two methods within the model that make changes to fields. The first method's changes get persisted when a save is called. The second method's changes do not get saved. I have noticed this behavior before in other models and I think I'm missing some basic knowledge on how models work. Any help on this would be greatly appreciated!
class Post < ActiveRecord::Base
def publish(user) # These changes get saved
reviewed_by = user
touch(:reviewed_at)
active = true
end
def unpublish() # These changes get ignored.
reviewed_by = nil
reviewed_at = nil
active = false
end
end
EDIT:
Here is a snippet from the controller"
class PostsController < ApplicationController
def publish
if request.post?
post = Post.find(params[:id].to_i)
post.publish(current_user)
redirect_to(post, :notice => 'Post was successfully published.')
end
end
def unpublish
if request.post?
post = Post.find(params[:id].to_i)
post.unpublish()
redirect_to(post, :notice => 'Post was successfully unpublished.')
end
end
...
UPDATE
Problem was solved by adding self to all the attributes being changed in the model. Thanks Simone Carletti
In publish you call the method touch that saves the changes to the database. In unpublish, you don't save anything to the database.
If you want to update a model, be sure to use a method that saves the changes to the database.
def publish(user)
self.reviewed_by = user
self.active = true
self.reviewed_at = Time.now
save!
end
def unpublish
self.reviewed_by = nil
self.reviewed_at = nil
self.active = false
save!
end
Also, make sure to use self.attribute when you set a value, otherwise the attribute will be consideres as a local variable.
In my experience you don't persist your changes until you save them so you can
explicitly call Model.save in your controller
explicitly call Model.update_attributes(params[:model_attr]) in your controller
if you want to save an attribute in your model I saw something like write_attribute :attr_name, value but TBH I never used it.
Cheers