Rails expire cache with ActionController::Caching::Sweeper not sweeping at all - ruby-on-rails

I've a CategoryController action which responds with a complex Category tree (counts containing Products. For better performance I use Memcached. It works: First request 200ms, second request 12ms.
What I try to achieve:
When a product is added to a Category, it should invalidate the Category action "main_menu".
This is what I've so far, but the CategorySweeper methods are never called:
class CategoryController < ApplicationController
caches_action :main_menu, expires_in: 1.day
cache_sweeper CategorySweeper, only: [:main_menu]
def main_menu
render json: Category.navigation
end
end
class CategorySweeper < ActionController::Caching::Sweeper
observe Category, Product
def after_create(category)
puts "##### update #{category}" # never gets called
expire_cache_for(category)
end
def after_update(category)
puts "##### update #{category}" # never gets called
expire_cache_for(category)
end
private
def expire_cache_for(category)
expire_page(controller: 'category', action: 'main_menu')
expire_action(controller: "category", action: 'main_menu')
end
end
of course I've the gem in my Gemfile
gem 'actionpack-action_caching', github: 'rails/actionpack-action_caching', :branch => 'master'
gem 'rails-observers'
What I am doing wrong?
Am I using the gem incorrect?
Thank you in advance

Related

Getting Rails 5 app to return JSON API format

I'm trying to get my app to return in lowercase camelcase for eventual JSON API formatting.
I've installed gem 'active_model_serializers' and created a new initializer with the following code in it:
ActiveModelSerializers.config.adapter = :json_api
ActiveModelSerializers.config.key_transform = :camel_lower
Then I have a small API that returns json, as all of the best internet applications do:
class Api::V1::UsersController < API::V1::BaseController
def sky
#user = User.find_by_id(params[:user_id])
if #user
obj = {
sky: {
sectors: #user.sectors,
slots: #user.slots
}
}
render json: obj
else
raise "Unable to get Sky"
end
end
More on the API controller inheritance pattern: class API::V1::BaseController < ActionController::Base
The Problem
In my API response, things are still snake cased and I see this error in the console [active_model_serializers] Rendered ActiveModel::Serializer::Null but my research has led me to a dead end as to what to do.
Any suggestions would be very welcome. Thanks!
The problem is you're not calling an active record serializer in your controller, so those config settings aren't being picked up.
Solution:
Create a UserSerializer in "app/serializers/user_serializer.rb" that should look something like this:
class UserSerializer < ActiveModel::Serializer
attributes :id
has_many :sectors
has_many :slots
end
as well as similarly structured SectorSerializer and a SlotSerializer with all of the attributes you want from each (Here are the getting started docs and the general syntax docs for active record serializers)
Then in your controller:
class Api::V1::UsersController < API::V1::BaseController
def sky
#user = User.includes(:sectors, :slots).find_by_id(params[:user_id])
if #user
render json: #user
else
raise "Unable to get Sky"
end
end
end
Which will eager load :slots and :sectors with includes then calls your UserSerializer using your camel_case config options.
In your controller put respond_to :json
class Api::V1::UsersController < API::V1::BaseController
respond_to :json
and in the action put same that you have
def sky
...
render json: obj
...
end
and define in base controller
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
From this pull request (*) it looks like you should be able to configure key_format = :lower_camel in your ActiveModel::Serializers config.
(*) https://github.com/rails-api/active_model_serializers/pull/534
i think it helps you. in my case i use gem 'active_model_serializers', '~> 0.10.5' which depends on case_transform (>= 0.2)
and in rails console i can do
CaseTransform.camel_lower(initially_serialized_output)
=> {:name=>"My Company", :jsonThings=>{:rating=>8, :aKey=>{:aSubkey=>{:anotherKey=>"value"}}}}
my research was by steps:
https://github.com/rails-api/active_model_serializers/pull/1993 => https://github.com/NullVoxPopuli/case_transform-rust-extensions
did you find this?

Error while generating sweeper in rails

Everyone is like this is sweeper. But can anyone please tell me how to create sweeper file for a particular controller. I copy pasted the code but its not working.
Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change. They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
class ImageSweeper < ActionController::Caching::Sweeper
observe Image
def after_save(record)
def after_save(image)
expire_cache(image)
end
def after_destroy(image)
expire_cache(image)
end
def expire_cache(image)
##expire_fragment #'image'
expire_cache(image)
end
end
The sweeper is assigned in the controllers that wish to have its job performed using the cache_sweeper class method:
class ImageController < ApplicationController
cache_sweeper :image_sweeper, :only => [ :edit, :destroy, :share ]
end
In the example above, three actions are responsible for expiring those caches
Above will only work if you have config.action_controller.perform_caching = true in development.rb
I got it working. Sweeper file will be in app/sweepers/controllername(without s)_sweeper.rb

in Rails where do you put your Sweepers?

