how to capture the time a field is updated - ruby-on-rails

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

Related

Rails set model attribute if blank

I have a User model that has an attribute called country. This attribute is set by a method called methodA.
Somewhere else in my code I may try to access User's country attribute and it might be blank if methodA never ran.
What I'm looking for is to run methodA if I try to access User's country attribute and it's blank.
I tried something like that in my Model :
def country
c = read_attribute(:country).presence
if c.blank?
methodA
else
return c
end
end
But I get an error when it first runs. If I reload the page, country has been set on the previous run (even tho the error) and it's all good.
I would love it to work on the first run and avoid the error page tho...
Thanks in advance for your help
You can just call super
def country
super.presence || "do whatever"
end
presence will check present? and if present? it will return its receiver; otherwise it returns a false-y value (nil).
Remember that if possible you should be setting a database default.
I have managed to achieve my goal using this :
class User < ApplicationRecord
after_find :check_country
def check_country
if country.blank?
methodA
end
end
def methodA
...code
end
end
It does work but I'm not sure if this is ideal... Because check_country will be executed everythime a User is fetched... Even if country is set.
I would prefer it to run only if country is blank.
Any idea ?

Is there a possible way to track when a boolean initially turn true in rails?

I have a boolean column in my table and a datetime related to it. I want to be able to track down when the boolean initially turns true. In the model I have tried
def example
if boolean_field == true
datetime_field = time.now
end
end
but the problem with that is when the boolean is set to true, the datetime_field will keep updating everytime i reload the page or database since the boolean will always be in a constant state of true. I have also try to use rails dirty on it with:
boolean_field_changed?
but that method doesn't seems to update the datetime column at all.
P.S i do have a before_save callback on the method.
def returned_time
if self.returned?
returned_at = Time.now
end
end
before_save :returned_time
any help on how i can save the initial time once the boolean_field changes from false to true will be much appreciated. thanks in advance
If returned is a boolean column in your table, then you should not need ActiveModel::Dirty. That module is for adding change detection for non-database attributes. You should be able to use Active Record's built-in changed? method:
class MyModel < Application Record
before_save :returned_time
def returned_time
if self.returned? and self.returned_changed?
self.returned_at = Time.now
end
end
end
This will update the returned_at time if returned is true and if that field has changed. This may suit your business logic.
However, if your goal is to only update the returned_at field when a book has been returned, you may want to check for the presence of a returned_at date instead:
def returned_time
if self.returned? and self.returned_at.blank?
self.returned_at = Time.now
end
end
This will only update the date if the book has been returned and there is no return date.
One final note, the self before returned_at in self.returned_at = Time.now is important. Otherwise, the field may not get updated.

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

track params changes in rails active record

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.

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.

Resources