I have an offline rake job that updates my models. When that happens, I want to expire the :show action for that model.
# in lib/models/my_model.rb
after_update :expire_cache
def expire_cache
expire_action :controller => :my_models, :action => :show, :id => self
end
This doesn't work because expire_action isn't available in the model. Calling ActionController.new.expire_action gives me a lot of weird route issues, which is reasonable since none of the route logic is hooked up.
I think the common way to expire_action is with a sweeper, but that doesn't work because my model is not updated through controller actions.
NOTE: I feel like I may be using caching the wrong way since I can't find an answer to this anywhere.
You're looking for an ActionController Sweeper. You can find the official Rails documentation on how to implement them here, but likely you want something like this:
class MyModelSweeper < ActionController::Caching::Sweeper
observe MyModel
def after_update(my_model)
expire_action :controller => :my_models, :action => :show, :id => my_model
end
end
Related
Having a controller handling rendering of large XML feeds
module Spree
class FeedsController < Spree::StoreController
...
caches_action :products_out
cache_sweeper FeedSweeper
# XML feed in format of `xxxxxxx.com'
def products_out
#products = Product.all
respond_to do |format|
format.xml
end
end
end
Bellow is the corresponding sweeper's sublass:
module Spree
class FeedSweeper< ActionController::Caching::Sweeper
observe Product
def after_update(product)
# cache_configured? is nil, #controller is nil here, why ?
expire_action(:controller => :feeds,
:action => :products_out,
:format => :xml)
end
end
Above Spree::FeedSweeper is called when Spree::Product gets updated, however it seems expire_action silently dies and cache won't get invalidated.
Can somebody explain the issue ? Even better suggest some solution ?
Thanks.
Which Rails version are you using? expire_action seems to be deprecated after Rails 3.2.14.
Maybe you can try to find out the key then directly clear it with Rails.cache.delete(key).
We use dalli gem w/memcached. The following code caches Foo objects paginated across multiple pages. We are able to cache Foo(s) when we are in a certain page (say 2 or 10 or 15). But when I modify a Foo in page 15 (say Foo-150), we clear the cache for all the objects using the method in FooSweeper. after_save method is being called when the above
action happens, but the cache is not getting cleared for all pages and reflects the older values for the requested page.
Is there any mistake in the code snippet given below.
My controller looks like this..
class FooController
...
caches_action :index, cache_path: proc { |c| c.params.except(:_).merge(format: request.format) }
...
My sweeper code looks like this..
class FooSweeper < ActionController::Caching::Sweeper
observe :foo
def after_save(foo)
expire_cache(foo)
end
def before_destroy(foo)
expire_cache(foo)
end
def expire_cache(foo)
expire_action(:controller => 'foos', :action => 'index')
expire_action(:controller => 'foos', :action => 'index', :format => 'text/html')
end
end
Your controller is calles Foo, but you call controller: 'foos' from your sweeper. It could be the reason why your cache is not properly cleared.
You can fix this by changing foos to foo.
I'm writing controller tests in Rails and RSpec, and it seems from reading the source code of ActionController::TestCase that it's not possible to pass arbitrary query parameters to the controller -- only routing parameters.
To work around this limitation, I am currently using with_routing:
with_routing do |routes|
# this nonsense is necessary because
# Rails controller testing does not
# pass on query params, only routing params
routes.draw do
get '/users/confirmation/:confirmation_token' => 'user_confirmations#show'
root :to => 'root#index'
end
get :show, 'confirmation_token' => CONFIRMATION_TOKEN
end
As you may be able to guess, I am testing a custom Confirmations controller for Devise. This means I am jacking into an existing API and do not have the option to change how the real mapping in config/routes.rb is done.
Is there a neater way to do this? A supported way for get to pass query parameters?
EDIT: There is something else going on. I created a minimal example in https://github.com/clacke/so_13866283 :
spec/controllers/receive_query_param_controller_spec.rb
describe ReceiveQueryParamController do
describe '#please' do
it 'receives query param, sets #my_param' do
get :please, :my_param => 'test_value'
assigns(:my_param).should eq 'test_value'
end
end
end
app/controllers/receive_query_param_controller.rb
class ReceiveQueryParamController < ApplicationController
def please
#my_param = params[:my_param]
end
end
config/routes.rb
So13866283::Application.routes.draw do
get '/receive_query_param/please' => 'receive_query_param#please'
end
This test passes, so I suppose it is Devise that does something funky with the routing.
EDIT:
Pinned down where in Devise routes are defined, and updated my example app to match it.
So13866283::Application.routes.draw do
resource :receive_query_param, :only => [:show],
:controller => "receive_query_param"
end
... and spec and controller updated accordingly to use #show. The test still passes, i.e. params[:my_param] is populated by get :show, :my_param => 'blah'. So, still a mystery why this does not happen in my real app.
Controller tests don't route. You are unit-testing the controller--routing is outside its scope.
A typical controller spec example tests an action:
describe MyController do
it "is successful" do
get :index
response.status.should == 200
end
end
You set up the test context by passing parameters to get, e.g.:
get :show, :id => 1
You can pass query parameters in that hash.
If you do want to test routing, you can write routing specs, or request (integration) specs.
Are you sure there isn't something else going on? I have a Rails 3.0.x project and am passing parameters.. well.. this is a post.. maybe it's different for get, but that seems odd..
before { post :contact_us, :contact_us => {:email => 'joe#example.com',
:category => 'Category', :subject => 'Subject', :message => 'Message'} }
The above is definitely being used in my controller in the params object.
I am doing this now:
#request.env['QUERY_STRING'] = "confirmation_token=" # otherwise it's ignored
get :show, :confirmation_token => CONFIRMATION_TOKEN
... but it looks hacky.
If someone could show me a neat and official way to do this, I would be delighted. Judging from what I've seen in the source code of #get and everything it calls, there doesn't seem to be any other way, but I'm hoping I overlooked something.
I have a controller action that has page caching, and I made a sweeper that calls expire_page with the controller and the action specified...
The controller action renders a js.erb template, so I am trying to ensure that expire_page deletes the .js file in public/javascripts, which it is not doing.
class JavascriptsController < ApplicationController
caches_page :lol
def lol
#lol = Lol.all
end
end
class LolSweeper < ActionController::Caching::Sweeper
observe Lol
def after_create(lol)
puts "lol!!!!!!!"
expire_page(:controller => "javascripts", :action => "lol", :format => 'js')
end
end
... So, I visit javascripts/lol.js and I get my template rendered.. I verified that public/javascripts/lol.js exists... I then create a new Lol record, and I see "lol!!!!!!!!!" meaning the after_create observer method is triggered, but expire_page is doing nothing...
According to RailsGuides: 'Page caching ignores all parameters.' I think I had similar problem while working on cashing .xml responses: I would write the cache for /lol.xml, but was trying to expire cache for /lol (write and expire operations can be seen in the server log). The way I made it work: I made the cache "format-agnostic" like this:
cashes_page :lol, :cache_path => Proc.new { |controller| controller.params.delete_if {|k,v| k == "format"} }
and expire in the sweeper like this:
expire_page(:controller => "javascripts", :action => "lol")
It solved my problem. Also, as a note, shouldn't your lol action be called lols? Good luck.
I tried solution form Simon's answer and it didn't work for me. The solution that worked was:
expire_page('javascripts/lol.js')
We have an app with an extensive admin section. We got a little trigger happy with features (as you do) and are looking for some quick and easy way to monitor "who uses what".
Ideally a simple gem that will allow us to track controller/actions on a per user basis to build up a picture of the features that are used and those that are not.
Anything out there that you'd recommend..
Thanks
Dom
I don't know that there's a popular gem or plugin for this; in the past, I've implemented this sort of auditing as a before_filter in ApplicationController:
from memory:
class ApplicationController < ActionController::Base
before_filter :audit_events
# ...
protected
def audit_events
local_params = params.clone
controller = local_params.delete(:controller)
action = local_params.delete(:action)
Audit.create(
:user => current_user,
:controller => controller,
:action => action,
:params => local_params
)
end
end
This assumes that you're using something like restful_authentication to get current user, of course.
EDIT: Depending on how your associations are set up, you'd do even better to replace the Audit.create bit with this:
current_user.audits.create({
:controller => controller,
:action => action,
:params => local_params
})
Scoping creations via ActiveRecord assoiations == best practice