Is there a convention in Rails to put Sweeper classes in a particular directory location?
UPDATE: Since observers are put into app/models, I'm assuming sweepers are no different, as long as the name always ends with "sweeper".
I like to put them in the app/sweepers directory.
I also put Presenters in the app/presenters directory...and Observers in the app/observers directory.
Try putting them in the app/models directory.
Sweepers
Cache sweeping is a mechanism which allows you to get around having a ton of expire_{page,action,fragment} calls in your code. It does this by moving all the work required to expire cached content into
na ActionController::Caching::Sweeper class. This class is an Observer that looks for changes to an object via callbacks, and when a change occurs it expires the caches associated with that object in an around or after filter.
Continuing with our Product controller example, we could rewrite it with a sweeper like this:
class StoreSweeper < ActionController::Caching::Sweeper
# This sweeper is going to keep an eye on the Product model
observe Product
# If our sweeper detects that a Product was created call this
def after_create(product)
expire_cache_for(product)
end
# If our sweeper detects that a Product was updated call this
def after_update(product)
expire_cache_for(product)
end
# If our sweeper detects that a Product was deleted call this
def after_destroy(product)
expire_cache_for(product)
end
private
def expire_cache_for(record)
# Expire the list page now that we added a new product
expire_page(:controller => '#{record}', :action => 'list')
# Expire a fragment
expire_fragment(:controller => '#{record}',
:action => 'recent', :action_suffix => 'all_products')
end
end
The sweeper has to be added to the controller that will use it. So, if we wanted to expire the cached content for the list and edit actions when the create action was called, we could do the following:
class ProductsController < ActionController
before_filter :authenticate, :only => [ :edit, :create ]
caches_page :list
caches_action :edit
cache_sweeper :store_sweeper, :only => [ :create ]
def list; end
def create
expire_page :action => :list
expire_action :action => :edit
end
def edit; end
end
source rails guide

Trouble on caching an action when I rewrite a URL

I am using Ruby on Rails 3 and I have an issue on caching when I rewrite a URL in the model using the to_param method.
In my User model I have:
class User < ActiveRecord::Base
def to_param # Rewrites URL
"#{self.id}-#{self.name}-#{self.surname}"
end
...
end
In the User controller I have:
class UsersController < ApplicationController
caches_action :show
def show
...
end
end
In the Users sweeper I have:
class UsersSweeper < ActionController::Caching::Sweeper
observe User
def after_save(user)
clear_users_cache(user)
end
def after_destroy(user)
clear_users_cache(user)
end
def clear_users_cache(user)
expire_action :controller => :users, :action => :show, :id => user
end
end
Now, when I browse the user show page in the log file I get:
Write fragment views/<my_site_name>/users/2-Test_name-Test_surname (0.3ms)
When I expire the cache after a change the name or surname in the log file I get
Expire fragment views/<my_site_name>/users/2-New_test_name-New_test_surname (0.3ms)
So, since the data is changed, it doesn't expire the cache because Rails try to expire 2-New_test_name-New_test_surname and not 2-Test_name-Test_surname.
How can I "easly" handle the Rails caching behavior to make it to work?
P.S.: Of course if I don't use the to_param method, it works as well.
UPDATED
I can do something like this
caches_action :show, :cache_path => Proc.new { |c| 'users/' + c.params[:id].split('-').first }
but I don't think that is a good way to solve things...
Try using a custom path:
You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
caches_action :show, :cache_path => { :project => 1 }
Obviously customize to suit your needs. See the API for more info.

Can Rails sweepers work across different controllers?

I have action caching working on my Sites index, and set up a SiteSweeper that works fine:
# app/controllers/admin/sites_controller.rb
class Admin::SitesController < Admin::BaseController
cache_sweeper :site_sweeper, :only => [:create, :update, :destroy]
caches_action :index, :cache_path => '/admin/sites'
...
# app/sweepers/site_sweeper.rb
class SiteSweeper < ActionController::Caching::Sweeper
observe Site
def after_save(site)
expire_cache(site)
end
def after_destroy(site)
expire_cache(site)
end
def expire_cache(site)
expire_action '/admin/sites'
end
end
But I also want to expire /admin/sites whenever any Publishers are saved or destroyed. Is it possible to have a PublisherSweeper expire the Sites index with something like this?
# app/sweepers/publisher_sweeper.rb
class PublisherSweeper < ActionController::Caching::Sweeper
observe Publisher
def after_save(publisher)
expire_cache(publisher)
end
def after_destroy(publisher)
expire_cache(publisher)
end
def expire_cache(publisher)
expire_action '/admin/sites'
end
end
I know I can just call expire_action '/admin/sites' within the various Publisher actions. I'm just wondering if sweepers have this capability (to keep my controllers a bit cleaner).
One sweeper can observe many Models, and any controller can have multiple sweepers.
I think you should change your logic to use something like that:
class SiteSweeper < ActionController::Caching::Sweeper
observe Site, Publisher
(…)
end
On PublishersController
cache_sweeper :site_sweeper, :admin_sweeper
So you don't repeat the logic of cleaning the /admin/site. Call it AdminSweeper, so when something goes wrong you know the only one place that expired the "/admin/sites" action.

Resources