Instance variable in controller with Ruby On Rails - ruby-on-rails

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).

Related

How does cancancan set the model instance, and how can I inspect it?

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

Is there a better alternative to using global variables in a Rails controller with partials?

I've heard that it's considered bad practice in Ruby to use global variables beginning with a dollar sign. Is this also true for Rails controllers?
For example, I have a web app that uses a series of partial views that render in successive stages. The user input from the first stage gets taken from the param and put into a global variable so that it is accessible to each subsequent method. Those later stages need to easily access the selections the user made in the earlier stages.
routes.rb
post 'stage_one_form' => 'myexample#stage_two_form'
post 'stage_two_form' => 'myexample#stage_three_form'
post 'stage_three_form' => 'myexample#stage_four_form'
myexample_controller.rb
def stage_two_form
$stage_one_form_input = params[:stage_one_form_input]
end
...
def stage_four_form
#stage_four_displayed_info = $stage_one_form_input + "some other stuff"
end
This is just a dummy example but it seems a lot more graceful to use global variables here than my original approach, which was to pass the information back and forth from the client to the server in each stage, by using hidden fields.
Are global variables appropriate, or is there a better way?
If you want store the input from the first stage and use on stage two, you are doing a kind o wizard. You consider using the session or something more robust to store the information, not configuration or models as stated by #NickM.
For more info:
Rails Multi-Step Form without Wizard Gem
http://railscasts.com/episodes/217-multistep-forms
Additional info...
What you have done here with these global variables will not work in a production deployment where you're using an application server. In those environments you need multiple processes (or threads) so that more than one visitor to your site can be served at the same time.
With both of these you will have two problems:
Setting of the variable for one visitor will affect the experience of the next visitor (even if it's a different person) to be served by that process/thread.
A related problem is that a single visitor is not at all guaranteed to be served by the same process on their next request, so the process/thread that serves their second request is probably not going to have the global variable set from their first request.
In summary, chaos, use session - that's precisely what it's for.
You can throw them in config/application.rb, or your config.yml file.
config.your_variable = 'something'
Then call it from inside your app
<%= Rails.configuration.your_variable %>
Or you could throw it in a controller:
class Foo
MY_VARIABLE = 'something'
end
And then call it in your view:
<%= Foo::MY_VARIABLE %>
Or you could throw it in a method in your controller and define a helper:
class FooController
def my_variable
'something'
end
helper_method :my_variable
end
More info about config.yml here: Best way to create custom config options for my Rails app?
..but if you need to access one variable in various stages, you might want to dump it in a session variable in your controller:
session[:stage] = 'something'
and access it later using session[:stage]
Then you can clear it when the process starts again:
session[:stage] = nil

Override redirect_to in rails

I use an engine in my rails app that logins the user and redirects to a service param (it's a CAS engine). However, from the host app I want to redirect the user (after he/she has logged in) in a different location sometimes depending on the params. At the moment I can't get it work because rails permits only 1 call of redirect_to/render. The engine inherits from my rails app ApplicationController.
How can I override/redefine redirect_to to call it multiple times?
The problem might be solved in other ways but I really don't want them. I have tried some of them but none can compete with the simplicity of just letting the last defined redirect_to take action.
I'm only interested in solutions that involve redefining redirect_to so that I can invoke it multiple times.
Of course you can "override" it. You can redefine any method in any object at any point in Ruby. However, this is a terrible idea. You should fix your problem, which is that you're calling redirect_to twice, instead of hacking apart Rails in order to allow your problem to continue.
If you're still set on "fixing" this the wrong way, find the source code (this was trivially easy to do), copy it into an initializer/library file of your own, and make the modifications.
module ActionController::Redirecting
def redirect_to(options = {}, response_status = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(options)
self.response_body = "<html><body>You are being redirected.</body></html>"
end
end
If you really want to do this, despite being forewarned that it is the wrong solution to your problem and that you're fundamentally altering behavior of Rails that other things may depend on, comment out the line that raises a DoubleRenderError.
It seems it was much easier than I thought. All you need to do is to explicitly modify the response object.
Thus you could declare the following function in ApplicationController:
def re_redirect_to(location, status = 303)
response.location = location
response.status = status
end
That's it basically. Elegant and simple.
As I said in the first post:
I use an engine in my rails app that logins the user and redirects to a service param (it's a CAS engine). However, from the host app I want to redirect the user (after he/she has logged in) in a different location sometimes depending on the params. At the moment I can't get it work because rails permits only 1 call of redirect_to/render. The engine inherits from my rails app ApplicationController.
So basically I had no other option than override the engine's redirect_to in an after_action in ApplicationController. I believe it's much better and more maintainable solution than modifying the engine's source code.
I would like to note here that it's absolutely good to follow the conventions. Definitely calling redirect_to more than once should be avoided in 99% cases. But it's good to know that there is a way to deal with that other 1%. Conventions are just conventions.
This worked for me, in the application controller, override redirect, do your thing, then call super:
class ApplicationControler < ... #depends on your rails version
def redirect_to(options = {}, response_status = {})
# do your custom thing here
super # and call the default rails redirect
Hope this helps,
Kevin

How Would I Load Config From Database And Only Refresh When App Restarted Or By Touching URL

Ok, I have a Rails 3 application and I am using CouchDB as my primary database to take advantage of it's replication capabilities.
Anyway, what I want to do is store some configuration type stuff in 1 document in the database and load the values of this configuration file one time when the app starts up in production and reload ONLY if the user goes to the admin panel and explicitly requests it to happen. I was thinking by touching a URL to clear the loaded config or something.
My thought was that I would just create a before_filter in application_controller, but since I am new to rails, I didn't know if this was the proper way to do this.
before_filter :get_config
private
def get_config
#config = Config.get('_id')
end
Clearly this would run every request, which I don't want or need. Is there a way to save the config output so I don't have to fetch it every single request, or is there a better way to do this.
Thanks in advance.
Actually I am writing an article about the proper way of using global variables in rails. This seems to be the case to introduce global variables, as their values are shared across different users.
In your before_filter, try this:
def get_config
$config ||= Config.get('_id')
end
This would call Config.get('_id') only if $config is false or nil. Otherwise, $config wiil remain unchanged.
The tricky part is global variables (starting with a $ sign) alive in the whole application. So $config is available everywhere (and that would be a problem for careless design!)
Another point is, as you said you are new to rails, I do suggest you to read more about global variables before you use it and DO NOT ADDICT to it.

How do I store an instance variable across multiple actions in a controller?

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.

Resources