Rails model set boolean when record updated - ruby-on-rails

I have model that I want to set a boolean to false whenever it is changed. Taht way I can reprocess it later to make sure the related records are up to date. I've tried a few things, but keep getting myself into a loop.
/app/model/contest.rb
class Contest < ActiveRecord::Base
has_many :results, dependent: :destroy
after_update :set_contest_not_updated
def set_contest_not_updated
self.contest_updated=false
self.save
end
def set_contest_updated
self.update_column(:contest_updated_at, Time.now)
self.update_column(:contesy_updated, true)
end
Expected action:
contest.update_atrributes(flag_that_effects_scoring: true)
I would expect that is the above is run, the contest.contest_updated boolean would be set to false and only be set to true when the contest.set_contest_updated() method is run.

Of course calling save in the after_update callback will get you into a loop. It keeps saving over and over again until the end of time.
However, before_update should get the job done.
PS: Don't call save on *_save and *_update callbacks. This will always get you into loops.
before_update :set_contest_not_updated
def set_contest_not_updated
self.contest_updated = false
end

This will never work as when your after_update is called, it will invoke another after_update call and you will get the stack level too deep message.
The best solution is to have a foriegn key in another table/model such as contest_updates.
If you want your solution however, set the flag in a before_update filter

Related

Update record in before_destroy and prevent its destruction

I am using Rails 3.2, Ruby 1.9.3.
I need to prevent a record from being destroyed and update it in the before_destroy callback.
Given two classes with the following associations
class Course:
has_many :attendants
...
class Attendant:
belongs_to :course
before_destroy :dont_really_destroy
...
I got a before_destroy callback in Attendant:
def dont_really_destroy
update_attribute :deleted_at, Time.now
false
end
The callback does in fact prevent the delete when I call the destroy method. However, the record is not updated. It seemed reasonable since I by returning false I might be aborting any update (I tried with update_column as well). However, somehow, it does work as expected when the attendant record is "destroyed" from its association's (Course) form, by setting a _destroy form element. The record is correctly updated with deteled_at set, and not destroyed.
I've tried debugging to see the if the instances are different when I try to destroy from the course form vs directly destroying the attendant but I cannot see any differences.
When I do it via the course form, the record is updated like this:
course.assign_attributes(params[:course], :without_protection => true)
...
course.save
Hi instead of using callbacks here, why don't you simply update the destroy action?
#AttendantsController.rb
def destroy
update_attribute :deleted_at, Time.now
head :ok
end
And you default scope in your model
class Attendant:
belongs_to :course
default_scope -> { where(deleted_at: nil) }
...
Hope this will help you.
You could also use ActAsParanoid Gem which introduces soft deletion for rails.
https://github.com/ActsAsParanoid/acts_as_paranoid

Cannot access children in after_create method in Rails 5

In my app I have bookings and passengers where one booking has many passengers.
After creating a new booking I want to send a notification to all passengers, so in my Booking model I have:
after_create :send_notification
def send_notification
self.passengers.each do |passenger|
#DO STUFF
end
end
This does not do anything and if I try
puts(self.passengers.count)
it returns 0.
When I user after_save everything works, so I assume that after_create it created the parent but not the children yet.
Problem is that I can't use after_save because this trigger also after updating.
Any ideas?
You can add condition for after_save:
after_save :send_notification, if: :id_changed?
# or :id_previously_changed? I don't remember if in after_save you can access dirty attributes
Change your callback to this:
after_save :send_notification, on: :create
So that your callback will fire after saving instead of creating the object, but will only occur when it's first created.

How do I invoke a method only when my object (model) is first created in Rails 5?

