Rails 4, actions caching, why actions still runs, even when cached - ruby-on-rails

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.

Related

Common method to be called for all CREATE, UPDATE and DESTROY actions for all controllers

I am trying to create database logs for all CRUD actions. I know I can go to each controller action and insert the method to get it working. Is there a way to do this more elegantly so the methods get called for all the controllers before a CRUD action takes place. These are the methods I am using:
To create transaction
TransactionLog.create(:contact_id => contact_id) #Create the transaction
To create the changelog
def self.start_logging(current_user, data, action, new_content ={}, old_content ={}, transaction_log_id)
#log = ChangeLog.new(:table => data, :action => action, :new_content => new_content.to_json, :old_content => old_content.to_json, :transaction_log_id => transaction_log_id)
#log.save
end
Try using Filters (Refer Section 8 here : http://guides.rubyonrails.org/action_controller_overview.html)
The before_filter idea is the way to go. What's missing in the other answers is how to apply this to all your controllers. The best option is to create a mixin with the code that defines and activates the filter.
module HasCrudFilter
def self.included(base)
base.class_eval do
before_filter :my_filter, only: [:create, :update, :destroy]
def my_filter
# Your code here...
end
end
end
end
Then in your controllers
class MyModelController < ApplicationController
include HasCrudFilter
end
you can do
before_filter :your_method, only: ['create', 'update' 'destroy']
def your_method
#do the things
end

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.

Generics Example Question

I am fairly new to Ruby on Rails and as a C# developer, when I want to re-use code (for a repository class), I could put it into a base class of type <T> to be able to do something like this:
public virtual IEnumerable<T> GetAll()
{
return Context<T>.GetAll();
}
If I need to do any custom logic, I could, of course, override the method in my 'User' repository.
In Ruby, I am familiar that you can do this:
class UsersController < ApplicationController
This will allow access to all methods in ApplicationController and it's parent classes. When using scaffolding, it generates the following method in each of my child classes:
def index
#users = User.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #users }
end
end
What I end up with is 10 classes that have the same method, but the only difference is 'User.all', 'Post.all', etc.
How would I make this method generic so I can put it in my ApplicationController class?
Thanks for any assistance you can provide to a Ruby on Rails newbie.
The first thing to realize about the scaffolding code is that it can be abreviated, as such:
def index
#users = User.all
end
unless you intend to deliver the view in another format, like json, html, pdf, the respond_to block is unnecessary. If you still feel the need to dry up this method, you could do something like
# app/controllers/concerns/autoload_records.rb
module AutoloadRecords
included do
before_action :load_records, only: :index
before_action :load_record, only: [:create, :show, :edit, :update, :destroy]
end
private
def load_records
#records = model_class.all
end
def load_record
#record = model_class.find(params[:id])
end
def model_class
klass = self.class.to_s[/\A(\w+)sController\Z/,1] #=> get the name of the class from the controller Constant
Object.const_get(klass)
end
end
and write your controller like
class UsersController < ApplicationController
include AutoloadRecords
def index
#records # => #<ActiveRecord::Relation[...]>
end
def show
#record # => #<User ...>
end
def non_rest_action
#record # => nil
#records # => nil
end
end
Rather than doing an eval where you really don't want to be doing one. Check out Jose Valim's Inherited Resources gem. It provides the standard CRUD methods for all of your controllers and is quite sophisticated. It is also thoroughly tested so you don't have to worry about making sure your generic code operates as expected in all cases.
For details on how to use it see the GitHub page linked.
Maybe a simple solution could be to rely on mixins.
You define a module,
module MyModule
def my_index(klass)
#elements = klass.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #elements }
end
end
end
Then, you have in your controller,
include MyModule
def index
my_index(User)
end
Of course, you need to use #elements in your views. If you want a different variable name in each view you can do
def my_index(klass, var_name)
self.instance_variable_set(var_name, klass.all)
...
end
There are several rails plugins that help to reduce this kind of duplication. This one was covered in railscast episode 230.
https://github.com/josevalim/inherited_resources
Based on my experience, you rarely end up with 10 index action looking like #user = User.all. If you know in advance that some actions between different models will be identical - well then may be it makes sense to extract common logic. But then again may be these models are somehow connected? I wouldn't say in advance that Post and User will have identical index actions.
For a short method like this I wouldn't try to eleminate repetition because you may end up losing readability.

How can I make a before_filter function behave differently according to the action?

I'm trying to make my authentication/permissions code a bit more succinct.
Currently I have this:
def index
require_role "normal" do
#projects = Project.all
respond_to do |format|
format.html
end
end
end
where require_role handles checking permissions and redirecting to an error page if you try to do something you shouldn't.
I'd like to be able to just put something like this at the top of each controller:
require_role "admin", [:delete]
require_role "normal", [:edit, :new, :create]
require_role "guest", [:show, :index]
defined something like:
def self.require_perm( role_name, actions )
before_filter :require_perm_admin, :only => actions
end
The only problem is that I have to hard-code the name of the method, require_perm_admin. That means, if I ever add new roles, I'd have to define a method for each one.
Is it possible to add dynamically named methods to a class? e.g. "check_role_admin", "check_role_guest" etc.
Otherwise, can I tell what action is about to be invoked from inside my before_filter function?
If all you want is the current action name, you can call action_name in the before_filter. I'll leave the rest up to you if that's all you think you need :)
Why not do something like
def self.require_perm( role_name, actions )
before_filter "require_perm_#{role_name}".to_sym, :only => actions
end

Passing arguments to filters - best practices

What better ways to pass arguments to filters in Rails controllers?
EDIT: The filter has a different behavior depending on the parameters passed to it, or depends on the parameters to perform its action.
I have an example in my app, where a filter determines how the data is sorted. This filter has a klass param and calls klass.set_filter(param[:order]) to determine :order in the search.
You have to use procs for this.
class FooController < ApplicationController
before_filter { |controller| controller.send(:generic_filter, "XYZ") },
:only => :edit
before_filter { |controller| controller.send(:generic_filter, "ABC") },
:only => :new
private
def generic_filter type
end
end
Edit
One more way to pass the parameter is to override the call method of ActionController::Filters::BeforeFilter.
class ActionController::Filters::BeforeFilter
def call(controller, &block)
super controller, *(options[:para] || []), block
if controller.__send__(:performed?)
controller.__send__(:halt_filter_chain, method, :rendered_or_redirected)
end
end
end
Now you can change your before_filter specification as follows
class FooController < ApplicationController
# calls the generic_filter with param1= "foo"
before_filter :generic_filter, :para => "foo", :only => :new
# calls the generic_filter with param1= "foo" and param2="tan"
before_filter :generic_filter, :para => ["foo", "tan"], , :only => :edit
private
def generic_filter para1, para2="bar"
end
end
I -think- you are looking for the use of successive named_scope filters, but I am not sure. We need more information, if that's not what you need.

Resources