I have this in my controller:
def frontpage
#sections = Section.all
end
In production environment, the query seems to be cached between requests. Until I restart an app, #sections is always the same. Why and how do I fix this? I should also mention, I use Postgres in production, could it be the reason?
I believe that in your production environment, you have enabled caching somewhere. While I'm not able to tell you why and where, I can give you a solution:
def frontpage
uncached do
#sections = find(:all)
end
end
At least it should do the trick
e: The benefit of this, is that it only affects the code inside the block. Everything else should still get cached.
Queries are not cached between requests, but actions could be. Do you have cache_action or other caching mechanism defined in your controller?
EDIT: to figure out if it's your action that's being cached, put this in your action:
def frontpage
return render :text => Time.now.to_s
...
end
And see it it changes.
Have a look at this answer from a related question. If your configuration is set up to cache, then you should switch that off (in development at least.) Otherwise, if you want to invalidate the cache of a specific page, you can look into defining a method to do so in your controller:
private
def invalidate_cache
Rails.cache.delete("/sections")
end
You would need to specify when that is called, but it would do the trick.
Related
Alright, I have a basic CMS in the works & would liked to try my hand at caching what are essentially a set of dynamically generated navigation links. In my pages_controller, I have the following action to toggle a page's visibility, as well as the query to create an #nav_links variable based on those pages that are visible
before_filter :nav_links, only: [:index, :new, :show, :edit]
def toggle
#visibility = #page.visible? ? false : true
unless #page.update_attribute(:visible, #visibility)
flash[:alert] = "Uh oh! Looks like something went wrong."
end
expire_fragment "nav"
end
def nav_links
#nav_links = {}
groups = PageGroup.order("id")
groups.each do |group|
if group.visible?
#nav_links[group.name.to_sym] = group.pages.where(visible: true)
end
end
end
In my view, I've wrapped the section of the page pertaining to #nav_links with <% cache "nav" do %>...<% end %>. So my question, what happens exactly when one of these actions is called?
Does Rails still execute the before_filter, query the database, & re-populate the #nav_links hash per visit? Or does it only get called if the view can't find a copy of #nav_links in the cache? If it doesn't execute each time, does Rails use SQL caching by default to use the same results unless a record was changed?
Do I need to modify the nav_links method as well or will the cache work as expected with only the changes to the view & action that updates a page's visibility?
Thanks in advanced; I'm new at Rails so I hope my questions make sense.
If you have cache "nav" wrapped around some template code, that code will be run once, the results cached, and the cache will be reused until the template changes. Your cache invocation doesn't reference any data objects, so there is no application state that can change which would result in the cache being busted.
Also, I'm fairly certain that expire_fragment doesn't work with Rails 4 cache digests.
I try to optimise a Rails app with big load that currently hit the databsae on every request. I try now to optimise this by saving some info on the session so I don't need to go the database every time. I'm currently doing something like this.
def set_audience
if current_user
session[:audience] ||= current_user.audience
else
session[:audience] ||= 'general'
end
end
And then calling session[:audience] anywhere on my controller and views. Seems fine except that I'm seeing on the New Relic logs that sometimes the session is not set and therefore the app get a nasty nil.
Im thinking better I should use instance variables, maybe more something like this.
def set_audience
if current_user
session[:audience] ||= current_user.audience
end
#audience = session[:audience]
#audience = 'general' if #audience.empty?
end
And then calling #audience in my app.
Is this correct? I would like to make sure I'm used the preferred approach to this.
I think the standard approach here would be to use a helper method on ApplicationContoller:
class ApplicationController < ActionController::Base
private
def current_audience
#current_audience ||= current_user.audience
end
helper_method :current_audience
end
This will work pretty much exactly like the current_user helper method in your controllers and views. Depending on the specifics of your application, you may want to add some more robust nil handling, but this is the basic idea.
UPDATED:
I am setting default scope for some models in a runtime which seems working locally in my development env and my code is given below.
SET_OF_MODELS = [Event, Group, User]
#account = Account.find_by_subdomain(account_subdomain)
SET_OF_MODELS.each { |m| m.set_default_scope(#account.id) }
def set_default_scope(account_id)
default_scope :conditions=> { :account_id => account_id }
end
If I execute this code in ruby console with say #account1, User.first returns #account1 user whereas if I repeat the code with #account2 then User.first returns #account1 user instead of #account2. And this problem is not revealed while running app in local server but in staging server.
My guess is towards their states if they are really cached but not sure. Can someone explain in depth.
Thanks in advance
default_scope will save state in its class. It's harmful in concurrent environment because it leads to race condition. So you must isolate scope state between requests.
You can use around_filter
class ApplicationController < ActionController::Base
around_filter :set_default_scope
def set_default_scope
#account = Account.find_by_subdomain(account_subdomain)
opts = :condition => {:account_id => #account.id}
Event.send(:with_scope, opts) do
Group.send(:with_scope, opts) do
User.send(:with_scope, opts) do
yield
end
end
end
end
end
You can refactor .send(:with_scope, opts) to a class method like with_account_scope(account_id)
Development differs from production. In production all classes are loaded once and cached, so you can't redefine the default scopes on each request.
In development the classes are loaded on each request, to allow easy development: each change you do in the code is visible/active on the next request.
If you really want to, you can disable this behaviour in production. This will make your complete site slower, but maybe that is not really an issue. To turn this off, you have edit your config/environments/production.rb, find the line containing
config.cache_classes = true
and switch that to false.
Hope this helps.
There is nothing wrong with the above code but the problem was with the server used i.e. thin server. It worked perfectly after replacing thin with mongrel. I think thin wasn't allowing to execute set_default_scope more than once except after loading the application.
I'm working on a Rails app, where I'm using page caching to store static html output. The caching works fine. I'm having trouble expiring the caches, though.
I believe my problem is, in part, because I'm not expiring the cache from my controller. All of the actions necessary for this are being handled within the model. This seems like it should be doable, but all of the references to Model-based cache expiration that I'm finding seem to be out of date, or are otherwise not working.
In my environment.rb file, I'm calling
config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
And I have, in the /sweepers folder, a LinkSweeper file:
class LinkSweeper < ActionController::Caching::Sweeper
observe Link
def after_update(link)
clear_links_cache(link)
end
def clear_links_cache(link)
# expire_page :controller => 'links', :action => 'show', :md5 => link.md5
expire_page '/l/'+ link.md5 + '.html'
end
end
So ... why isn't it deleting the cached page when I update the model? (Process: using script/console, I'm selecting items from the database and saving them, but their corresponding pages aren't deleting from the cache), and I'm also calling the specific method in the Link model that would normally invoke the sweeper. Neither works.
If it matters, the cached file is an md5 hash off a key value in the Links table. The cached page is getting stored as something like /l/45ed4aade64d427...99919cba2bd90f.html.
Essentially, it seems as though the Sweeper isn't actually observing the Link. I also read (here) that it might be possible to simply add the sweeper to config.active_record.observers in environment.rb, but that didn't seem to do it (and I wasn't sure if the load_path of app/sweepers in environment.rb obviated that).
So I've tried a number of different approaches, to see what works, and what doesn't.
Again, to summarize the situation: My goal is to expire cached pages when an object updates, but to expire them without relying on a Controller action. Conventional sweepers use a line in the controller to notify the sweeper that it needs to function. In this case, I can't use a line in the controller, as the update is happening within the model. Normal sweeper tutorials aren't working, as they presume that your main interaction with the database object is through the controller.
If, in reading this, you see a way to tighten up my code, please comment and let me know.
First, let's look at the things that DO work, in case you're stuck on this, too, and need help.
Of all the things I tried, the only thing that really seemed to work was to declare an after_update command in the Observer for the model. In that command, I used the explicit command for the expire_page action, and included a path that had been declared in routes.rb.
So. This works:
In config/routes.rb:
map.link 'l/:md5.:format', :controller => 'links', :action => 'show'
In app/models/link_observer.rb:
def after_update(link)
ActionController::Base.expire_page(app.link_path(:md5 => link.md5))
end
Note that that "md5" is specific to my app. You might want to use :id or some other unique identifier.
I also found that declaring that ActionController::Base... line from the method in the model that's doing the updating worked. That is, within Link.rb, in the method that's actually updating the database, if I just stuck that whole line in, it worked. But since I might want to expire that page cache on other methods in the future, I'd rather have it extracted into the Observer.
Now, let's look at some things that DID NOT work, in case you're Googling around for this.
Calling "expire_page(...)" within the after_update(link) method within link_observer.rb did not work, as it returned an "undefined method `expire_page'" error
Creating a Sweeper file that observed the Model did not work. I couldn't find any error codes, but it just seemed to not even be aware that it had a job to do. This was after explicitly calling "config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )" within environment.rb. Just in case I fat-fingered something in that code, here it is:
class LinkSweeper < ActionController::Caching::Sweeper
observe Link
def after_update(link)
clear_links_cache(link)
end
def clear_links_cache(link)
# DID NOT WORK expire_page :controller => 'links', :action => 'show', :md5 => link.md5
# DID NOT WORK expire_page '/l/'+ link.md5 + '.html'
# DID NOT WORK ActionController::Base.expire_page(app.link_path(:md5 => link.md5))
end
end
That above example had the link_sweeper.rb file in a directory, /app/sweepers. I also tried putting link_sweeper.rb within the app/models directory, and tried calling it with the config.active_record.observers command in environment.rb:
config.active_record.observers = :link_observer, :link_sweeper
But that didn't work, either.
So, yeah. It's quite possible that one of these methods would work, and that I messed up something in the code. But I think I did everything by the book.
Ultimately, to summarize: Rather than using a Sweeper to expire page caching, you want to set up an after_ callback in the model's Observer. You'll want to use the explicit path to the Base.expire_page method:
def after_update(<model>) # where <model> is the name of the model you're observing
ActionController::Base.expire_page(app.<model>_path(:id => <model>.id)) # where <model> is the name of the model you're observing
end
Hopefully this will help someone else down the road. Again, if you see anywhere in my not-working code where I should have done something differently, please let me know. If you see something in my working code that can be tighter, please let me know that, too.
Just a note: you can use cache_sweeper in ApplicationController.
class ApplicationController < ActionController::Base
cache_sweeper :my_sweeper
end
class MySweeper < ActionController::Caching::Sweeper
observe MyModel
def after_update(my_model)
expire_page(...)
end
end
I was experiencing the same problem when trying to do fragment caching (rails 3). Couldn't get the sweeper to observe, so I settled for the solution to make it an AR Observer as described above and calling ApplicationController.new.expire_fragment(...).
I did get this working. The only slight difference in my setup is that the sweeper is part of a Rails engine; which accounts for slight differences (loading the sweeper file with a require in the engine's init instead of adding it to the load path in environment.rb, etc).
So, the sweeper is loaded in the init.rb of the engine like this:
require File.join(File.dirname(__FILE__), 'app', 'sweepers', cached_category_count_sweeper')
I called it a sweeper because it "sweeps" the cache, but I guess its just an observer on the model:
class CachedCategoryCountSweeper < ActiveRecord::Observer
observe CategoryFeature
def before_save(cf)
expire_cache(cf.category_id_was) if cf.category_id_changed?
end
def after_save(cf)
expire_cache(cf.category_id)
end
def after_destroy(cf)
expire_cache(cf.category_id)
end
def expire_cache(c)
ApplicationController.expire_page("/categories/#{c}/counts.xml") if !c.nil?
end
end
Frankly, I don't like having to hard-code the path, but I tried adding:
include ActionController:UrlWriter
and then using the path method, but it only worked for me in development. It didn't work in production, because my production server uses a relative url root (instead of virtual hosts) and the internal method "page_cache_path" would consistently get the file path wrong so it couldn't expire.
Since this is an observer, I added to the environment.rb:
config.active_record.observers = :cached_category_count_sweeper
Finally the controller that uses the cache (doesn't expire it, that is done through the model observer):
class CachedCategoryCountsController < ApplicationController
caches_page :index
# GET /cached_category_counts.xml
def index
...
end
end
Anyhow, hope this helps.
Andres Montano
I've been able to get it to work, by way of adding
ActionController::Base.expire_page(app.link_path(:md5 => #link.md5))
to the method in the Model itself that's updating the database. This feels somewhat hacky, though, and I'd love to know if anyone can explain why it's not working with the normal sweeper setup, and if there's a more elegant way to handle this.
That snippet of code (apart from customizations I put in for my own app) came from this post on ruby-forum.com.
I wrote a bit about this topic here: Rails Cache Sweeper Confusion. Would love to hear your opinions.
Based on #moiristo and #ZoogieZork 's answers, I am guessing this would work (untested).
class LinkSweeper < ActiveRecord::Observer
include ActionController::Caching::Pages
# or if you want to expire fragments
#include ActionController::Caching::Fragments
observe Link
def after_update(link)
expire_page( ... )
#expire_fragment( ... )
end
end
I'd like to be able to dispatch from one controller action to another conditionally, based on a combination of query parameters and data in the database.
What I have right now is something like:
class OldController < ApplicationController
def old_controller_action
if should_use_new_controller
new_params = params.dup
new_params[:controller] = "new_controller_action"
redirect_to new_params
return
end
# rest of old and busted
end
end
class NewController < ApplicationController
def new_controller_action
# new hotness
end
end
This works just fine, but it issues an HTTP redirect, which is slow. I'd like to be able to do this same thing, but within the same HTTP request.
Is there a clean way to do this?
Edit: The bounty will go to someone who can show me a clean way to do this that leaves the controllers and their actions relatively untouched (other than the redirect code itself).
Instead of calling code across actions, extract the code to lib/ or something, and call that code from both controllers.
# lib/foo.rb
module Foo
def self.bar
# ...
end
end
# posts_controller
def index
Foo.bar
end
# things_controller
def index
Foo.bar
end
Create an instance of the controller class:
#my_other_controller = MyOtherController.new
Then call methods on it:
#my_other_controller.some_method(params[:id])
I prefer the module idea, but this should do the trick.
You can also pass parameters as a whole from another controller:
#my_other_controller.params = params
I suspect you want option 3, but lets go through the some alternatives first
Option 1 - Push the controller selection logic into a helper that inserts the right link into your view. Benifits - controllers remain clean, Cons - if decision logic depending on submitted values this approach won't work. If URL is being called by external websites then this won't work.
Option 2 - Push the logic back into your model. Pro's - keeps controller clean. Cons - doesn't work well if you've got lots of sesson, params or render / redirect_to interaction.
Option 3 - Stay within the same controller. I suspect you are trying to replace some existing functionality with some new functionality, but only in some cases. Pro's - Simple and have access to everything you need. Cons - only works if it makes sense to use the same controller i.e. you're working with the same entity such as user, place or company.
Lets look an an example for option 3. My links controller has totally diferent behavour for admins than other users ...
class LinksController < ApplicationController
#...
def new
#Check params and db values to make a choice here
admin? ? new_admin : new_user
end
#...
private
def new_admin
#All of the good stuff - can use params, flash, etc
render :action => 'new_admin'
end
def new_user
#All of the good stuff - can use params, flash, etc
render :action => 'new_user'
end
end
If two controllers are trying to do the same thing, there's a very good chance this should be in a model. Take a good look at your design and -- I'm sorry I don't know your experience level with MVC -- read up on thin controller techniques:
http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model
http://www.robbyonrails.com/articles/2007/06/19/put-your-controllers-on-a-diet-already
http://andrzejonsoftware.blogspot.com/2008/07/mvc-how-to-write-controllers.html
If the problem is that you need the other controller to do the render, then maybe the route should have pointed there to begin with, and still the skinny controller technique should save the day.
If extracting the common code between controllers into a module doesn't work for you, I would use Rack middleware. I haven't seen code that uses ActiveRecord within middleware but I don't know of any reason why it shouldn't be possible since people have used Redis and the like.
Otherwise I think your only option would be to restart processing of the request with something like (untested, pseudo example):
env['REQUEST_URI'] = new_controller_uri_with_your_params
call(env)
This is similar to how integration tests are implemented. But I don't know if everything from call until you hit a controller is idempotent and safe to rerun like this. You could trace through the source and see. But even if it's ok now, it might break in any future version of rails or rack.
Using middleware would avoid this by letting you intercept the request before it's been run. You should still be able to share code with your rails application by extracting it out into common modules included in both places.
Honestly I think just doing the simple thing of factoring the common controller code is likely cleaner, but it's hard to know without the details of your situation so I thought I'd go ahead and suggest this.
Do this:
class OldController < ApplicationController
def old_controller_action
if should_use_new_controller
new_controller_action
end
# rest of old and busted
end
end
and the new controller
class NewController < OldController
def new_controller_action
# new hotness
end
end