Web2py disable session cookies per controller - session-cookies

Is there a way to not save session ids & the session cookie in web2py on a per-controller level? global_settings.web2py_disable_session = True will do it for the whole site, but I want some pages to retain sessions.

If you don't need to use the parameter-based rewrite system, you can disable sessions based on routing using the pattern-based rewrite system. In the routes.py file, you would do something like this:
routes_in = [
('/myapp/default/$anything', '/myapp/default/$anything',
dict(web2py_disable_session=True))
]
The optional third element of a routes_in tuple is a dictionary, which will be used to update request.env. The above will add web2py_disable_session=True to request.env only for routes starting with /myapp/default/ (setting global_settings.web2py_disable_session=True, on the other hand, will add web2py_disable_session=True to request.env for all requests).
Alternatively, you can simply call session.forget(response) in any controller or action that does not need the session (or conditionally in a model file depending on the requested path). If no session cookie or file have yet been created, this will prevent their creation. Although simpler, this method is slightly less efficient than the above, as it will still result in the session initialization code running on every request.
One final alternative would be to create a custom WSGI application function in the WSGI handler file that conditionally adds web2py_disable_session=True to the WSGI environment dictionary depending on the requested route. Then pass the modified environment dictionary to gluon.main.wsgibase.

Related

How is the `flash` in Rails made global to an application?

I'm curious as to how Rails achieves this. I notice that you can access the flash variable globally in an app, but it isn't prefixed with an # or a $.
I can also see that there's a method for accessing the flash and there's also an initializer as well that will set #flash, but how is it that I can call flash as a local variable?
Session
Further to apneadiving's answer, the flash is part of the middleware stack (ActionDispatch::Flash). It's actually a non-persistent session cookie:
--
From the docs:
The flash is a special part of the session which is cleared with each
request. This means that values stored there will only be available in
the next request, which is useful for passing error messages etc.
Much in the same way that params works (on a per request basis), the flash variable is only populated with data from the previous request.
--
Middleware
If you take apneadiving's comment, you'll see that the flash is created through the middleware stack - meaning the local nature of the variable is only set for that particular request (much the same as params). This is why you can access / set a flash message in any controller - because it's defined higher up the "middleware stack" - it provides a scope which appears global
I'm sure someone like apneadiving can explain it better than me, but that's how I see it

How to set variables across models for a single request in rails?

