Rails: How can you access session variables using multiple controllers? - ruby-on-rails

I am having a problem with using session variables. I have two controllers named 'graduate_students_controller' and 'current_students_controller'. Each of these controllers control different view files. I am using session variables with both these controllers to store session information.
Here's the problem. Let's say I have two view files 'reports/current_students_list', 'reports/graduate_students_list' each controlled separately by the above mentioned controllers.
Now if I try to open those two web pages from within the same browser and try to work with them simultaneously, I get 'nil object access' error from the firstly loaded page. The 'nil object' refers to a session variable that the first page is supposed to access. However, when I use any of those two web applications individually, they work fine.
So its seems to me that session variables of the firstly loaded web app. are getting overwritten by the secondly loaded web app. maybe because the second page stores a new cookie over the first one?
How do I fix this?
Any suggestion is much appreciated.
To clarify a bit more: The two controllers belong to the same Rails application. And I am not using identical session variable names within both controllers. So I cannot see why they can get overwritten
I am new to rails and I would really appreciate some help with this problem. Thanks.

I'm not sure if you are running two apps, or are referring to two controllers under the same app. If you are looking at different web apps, then I think you are using the same name and session key in your environment for each of these apps. Try changing the key value in your environment.rb:
config.action_controller.session = { :key => "_myapp_session", :secret => "..." }
If you are using the same session variable from two different controllers in the same application, then you'll need to write your code to accomodate this, though I wouldn't recommend doing this. When accessing your session data, check for nil values:
session[:some_key].nil?
and make sure that common code (i.e. in the application_controller.rb) isn't overwriting your values.

Related

Rails, handle two sites with different url and design but with the same db

I'm looking for the best way to solve a problem.
At this moment I have a site for a customer, example.domain.com
My customer ask to create another website with some changes in design, but the contents are the same of the first website. I don't want to duplicate the website, because every feature I add to the site A must be deployed also to site B, and I'm looking a smart way to handle the situation.
I need to keep two different domains and I need also custom mailers and other small tweaks in the controllers (and maybe in some models).
My idea is to put in application controller a before filter like this
before_action :detect_domain
private
def detect_domain
case request.env['HTTP_HOST']
when "example.domain.com"
request.variant = :host1
when "example1.domain.com"
request.variant = :host2
end
end
Then I use the variant with some conditional to choose the mailer, to customize the views and to apply some code changes.
Any other idea?
Using a before filter and a per-request variable like your proposal will work, with a couple caveats that I'll mention below. I'd recommend a tool like the request_store gem to actually store the per-request value of which "skin" is selected.
Now, for the caveats. First, the main problem with per-request variables is that your Rails app does not always exist in the context of a request. Background jobs and console sessions operate outside of the usual request/response flow of your app. You will need to think about what happens when your models or other non-controller/view code is executed when that variable isn't set. I would suggest simply not having your models depend on RequestStore at all -- have the controllers pass any request-specific information down into the models, if needed.
Secondly, it's not clear from your description if you want any data or logical separation between the two domains, or if you just want different look-and-feels. If the former, you might consider the apartment gem, which aims to make database multi-tenancy easier.
EDIT: I also want to mention that, as an alternative to the multi-tenant solution above, you also have the option of a multi-instance solution. Wherein, you use an environment variable to indicate which version of the site should be displayed, and spin up multiple instances of your app (either on the same server with a reverse proxy, or on separate servers with separate DNS entries or a reverse proxy). The downside is increased infrastructure costs, but the context problem I mentioned above no longer exists (everything always has access to environment variables).

Rails: controller, view and helper life cycles

I wonder if there is any doc on that. I believe it is helpful to know when those objects get created and destroyed and whether they are re-used (like Java servlets). Particularly, I am wondering about helpers. I assume a new controller instance is created for every new request, and the same is true for views but not for helpers. In his tutorial Michael Hartl uses SessionsHelper to store the current user, which makes me think helper lifetime is bound to the session. Is this correct? Then technically I can use helpers to store session info, right?
The basic flow goes like this:
(request) -- Rack stuff -- Controller instance -- View instance -- (response)
Any instance variables you set in the Controller are made available to the View. Those are threadsafe. Class variables, on the other hand, are not threadsafe.
Helpers are Modules, not Classes, so they aren't (can't be, really) instantiated. Instead, they're mixed into the View instance that evaluates your templates. Again, instance variables are threadsafe, class variables aren't.
My thinking is that helper methods, as with every other part of a Rails app, is created & destroyed with each request
You have to remember the helper methods will not "store" any session data - they'll only call the session cookies to display the relevant data:
Most applications need to keep track of certain state of a particular
user. This could be the contents of a shopping basket or the user id
of the currently logged in user. Without the idea of sessions, the
user would have to identify, and probably authenticate, on every
request. Rails will create a new session automatically if a new user
accesses the application. It will load an existing session if the user
has already used the application.
A session usually consists of a hash of values and a session id,
usually a 32-character string, to identify the hash. Every cookie sent
to the client's browser includes the session id. And the other way
round: the browser will send it to the server on every request from
the client. In Rails you can save and retrieve values using the
session method:
This demonstrates, to me at least, that every time you load a "helper", it's really relying on the user's stored data (particularly for the session). This means that although it may appear that a helper's lifecycle may extend beyond each request, it's really just relying on the data provided
Having said that, I need to read up on how to make this threadsafe etc, as per benjamin sinclaire's comment