I'm using Rails 5. I want a method invoked on my model only when the model is first created. I have tried this ...
class UserSubscription < ApplicationRecord
belongs_to :user
belongs_to :scenario
def self.find_active_subscriptions_by_user(user)
UserSubscription.joins(:scenario)
.where(["user_id = ? and start_date < NOW() and end_date > NOW()", user.id])
end
after_initialize do |user_subscription|
self.consumer_key = SecureRandom.urlsafe_base64(10)
self.consumer_secret = SecureRandom.urlsafe_base64(25)
end
end
but I noticed this gets called every tiem I retrieve a model from a finder method in addition to its begin created. How can I create such functionality in my model?
You want to use after_create (from active record docs) or after_create_commit which was introduced in Rails 5 as a shortcut for after_commit :hook, on: :create.
after_create always executes after the transactions block whereas after_create_commit does so after the commit but within the same transactions block. These details likely don't matter here, but it's a new capability if you need that extra control for ensuring the model state is correct before you execute the after call.
Pyrce's answer is good. Another way is to keep the after_initialize method but only run if it's a new record:
after_initialize :set_defaults
def set_defaults
if self.new_record?
self.consumer_key = SecureRandom.urlsafe_base64(10)
self.consumer_secret = SecureRandom.urlsafe_base64(25)
end
end
(It's generally considered better to not override the after_initialize method. Instead provide the name of a method to run, as I did above.

Why does after_save not trigger when using touch?

Recent days , I was trying to cache rails app use Redis store.
I have two models:
class Category < ActiveRecord::Base
has_many :products
after_save :clear_redis_cache
private
def clear_redis_cache
puts "heelllooooo"
$redis.del 'products'
end
end
and
class Product < ActiveRecord::Base
belongs_to :category, touch: true
end
in controller
def index
#products = $redis.get('products')
if #products.nil?
#products = Product.joins(:category).pluck("products.id", "products.name", "categories.name")
$redis.set('products', #products)
$redis.expire('products', 3.hour.to_i)
end
#products = JSON.load(#products) if #products.is_a?(String)
end
With this code , the cache worked fine.
But when I updated or created new product (I have used touch method in relationship) it's not trigger after_save callback in Category model.
Can you explain me why ?
Have you read documentation for touch method?
Saves the record with the updated_at/on attributes set to the current
time. Please note that no validation is performed and only the
after_touch, after_commit and after_rollback callbacks are executed.
If an attribute name is passed, that attribute is updated along with
updated_at/on attributes.
If you want after_save callbacks to be executed when calling touch on some model, you can add
after_touch :save
to this model.
If you set the :touch option to :true, then the updated_at or updated_on timestamp on the associated object will be set to the current time whenever this object is saved or destroyed
this the doc :
http://guides.rubyonrails.org/association_basics.html#touch
You can also (at least in rails 6) just use after_touch: callback
From the docs:
after_touch: Registers a callback to be called after a record is touched. See ActiveRecord::Callbacks for more information.

_changed? method when using counter_cache

I need to update attribute :average_rate when new comment is added.
I have in comments.rb
belongs_to :page, :counter_cache => true
and in page.rb
has_many :comments
after_save :update_average_rate
and update_average_rate method in page.rb
def update_average_rate(comment)
if comments_count_changed?
write_attribute :average_rate, (comments.sum(:rate) / comments.count.to_f).ceil
end
end
but it doesnt work. When I am doing
raise comments_count_changed?.inspect
in update_average_rate method, it outputs "false" , but comments_count is changed. What I am doing wrong? Thanks in advance
Your problem is that counter updates don't actually set the "changed" flags to true.
For a column/attribute a, a_changed? will be true if and only if a has been changed but not saved to the database. The basic behavior goes like this:
Load or create o. o.a_changed? will be false.
o.a = pancakes, o.a_changed? will be true.
o.save, o.a_changed? will be false.
You're using :counter_cache but internally, that uses update_counters and that:
simply does a direct SQL update for the record with the given ID, altering the given hash of counters by the amount given by the corresponding value
So after update_counters has been called, the counter attribute will not be marked as changed as the counter value in the database will be the new one.
I think you'll have to move your average_rate logic into an after_save callback on Comment.

Resources