track params changes in rails active record - ruby-on-rails

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.

Related

Detecting if value of attribute changed during last update doesnt work with Active Model Dirty

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]}

How to skip_callback before_save for specific user?

I've a method named update inside my DailyOrdersController:
def update
if #daily_order.update( daily_order_params.merge({default_order:false}) )
respond_or_redirect(#daily_order)
else
render :edit
end
end
My DailyOrder model:
before_save :refresh_total
def refresh_total
# i do something here
end
What I'm trying to do now is, I want the refresh_total callback to be skipped if the update request is coming from current_admin.
I have 2 user model generated using Devise gem:
User (has current_user)
Admin (has current_admin)
I try to make it like this:
def update
if current_admin
DailyOrder.skip_callback :update, :before, :refresh_total
end
if #daily_order.update( daily_order_params.merge({default_order:false}) )
respond_or_redirect(#daily_order)
else
render :edit
end
end
But it's not working and still keep calling the refresh_total callback if the update request is coming from current_admin (when the logged-in user is admin user).
What should I do now?
I think this is all what you need:
http://guides.rubyonrails.org/active_record_callbacks.html#conditional-callbacks
If you skip callback, you should enable it later. Anyway, this does not look as the best solution. Perhaps you could avoid the callbacks otherwise.
One way would be to use update_all:
DailyOrder.where(id: #daily_order.id).update_all( daily_order_params.merge({default_order:false}) )
Or you could do something like this:
#in the model:
before_validation :refresh_total
#in the controller
#daily_order.assign_attributes( daily_order_params.merge({default_order:false}) )
#daily_order.save(validate: current_admin.nil?)
or maybe it would be the best to add a new column to the model: refresh_needed and then you would conditionally update that column on before_validation, and on before_save you would still call the same callback, but conditionally to the state of refresh_needed. In this callback you should reset that column. Please let me know if you would like me to illustrate this with some code.
This may come in handy:
http://www.davidverhasselt.com/set-attributes-in-activerecord/
UPDATE
Even better, you can call update_columns.
Here is what it says in the documentation of the method:
Updates the attributes directly in the database issuing an UPDATE SQL
statement and sets them in the receiver:
user.update_columns(last_request_at: Time.current)
This is the fastest way to update attributes because it goes straight to
the database, but take into account that in consequence the regular update
procedures are totally bypassed. In particular:
\Validations are skipped.
\Callbacks are skipped.
+updated_at+/+updated_on+ are not updated.
This method raises an ActiveRecord::ActiveRecordError when called on new
objects, or when at least one of the attributes is marked as readonly.

Checking if an attribute changed after commit in Rails

I know that using dirty or changed? is useful in a before_commit situation or before the model is saved. After the model is saved, previous_changes gives the changes. Which is great. But,
How can I check if a specific attribute was changed in a controller if it looks like
def update
#item = Item.find(params[:id])
if #item.update_attributes(item_params)
# horray
# check if the :name changed, if so do something about it
else
# d'oh
end
end
Is there a better way than doing !#item.previous_changes[:name].nil?
I do not know if there is any better way, but I would do:
if #item.previous_changes.has_key?('name')
# Name has been changed.
end
In Rails 5 you can use saved_change_to_attribute? to check if the specific changes.

Using Rails model to update to change one boolean value based on another

I'm trying to make a pet rails app. My pet model includes two boolean values, hungry and feed_me. Right now, hungry and feed_me can both be set in the view, but I'm trying to set up the model so that if feed_me is true, hungry will automatically be changed to false. No matter what I do, however, feed_me never resets hungry. This is what I have in the model now:
attr_accessor :feed_me
before_save :feed
def feed
#feed_me = Creature.find(params[:feed_me])
#hungry=Creature.find(params[:hungry])
if #feed_me==true
#hungry=false
end
end
I'm new to Rails, but my understanding is that model should have access to the params hash, so I'm confused about why I can't use it to reset values.
You're on the right track using model callbacks, however models don't have access to the param hash - its available to controllers.
The model already knows the value of it's own attributes, so you don't need to get them from params. The controller I imagine is updating feed_me.
Also you shouldn't need to declare feed_me as an attr_accessor assuming it is backed by a database column.
You can change before_save to:
def feed
if self.feed_me
self.hungry = false
end
end
In your controller, I imagine you'd do something like:
def update
pet = Pet.find(params[:id])
pet.feed_me = params[:feed_me]
if pet.save
redirect_to pet_path(pet)
else
flash[:notice] = 'Error saving pet'
render :edit
end
end

how to capture the time a field is updated

I'm aware of updated_at and created_ate in rails.
But what I'm interested in is the ability to update a field within a model when another field is updated. Here's what I've tried:
in my model:
protected
def update_email_sent_on_date
if self.send_to_changed?
self.date_email_delivered = DateTime.now
end
end
and in the one place in my code that updates the field in question:
distribution.send(:update_email_sent_on_date)
the problem is, this doesn't seem to be doing anything to my db table at all. I even tried removed the check on "send_to" but still nothing.
what am I doing wrong?
You're not saving it after you make the change.
Change the method to this:
def update_email_sent_on_date
if send_to_changed?
self.date_email_delivered = DateTime.now
save
end
end
Or save the model after calling it like so:
distribution.update_email_sent_on_date
distribution.save

Resources