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.
Related
Am new to rails I want to perform a get request in different urls
such as
http://website.com/api/example1
http://website.com/api/example2
http://website.com/api/example3
http://website.com/api/example4
And how can i set session data to be accessed across all controllers in the app
You can simply set your session value in your code as so:
session[:some_value] = 'This is a session value'
What you do not want to do is store large amounts of data into your session. This will have huge performance implications. If you need to store large amounts of data, I would suggest a model to store it in within the database.
Although, I would question why you are setting things in the session for URL access?
http://guides.rubyonrails.org/security.html#sessions
You can add something like following at your routes.rb:
scope "api",:module => "api" do
get 'example1' => 'exact_data#example1', :as => 'api_example1'
get 'example2' => 'exact_data#example2', :as => 'api_example2'
get 'example3' => 'exact_data#example3', :as => 'api_example3'
end
So your controller should have:
class Api::ExactDataController < ApplicationController
def example1
#...
end
def example2
#...
end
def example3
#...
end
end
If you have common session logic that you want to access and use from all controllers then you can put them at your application_controller.rb as by default it is parent of all other controllers that you create.
For example if you put a current_user method at ApplicationController then you would have current_user method in all your controllers:
class ApplicationController < ActionController::Base
protect_from_forgery
private
def current_user
#current_user ||= User.find_by_username(session[:username]) if session[:username]
end
end
I have two implementation of one simple controller.
In first implementation every thing works fine, action show executing only when cache missing.
But I understand that set object #page in Proc of cache action it is a bad idea.
That's why I have second implementation, which looks much better.
It works too and returns cached view.
BUT, I can't understand why when I use before_filter, the action show is still executing even when cache hit. In log I see current time.
Could you explain me why?
please.
Implementation 1
class Frontend::StaticPagesController < Frontend::FrontendController
caches_action :show, :cache_path => Proc.new {
#page = StaticPage.find_in_cache(params[:permalink])
{key: "#{#page.cache_key}-#{I18n.locale}"}
}
def show
logger.debug Time.now.to_s.yellow
end
end
Implementation 2
class Frontend::StaticPagesController < Frontend::FrontendController
before_filter :set_page, :show
caches_action :show, :cache_path => Proc.new {
{key: "#{#page.cache_key}-#{I18n.locale}"}
}
def show
logger.debug Time.now.to_s.yellow
end
def set_page
#page = StaticPage.find_in_cache(params[:permalink])
end
end
P.S. Rails '4.2.3'
This line
before_filter :set_page, :show
Defines :show as a filter. This is why it runs.
My guess is that you want to define the :set_page filter to run only for show action. In this is indeed your intention, use:
before_filter :set_page, only: :show
PS: _filter filters are deprecated. Use _action filters instead, like before_action.
How can I modify load and authorize resources to load resource using different id. for ex.
my routes is
http://localhost:3000/organization/user/12/event/20/edit
and in my event controller I am accessing event using :event_id and user using :id
Now in my event controller how can I modify load_and_authorize_resource use :event_id instead of :id for loading an event
The resource will only be loaded into an instance variable if it hasn't been already. This allows you to easily override how the loading happens in a separate before_filter.
class EventsController < ApplicationController
before_filter :find_event
load_and_authorize_resource
private
def find_event
#event = Event.find(params[:event_id])
end
end
Read more in the docs: https://github.com/CanCanCommunity/cancancan/wiki/Authorizing-controller-actions
CanCan supports this through the id_param option:
From the documentation:
# [:+id_param+]
# Find using a param key other than :id. For example:
#
# load_resource :id_param => :url # will use find(params[:url])
Therefore you can do the following in your situation:
load_and_authorize_resource :id_param => :event_id
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
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.