Rails Cache Sweeper and Model Callback Firing - ruby-on-rails

I have the following classes:
class Vigil < ActiveRecord::Base
after_update :do_something_cool
private
def do_something_cool
# Sweet code here
end
end
class NewsFeedObserver < ActionController::Caching::Sweeper
observe Vigil
def after_update
# Create a news feed entry
end
end
Everything works as expected; however, the after_update in the sweeper requires that the do_something_cool method in the model has finished before it can run properly. The problem is that the after_update in the sweeper is being called before (or perhaps at the same time as) the do_something_cool callback and it's causing problems.
Does anyone know how to force the after_update in the sweeper to fire after the model callback? Is there better way to achieve this?
Update/Fix: As it turns out, unlike the answer below states, the observer callbacks actually ARE firing in the correct order (after the model callbacks). When I discovered this, I realized something else must be wrong.
The do_something_cool method destroys all of a vigil's slots, and replaces them with the correct number of slots with the correct times. The observer relies on the number of slots to figure out how long the vigil should last. So, the underlying problem was that all of the vigil's slots were being destroyed, and that data was cached, so when I called vigil.slots from the observer, it was using the cached (destroyed slots) data. The solution: simply call vigil.slots(true) at the end of do_something_cool to reload/recache the newly created slots!

It's not going to be running at the same time but you're right, it looks like the Sweeper callback is being run before the Model one.
This post might be helpful : http://upstre.am/2007/10/27/using-and-testing-activerecordrails-observers/
About halfway down (search for 'callback :after_read') they have tried to create custom callbacks for their observers. You could use this to create a after_something_cool ARObserver method that gets called when the Model is done being cool e.g.
class Vigil < ActiveRecord::Base
after_update :do_something_cool
private
def do_something_cool
# Sweet code here
callback :after_something_cool
end
end
class NewsFeedObserver < ActionController::Caching::Sweeper
observe Vigil
def after_something_cool
# Create a news feed entry
end
end
Disclaimer: I've never done this and I've always found sweepers to be temperamental between rails versions so what worked for them might not work for you :(

Related

Executing a conditional method only one time for a callback with multiple methods. Rails

In a Rails app, I'm using an after_update call back that runs multiple methods upon passing a conditional method such as below:
app/models/my_model.rb
class MyModel < ApplicationRecord
after_update :method_1, :method_2, :method_3, if: :this_happens?
#some custom methods
private
def this_happens?
# a condition that returns true or false here
end
end
I've noticed that the method this_happens? is executed three times, just before :method_1, :method_2, :method_3.
This make sense as any of those three call back methods could change the data in such a way that the condition has to be evaluated again to make sure it's met in every case. However, when you know those 3 methods are not changing the data in any way that could alter the condition evaluation, is it possible to run this_happens? only once and gain some efficiency?
I don't seem to find anything online and wonder if anyone could advise.
PS. using Rails 5.1
Encapsulation is the one of the easiest way that you can choose to overcome this situation.
Just wrap your methods in another one, then it will only check this_happens? one time.
class MyModel < ApplicationRecord
after_update :combine_methods, if: :this_happens?
#some custom methods
private
def combine_methods
method_1
method_2
method_3
end
def this_happens?
# a condition that returns true or false here
end
end

Ruby Metaprogramming Q: Calling an external class method on after_save

I have the following classes:
class AwardBase
class AwardOne < AwardBase
class Post < ActiveRecord::Base
The Post is an ActiveRecord, and the Award has a can_award? class method which takes a post object and checks to see if it meets some criteria. If yes, it updates post.owner.awards.
I know I can do this using an Observer pattern (I tested it and the code works fine). However, that requires me to add additional code to the model. I'd like not to touch the model at all if possible. What I'd like to do is run the Award checks like this (the trigger will be invoked at class load time):
class AwardOne < AwardBase
trigger :post, :after_save
def self.can_award?(post)
...
end
end
The intention with the above code is that it should automatically add AwardOne.can_award? to Post's after_save method
So essentially what I'm trying to do is to get the trigger call be equivalent to:
class Post < ActiveRecord::Base
after_save AwardOne.can_award?(self)
...
end
which is basically:
class Post < ActiveRecord::Base
after_save :check_award
def check_award
AwardOne.can_award?(self)
end
end
How can I do this without modifying the Post class?
Here's what I've done (which does not appear to work):
class AwardBase
def self.trigger (klass, active_record_event)
model_class = klass.to_class
this = self
model_class.instance_eval do
def award_callback
this.can_award?(self)
end
end
model_class.class_eval do
self.send(active_record_event, :award_callback)
end
end
def self.can_award? (model)
raise NotImplementedError
end
end
The above code fails with the error:
NameError (undefined local variable or method `award_callback' for #<Post:0x002b57c04d52e0>):
You should think about why you want to do it this way. I would argue it is even worse than using the observer pattern. You are violating the principle of least surprise (also called principle of least astonishment).
Imagine that this is a larger project and I come as a new developer to this project. I am debugging an issue where a Post does not save correctly.
Naturally, I will first go through the code of the model. I might even go through the code of the posts controller. Doing that there will be no indication that there is a second class involved in saving the Post. It would be much harder for me to figure out what the issue is since I would have no idea that the code from AwardOne is even involved.
In this case it would actually be most preferable to do this in the controller. It is the place that is easiest to debug and understand (since models have enough responsibilities already and are generally larger).
This is a common issue with metaprogramming. Most of the time it is better to avoid it precisely because of principle of least surprise. You will be glad you didn't use it a year from now when you get back to this code because of some issue you need to debug. You will forget what "clever" thing you have done. If you don't have a hell-of-a-good reason then just stick to the established conventions, they are there for a reason.
If nothing else then at least figure out a way to do this elegantly by declaring something in the Post model. For example by registering an awardable class method on ActiveRecord::Base. But the best approach would probably be doing it in the controller or via a service object. It is not the responsibility of AwardOne to handle how Post should be saved!
Because you are adding award_callback as class method. I bet it will be registered if you grep class methods.
So change your code like below. It should work fine.
model_class.class_eval do ## Changed to class_eval
def award_callback
this.can_award?(self)
end
end
Let me give a detailed example if it sounds confusing.
class Test
end
Test.instance_eval do
def class_fun
p "from class method "
end
end
Test.class_eval do
def instance_fun
p "from instance method "
end
end
Test.methods.grep /class_fun/
# => [:class_fun]
Test.instance_methods.grep /instance_fun/
# => [:instance_fun]
Test.class_fun
# => "from class method "
Test.new.instance_fun
# => "from instance method "

Mongoid 3 callbacks: before_upsert vs. before_save

For Mongoid 3+, is there a diagram/description of the various callbacks?
http://mongoid.org/en/mongoid/v3/callbacks.html
For example, what's the difference between before_upsert vs. before_save. Isn't a save caused by an insert or update call? Or does save also get called by destroy?
Also, what's difference between before_xxx and around_xxx?
Cheers,
With before_xxx the code is executed before the action and with around_xxx you have the option to execute code before and after the action itself.
For example, imagine you want to update all the user belongings after destroy a user project (User has_many :proyects and Project belongs_to User) :
class ProjectsController < ApplicationController
around_destroy :destroy_belongings
def destroy_belongings
old_user = self.user
...
# Here the before_destroy ends.
yield # Here the destroy is performed itself.
# Here the after_destroy starts. It's needed to do this operation after destroy the project because, imagine, the update_belongings method calculates something related to the current number of proyects. And a simple after_destroy is not useful as we would have lost the project owner.
old_user.update_belongings
end
end
You can also see related answers here and here. Moreover this other article could be useful for you.

Rails after_save method trying to update record over and over

I'm having a weird issue, my app seem to get stuck in a loop.
I have a model called Pages which allows the user to input a URL on save I have a after_save method called process_pages which looks as follows
pages.rb (model)
class Page < ActiveRecord::Base
require 'open-uri'
after_save :process_pages
def process_pages
self.html = open(self.url).read
self.save
end
end
On saving the URL I can see in the development console that it get the HTML of the site but tries to constantly save the record over and over again stalling the page and I have to manually exit the server.
When I start back up again the record has been added and works as expected until I add another URL?
Is there anything wrong with my code which might be causing this continuous loop?
Thanks for reading!
You are stuck in a loop because your callback is triggered after save, and then the method definition itself is calling save, causing the callback to fire again.
You have some options. You can change the callback to before_save.
class Page < ActiveRecord::Base
require 'open-uri'
before_save :process_pages
def process_pages
self.html = open(self.url).read
end
end
You can change to an after_create callback that only fires once the record is created and not updated, but this may not be desired behaviour.
You can also skip the callbacks by calling update_column instead of calling save as pointed out by #BroiSatse
This is because you are resaving it all the time. Instead do:
def process_pages
update_column(:html, open(self.url).read)
end
update_column is saving one column to the database skipping all the callbacks, so it won't retrigger your after_save callback. It is however pretty pointless to do two queries while you can do one with before_save filter:
before_save :process_pages
def process_pages
self.html = open(self.url).read
end
But probably best way to go with is overriding setter for url method:
def url=(value)
super
self.html = open(self.url).read
end
This way you can use page html before the model is saved.
If you need to call process_pages only once after Creating a Page record and not after Updating a Page record then I would suggest to use after_create instead.
class Page < ActiveRecord::Base
require 'open-uri'
after_create :process_pages
def process_pages
self.html = open(self.url).read
self.save
end
end
With after_save :process_pages, process_pages method would be called every time you save a Page record. You are saving a Page record again within process_pages method which triggers the after_save callback and you start looping.
See this SO Question for difference between after_save and after_create.
You will understand better as to why you are going in loops.
So does self.url point back to the pages url? If so, when you save it, it hits your server for the page, which might cause another save and another url request, etc.

How to get true after_destroy in Rails?

I have an after_destroy model callback that regenerates cache after the model instance has been destroyed. It does this by calling open("http://domain.com/page-to-cache") for as many pages as need to be re-cached.
The problem is that the model instance apparently isn't fully destroyed yet at this time, because those open url requests still register its presence, and the regenerated cache looks exactly like the pre-destroy cache. How can I run those calls after the model instance has been actually destroyed?
You may be able to use an after_commit callback to do something after the entire transaction has gone through to the database. This is different depending on the version of Rails you're using (2.3.x versus 3.x.x), but is essentially something like the following:
# model_observer.rb
class ModelObserver < ActiveRecord::Observer
def after_commit(instance)
do_something if instance.destroyed?
end
end
You can read some documentation about the Rails 3 after_commit callback here. If your version of Rails doesn't have an after_commit hook, you can try using this gem which will provide the functionality.
You could try adding an after_save callback like:
after_save :my_after_save_callback
def my_after_save_callback
do_something if destroyed?
end

Resources