Where should I store session code in a Rails app?

In a Rails app I'm creating, I have some code in my controller that I'm wondering whether it's in the proper place. The code is fairly insignificant, it stores ids in an array to show 'recently viewed' pages. It's about 3 lines of code, but I'm thinking to the future, what if this feature expands? I don't want my controller to be bloated.
I could make a module, but in that case where should I store the file?
Is the controller the right place to be doing the session management?
Any suggestions on my code organization?
Thanks
If it is specific to the controller, keep it in the controller.
If it applies to all controllers, it goes in ApplicationController.
If it is shared by some controllers and not others, then inherit from a controller that inherits from ApplicationController, or use include/extend or make a module that extends ActiveSupport::Concern (which is what Rails uses internally fairly commonly).
And it's best to keep everything in app/controllers or some subdirectory, sub-subdirectory, etc. Rails autoloading depends on the path to match up with the module namespace, so A::B::C belongs in app/controllers/a/b/c.rb. Don't make it deep like Java, etc. Just have the number of directories/modules you need to keep it organized.
Note: though controllers aren't as problematic to have in their own modules, in my experience your models should stay in the root, like app/models, or you'll have problems.
I'd also avoid storing too much in session if you can help it. Store in the DB (or long-life cookies, if it is browser-environment specific) instead. For example- if someone logs out and they were looking at one record, they might want to log back in later and have a list containing the link to that record.
BTW- you weren't asking and probably already have the code for storing recently visited pages in session, but here are similar questions/answers:
https://stackoverflow.com/a/10813602/178651
http://www.rorexperts.com/how-to-store-visited-pages-in-session-object-in-rails-t941.html

what should i be using instead of a global variable in my rails3 app?

I have a rails 3 app that is using sub domains for each account.
When a visitor visits a subdomain eg acme.domain.com, i lookup the subdomain from the database using the request.subdomain. I then store the subdomain id in a glabla variable.
I do this as i need access to this variable from controller, views and models so i can scope everything to just this subdomain. eg just users, companies, pages etc for this subdomain.
From what i hear using globals is bad design and can be buggy etc, but what should i use instead in this case ?
please can anyone help ?
best regards
rick
Use sessions or cookies for this.
Global variables are accessible for all users, while sessions and cookies are personalized
http://guides.rubyonrails.org/security.html#sessions
Obviously your controller knows about the subdomain. Your models and views shouldn't need to lookup this global at all, because this builds am artificial dependency into them, reducing reusability and testability.
If they really need additional data to work, and you can't keep that in the controller's Model.find() for example, the controller shall pass this data to everything else explicitly.

Multi domain rails app. How to intelligently use MVC?

Background:
We have app a, b, and plan to add more apps into this same application. The apps are similar enough they could share many views, assets, and actions. Currently a,b live in a single rails app(2.3.10). c will be similar enough that it could also be in this rails app.
The problem:
As we continue to add more apps to this one app, there's going to be too much case logic that the app will soon become a nightmare to maintain. There will also be potential namespace issues. However, the apps are very similar in function and layout, it also makes sense to keep them in one app so that it's one app to maintain(since roughly 50% of site look/functionality will be shared).
What we are trying to do is keep this as clean as possible so it's easy for multiple teams to work on and easy to maintain.
Some things we've thought about/are trying:
Engines. Make each app an engine. This would let us base routes on the domain. It also allows us to pull out controllers, models and views for the specific app. This solution does not seem ideal as we won't be reusing the apps any time soon. And explicitly stating the host in the routes doesn't seem right.
Skinning/themes. The auth logic would be different between the apps. Each user model would be different. So it's not just a skinning problem.
In app/view add folder sitea for sitea views, siteb for siteb views and so on. Do the same for controllers and models. This is still pretty messy and since it didn't follow naming conventions, it did not work with rails so nicely and made much of the code messier.
Making another rails app. We just didn't want to maintain the same controller or view in 2 apps if they are identical.
What we want to do is make the app intelligently use a controller based on the host. So there would be a sessions controller for each app, and perhaps some parent session controller for shared logic(not needed now). In each of these session controllers, it handles authentication for that specific app. So if the domain is a.mysite.com, it would use session controller for app a and know to use app a's views,models,controllers. And if the domain is b.mysite, it would use the session controller for b. And there would be a user model for a and user model for b, which also would be determined by the domain.
Does anyone have any suggestions or experience with this situation? And ideally using rails 2.3.x as updating to rails 3 isn't an option right now.
Devise does exactly this. You would do well to check out its architecture and apply that architecture to your own case.
You will have multiple separate Rails applications. The shared code will be a separate project, perhaps distributed as a gem or at least a separate Git repository. The shared code will include many controller actions and many view templates that are there to be sensible defaults, and which will be overridden in some apps but not in others.
All the custom code for application A will belong in a project solely devoted to containing the custom code for application A. It will be its own fully-functioning Rails application and will depend heavily on the majority of the sensible defaults provided by the shared code in the shared-code project.
I've used the theme support plugin before and dynamically set the theme based on the request uri:
http://mattmccray.com/svn/rails/plugins/theme_support
It will probably need some work to support Rails 2.3.
Update: Looks like there's a rewrite: https://github.com/dasil003/rails-multisite
Sounds like you want to make the 'base' app a plugin and use that in each of your site apps. You can use something like svn-extern so it's automatically updated whenever something changes.

Resources