The scenario: I need to give models access to API tokens stored in the session.
Background: I have an API-driven rails 3 application utilizing DataMapper(DM) and a DM adapter to interface with the API. Each DM model has a corresponding REST-ish API endpoint much like you get with rails scaffolding. The API requires various headers for requests, including API tokens, keys, ids etc. The headers have nothing to do with the requested data, they exist for authorization and tracking purposes only. A number of these tokens are stored in the session. I want a clean way to make these API headers available to any model during a request.
Possible solutions:
1. Passing session variables from the controller to the models
The obvious answer is passing the tokens in a hash or other object from the controller to the models. A controller action might have the following: #user = User.find(params[:id], api_headers).
The problem is needing to override any model method to accept the additional api_headers object. Not counting methods defined by Rails and DataMapper, there are hundreds of methods already defined in the application models that would need to be rewritten. So I'm ruling out a rewrite, and this also doesn't seem like a good solution since it would require overriding a ridiculous number of DM methods like the User#find example above.
2. Some metaprogramming hack
I could catch any ArgumentError's on DM's base class and check if the last argument is the api_headers object, then set the values as instance variables and invoke the requested method. This thought exercise already has me cringing at dealing with optional arguments etc. If given long enough I could probably create a functional Frankenstein that should get me fired but probably wouldn't.
3. Use a singleton (current preferred solution)
In the application controller set a before_filter to dump the session-stored API headers into a singleton ApiHeaders object. Then any model making an API request can get that singleton with the required API headers.
An additional after_filter* on the application controller would set all attributes to nil on the ApiHeaders singleton at the end of the request to prevent leaking headers between requests.
This is currently my preferred solution but I don't like that the API header values could potentially carry over into other requests if the after_filter doesn't get invoked. I don't know in which scenarios this might happen (in an application error perhaps?) which raises concerns. All I know is the values don't necessarily die with the request.
4. Custom code
Drop support of DataMapper and the custom API adapter and manually make all API calls, passing through all required API headers. Besides the fact I don't have time for this level of rewrite, why use a framework at all if you have to throw a huge chunk out to support a custom auth scheme?
Summary
What's the cleanest way to get these pesky API tokens from the session into the bowels of the application where they can be sent with each API request? I'm hoping for a better solution than those listed above.
* An alias for after_action
I set the current user and the request information on my User model using the request_store gem which is just a tiny shim over thread local storage with a bit of clean-up.
This makes the information available from any of my models via the User class. I have User.current, User.request and User.location available wherever I need it.
Your controller just has to set User.current and User.request once it has authenticated the user.
Example User model:
# models/user.rb
require 'request_store'
class User
def self.current
RequestStore.store[:current_user]
end
def self.current=(user)
RequestStore.store[:current_user] = user
end
def self.request
RequestStore.store[:current_request]
end
def self.request=(request)
# stash the request so things like IP address and GEO-IP based location is available to other models
RequestStore.store[:current_request] = request
end
def self.location
# resolve the location just once per request
RequestStore.store[:current_location] ||= self.request.try(:location)
end
end
Use Thread.current, which is passed in from request to model (note, this breaks if, inside your request, you use sub-threads). You can store the attribute you want to share in a cattr_accessor or in rails cache:
in a cattr_accessor
class YourClass
cattr_accessor :my_var_hash
...
# and in your controller
# set the var
YourClass.my_var_hash = {} if YourClass.my_var_hash.nil?
YourClass.my_var_hash[Thread.current.object_id] = {}
YourClass.my_var_hash[Thread.current.object_id][your_var] = 100
... and in your model
lvalue = YourClass.my_var_hash[Thread.current.object_id][your_var]
Note, if you use this method, you will also want to make one of the hash values a timestamp, and do some housekeeping on getting, by deleting old keys, b/c you'll eventually use up all your system memory if you don't do the housekeeping
with cache:
# in your controller
#var = Rails.cache.fetch("#{Thread.current.object_id}_var_name") do
return 100 # do your work here to create the var value and return it
end
# in your model
lvalue = Rails.cache.fetch(("#{Thread.current.object_id}_var_name")
You can then set the cache expiration to 5 minutes, or you can wildcard clear your cache at the end of your request.

Disable Session Lookup for a single controller action

I want to disable sessions completely for a controller action, because I want this single controller action (it's an autocomplete action on thousands of values, so speed matters) to be blazingly fast.
I tried using session_off, but it just sets the session variable to nil, an still looks up the users session in the database.
Is it possible to completely disable the Rails::SessionStore middleware, but only for a single controller action or URL?
I am running rails 3.2.17.
The answer is: handle this endpoint in a Rack middleware of your own, and insert it into the stack as early as possible!
You can achieve this in config/routes.rb just by routing to the middleware object:
match 'my_autocomplete_endpoint', to: AutocompleteMiddleware
then just return a response from the middleware and don't go up the stack.
You can put this wherever you want in the stack in config/application.rb with:
config.middleware.insert_before(SomeOtherMiddleware, AutocompleteMiddleware)
e.g., perhaps insert it before Rails::SessionStore.
Rails 5+ solution (maybe before, not sure when this became available).
Add this to your controller. You can specify which actions should not touch/update the session using the only: option.
after_action -> { request.session_options[:skip] = true }, only: :my_action_name
This will make the response not include the set_cookie response header. I found this particularly useful when dealing with a race condition in multiple AJAX requests, whereas one contained a very important session (cookie data) update and the other the session was not used, but Rails still sent back an updated cookie for the session. The race condition could cause the updated session data from the important action to be overwritten from the one I didn't care about.

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