I have Resque queue that processes a particular model to do some post processing. Once this model is updated I would like to expire the cached view action.
I have a Sweeper setup and working, but it only observes changes made to the models in Controller Actions.
I know it is not very MVC to be expiring cached items from my model, but all my post processing logic is contained in my models.
Ideally I would like to not double up on my cache busting code, so if I could get my existing sweeper to watch model changes that would be ideal.
Alternatively I would settle for expriing the action cache from inside my model OR a model observer.
p.s.: I can expire cached fragments from within a model observer I have setup, but not actions.
I'd really like to know what the best practice is here. I'm sure I am not the only one who has this requirment.
Thanks.
I'm wondering if something like this is the way to go:
http://dev.mensfeld.pl/2011/07/rails-wykorzystywanie-sweeperow-poza-kontrolerami-na-samych-modelach/
Since I wasn't able to add my comment on that site you linked to. I'll put it here:
In Rails 3.1.3, if you instantiate the controller and then try calling expire_fragment, you'll get errors about trying to call host on NilClass.
After some experimenting, I remembered that functional tests can instantiate your controller. So I changed the instantiation code to:
#controller ||= ApplicationController.new
if #controller.request.nil?
#controller.request = ActionDispatch::TestRequest.new
end
This seems to work, even in production, even using rails console.
In the end I came up with the following solution:
Added my existing sweeper to the list of object observers in application.rb:
config.active_record.observers = :app_sweeper
Added code to the sweeper methods to instantiate the #controller object if it was missing in the sweeper (which is what happens when coming via a object instead of a controller).
#controller ||= ActionController::Base.new
I could then use the expire_fragment method to expire the cached action, with the following tidbit.
expire_fragment("#{ActionMailer::Base.default_url_options[:host]}/items/#{item.id}")
That said, mj1531's answer might prove to be a nicer solution if it means that I can use the expire_action method instead of faking it with the expire fragment. I will report back when I have test it out and select the best answer.
Related
I noticed users could access an action they shouldn't be able to access.
I debugged in the rails console with something like
user = User.first
physician = Physician.first
ability = Ability.new(user)
ability.can?(:send_message, physician)
# => false
The above says that user can't access the send_message action for that physician, which is the desired behaviour, yet I know they can in the app!
I think this narrows down the cause to a problem with cancancan loading the wrong model instance for some reason. And that's hinted to in the cancan docs too:
Note: this assumes that the model instance is being loaded properly.
But the problem is I'm not sure how to diagnose the problem from here, since the console says it should be working. I don't know how to view the model instance that cancancan has set, and I don't know what else to try.
Any ideas?
Update
I did manage to work around this by using authorize! :send_message, physician in the controller, but since I only stumbled upon this behaviour by chance, I think it's much more important to figure out why the wrong model instance was being loaded (especially so I can see if that was happening elsewhere too).
I figured out why it was probably happening (but I still don't know how to disagnose it)
I think this was happening because I had many custom actions, and some had #physician = Physician.find(current_user.physician.id) (i.e they're the current user), whereas others were more like #physician = Physician.find_by_id(physician_params[:id]). I'm not sure how cancan sets the instance model, but I do know it's not psychic, so it wouldn't know whether to set it to the current user's physician instance, or the physician instance for the physician id passed in.
What remains?
How does cancancan set the model instance for custom methods (I presume it tries something, and if that doesn't work, tries something else, etc etc)?
Small notes that help:
load_and_authorize_resource does attempt to load the model instance for non RESTful actions
Some useful info in the docs
This may have something to do with what I experienced:
When I returned slug it breaks this behaviour and I can edit all pokemons.
Leaving my notes here in case they are helpful to anyone else.
TL;DR, there are a lot of nuanced assumptions cancancan makes, which you won't know about from the outset. I discovered many of them by thoroughly reading the comments in the cancancan readme, code, and defining abilities docs
So here goes..
How cancancan works
if you call authorize! in the controller action itself, cancancan will look for an instance variable in each controller action.
if you instead simply add load_and_authorize_resource at the start of your controller, that will do two things:
Load an instance variable that cancancan thinks should be loaded, and
Checks for authorization on that model instance
Note that for custom actions, load_and_authorize_resource will still try to load a model instance, but how does it know what to load? It doesn't, it guesses, which, for me, I do not like, so be aware of that.
For me, I prefer to do the work of load_and_authorize_resource myself in two separate steps, so I know exactly what's going on.
Ensure #article is generated via a before action for each controller action (or #articles for index action)
Simply have a line at the top of the controller saying load_and_authorize_resource after the before action that sets the model instance
Note that the only difference is now the developer is responsible for loading the right model instance, and cancancan is not trying to guess it. I prefer this approach because it only takes one mistake to accidentally allow access where it shouldn't be granted.
Also remember that load_and_authorize_resource should always go after any before actions that set the model instance variable
Random notes that may also help
The name of the instance variable depends on the action. If we have an articles controller, then:
For the index action, authorize looks for #articles
For all other actions, authorize looks for #article
It then checks to see if the user is allowed access to that resource.
load_and_authorize_resource checks to see if the model instance exists, and if not, creates one. So if you have a before action that creates #article/#articles, then load_and_authorize_resource won't do it for you (i.e. it won't overwrite it), but if you didn't set one, cancan will try to set one. See here for more on that.
An ability rule will override a previous one. (see here for an example)
Just one last thing, never use current_user in ability.rb, it will error silently (!!), so be sure to use user instead :)
Here's what is happening: https://github.com/CanCanCommunity/cancancan/blob/585e5ea54c900c6afd536f143cde962ccdf68607/lib/cancan/controller_additions.rb#L342-L355
# Creates and returns the current user's ability and caches it. If you
# want to override how the Ability is defined then this is the place.
# Just define the method in the controller to change behavior.
#
# def current_ability
# # instead of Ability.new(current_user)
# #current_ability ||= UserAbility.new(current_account)
# end
#
# Notice it is important to cache the ability object so it is not
# recreated every time.
def current_ability
#current_ability ||= ::Ability.new(current_user)
end
When someone is logging into my application, I use:
def create
#user = User.authenticate(params[:email], params[:password])
[...]
end
Ok, then, when someone is logging out:
def destroy
user = User.find_by_id(session[:user_id])
[...]
end
Knowledge
As far as I know, variable scopes work based on a scope, at least on Ruby (on Rails).
Our friend said:
In case of controllers, it present for that HTTP request alone, the object and the instance variables.
Ok. My variable scope created on create method is useless for destroy method, but I was thinking about the subject and the following question appears: There's a way to preserve #user for that controller at all, regardless of the HTTP request?
I mean, # in this case seems useless to me because its not flexible. I don't know, just sounds strange for me I can't reuse it when I want to.
That's how the web works and why http is a 'stateless protocol'. You must understand that you are not starting to run a program and stop it when your user logs out. But you 'restart' the program for every single request. It's a new instance, a new process that knows nothing of the last one and for sure shares no memory with it. Actually the Rails instance that handles the create and the one that handles the destroy could easily run on two physically different servers!
There is no state (but what you put in the session storage or the URL params). # in this case means that your view can use this data (which in the Ruby context means that Rails already is doing some tricks to get it handed over there, since these are two different classes and the view would otherwise not know anything about the controllers instance variables).
Hi I'm a beginner of rails and I'm not good at English. so if there is some total nonsense please understand..
I'm trying to record loading speed and page duration in every pages.
I made a database "pages" and method "savepage" in my "Page" model.
To save in every page I put "savepage" method in application controller.
Page.rb
def self.savepage
.
.
.
end
application_controller.rb
before_filter :dosave
def dosave
Page.savepage
end
these kind of format..
My question is
1. am I doing correct? using before_filter to do save in very first of loading process?
2. to save after loading all the contents in a page what should I use?
3. to save after user leave this page what should I use?
I saw before_destroy and after_filter, but I can't find what it is... what filter means.... what action means destroy....
thank you in advance!
before_filter is the first thing which loads before giving request to controller.But your need is completely different . Fundamentally filter are used boolean checking.If certain method is true,it will run otherwise it may not. This filter are further extended and we put code into that filters.(And Even sometimes it is consider as best practice) .
Now, before_filter :dosave might be right but is it not true way of knowing page(UI) loading process. I suggest you to use javascript call or use some manually created helper methods and place it into view .erb files.
May be this will interest you
https://github.com/grosser/record_activities
Log user activities in ROR
what action means ?
Action Controller is the C in MVC. After routing has determined which controller to use for a request, your controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible.
Source : http://guides.rubyonrails.org/action_controller_overview.html
I highly suggest you to read above documentation. It is very necessary for you and it covers topic which you asked here.`
And one more thing,
what is action destroy ?
This is simply an action method just like new. Since, rails follow Convention over configuration ( and its developer too) so they put code which do some delete destroy or some destruction. This make thing simple,otherwise more configuration will require which is against rails policy.
I have additional variables I add to an authlogic users session like:
session[:current_profile] = extra_id
I currently destroy these on logout in a controller like:
session[:current_profile] = nil
I'd like to clean this up and destroy them in the session model in the after_destroy method like:
def after_destroy
session[:current_profile] = nil
end
This session method doesn't seem to be callable from the models though. Any idea how to destroy a session variable from a model?
Thanks!
You're really not supposed to alter things in the Controller space from the Model space, which is to say, a Model should not be controlling a Controller. Models should be able to run independently of a controller, such as in unit tests where no controller is present.
While you might be able to wrangle this sort of thing with an Observer, I don't know of an easy way to do this. It's probably better to have the controller perform all the required actions directly.
If you put an after_destroy hook like this in you will have some serious side-effects if, for example, a user logged in as an admin destroys another user account and then their session profile suddenly disappears.
Say I want to store some variable in my controller. I want to initialize it in one action, increment it in another, and read it in yet another. Just declaring this variable with #foo doesn't work because #foo dies after the action that created it is rendered.
I do not want this variable to be stored in a model.
Is there a way to preserve this variable besides storing it in a session?
It seems like I've run into this simple problem a few times, and I want to know the best way to go about solving it.
Not really. Each call to a controller action is stateless. Nothing is available after the controller action finishes. A new controller instance is created for each request, and then discarded at the end of the request.
If you don't want to store it in the session, or database model, you don't have many options if you're wanting that variable to be specific to a particular session.
If it is global across all sessions, you could put it in a ##class_variable rather than an #instance_variable, but that can get messy once you start having multiple Rails processes (each which will have their own copy of it), or if you're running in threadsafe mode, you can end up with nasty concurrency bugs.
I guess you could look at something like memcached, but you'd still need to key that to some user_id or other session marker (unless it's global)
I too am wondering why you are against using session? If you don't like working with session directly in your actions, you could emulate a surviving #foo instance variable with filters. Something like this maybe?
class FooController < ApplicationController
before_filter :load_foo
after_filter :save_foo
private
def load_foo
#foo = session[:foo] || 0
end
def save_foo
session[:foo] = #foo
end
end
Your actions will the be able to manipulate the value through the #count instance variable and this will be automatically persisted to session.
You could make use of the built in Rails.cache mechanism to store the value but as mentioned in the first answer you'd have to key it off something like the user_id. This is a nice way to go since you can back it with different storage mechanisms.
Rails.cache.write(:foo)
# in later action
Rails.cache.read(:foo)
One other thing you could look at is the flash hash, which provides a keep method to make the flash value last more than one subsequent request.
So in action 1 you could create the value:
flash[:foo] = some_value
flash.keep(:foo)
In action 2 you can access it, and call keep again if you want it to stay alive for more subsequent actions.
flash[:foo] #use it for something
flash.keep(:foo) # keep it for another request
It's a bit of a tricky thing to do cleanly within the context of http requests.
If it's a simple count or string, I think the best solution is to store it in the session. That way it will be there if you are using multiple web servers.
Why are you against using a session for this?
Don't worry, sessions won't bite.
Also, the session is probably the best way to do this.