How do you access the session from within an ActionMailer class? - ruby-on-rails

I have is an application can be accessed from 2 different URLs and looks/behaves slightly different depending on which URL you use to access it. I'm able to do this by storing a value in the session object and basing some decisions off of it such as which path views get loaded from.
This sort of approach seemed like it would work until I needed to have different URL's sent out in emails. In a class that inherits from ActionMailer, I need to set the default_url_options[:host] based on the value of a session variable. Rails throws the following error when I call session from anywhere within the mailer:
undefined local variable or method `session' for ApplicationMailer:Class
The less-than-desirable way to handle this is to pass the session variable into my mailer calls. I'd rather not do this as it doesn't seem very DRY and would require changes to much of my code.

Whether you can shoehorn a reference to the session into the mailer or not, I think you've already hit upon the correct solution. Passing in the context you want to use would be preferable for a couple of reasons.
The mailer probably shouldn't know about the session in the first place.
Assume that someday you have to send a lot of mail, and batch process it. You'll be right back to where you are now -- having to pass in your context.

Related

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

Rails: elegant way to get user_agent in a before/after callback?

I have an :after_update callback that records edits to one of my models.
I'm wondering if there is an elegant way to get the user_agent into in this callback when the edit came from a web request.
I know it's in the model, which doesn't have access to the request object. And it doesn't have to be edited from a web request. For example, if edited from the console, it would be fine if user_agent were nil in that case.
The only alternative I can think of is to find every controller action which might update the model and make the call from there, but this is a huge code base that is changing rapidly so it would be easy to miss something this way.
Maybe there is a clever way to put an attr_acessor on the model called user_agent (not stored in database) and ensure every controller action populates this on an edit? But not sure.
Thanks!
Elegant I'm not sure about, but I believe Rails will use 1 thread per request (spawns it off to process it), so you can do
Thread.current[:my_user_agent] = request.user_agent
and that is accessible all the way through the app for the duration of that request. I'm also fairly sure that's not documented behaviour, so you might have issues if Rails modifies it.

Keep value in memory across requests and across users in Rails controller? Use class variable?

We're on Rails 3.0.6.
We maintain a list of numbers that changes only once a month, but nearly every page request requires access to this list.
We store the list in the database.
Instead of hitting the database on every request and grabbing the list, we would like to grab the data once and stash it in memory for efficient access.
If we store the list in each user session, we still need to hit the database for each session.
Is there a way to only hit the database once and let the values persist in memory across all users and all sessions? We need access to the list from the controller. Should we define a class variable in the controller?
Thanks!
I think Rails.cache is the answer to your problem here. It's a simple interface with multiple backends, the default stores the cache in memory, but if you're already using Memcached, Redis or similar in your app you can plug it into those instead.
Try throwing something similar to this in your ApplicationController
def list_of_numbers
#list_of_numbers ||= Rails.cache.fetch(:list_of_numbers, :expires_in => 24.hours) do
# Read from database
end
end
It will try to read from the cache, but if it doesn't find it, will do the intensive stuff and store it for next time
The pattern you're looking for is known as a singleton which is a simple way to cache stuff that doesn't change over time, for example, you'll often see something like this in application_controller.rb -- your code always calls the method
def current_user(user_id)
#current_user ||= User.find user_id
end
When it does, it checks the instance variable #current_user and returns it if not nil, otherwise it does the database lookup and assigns the result to the instance variable, which it returns.
Your problem is similar, but broader, since it applies to all instances.
One solution is with a class variable, which is documented here http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_classes.html#S3 -- a similar solution to the one above applies here.
This might be a good solution in your case, but has some issues. In specific, (assuming this is a web app) depending on your configuration, you may have multiple instances of Rails loaded in different processes, and class variables only apply to their specific instance. The popular Passenger module (for Apache and Nginx) can be configured to allow class variables to be accessible to all of it's instances ... which works great if you have only one server.
But when you have multiple servers, things get a little tricky. Sure, you could use a class variable and accept that you'll have to make one hit to the database for each server. This works great except for the when that the variable ... varies! You'll need some way of invalidating the variable across all servers. Depending on how critical the it is, this could create various very gnarly and difficult to track down errors (I learned the hard way :-).
Enter memcached. This is a wonderful tool that is a general purpose caching tool. It's very lightweight, and very, very smart. In particular, it can create distributed caches across a cluster of servers -- the value is only ever stored once (thus avoiding the synchronization problem noted above) and each server knows which server to look on to find any given cache key. It even handles when servers go down and all sorts of other unpleasantries.
Setup is remarkably easy, and Rails almost assumes you'll use it for your various caching needs, and the Rails gem just makes it as simple as pie.
On the assumption that there will be other opportunities to cache stuff that might not be as simple as a value you can store in a class variable, that's probably the first place to start.

Rails: How can you access session variables using multiple controllers?

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.

ActionMailer best practices: Call method in the model or the controller?

Sending an email is usually called after an action on a model, but the email itself is a view operation. I'm looking for how you think about what question(s) to ask yourself to determine where to put the action mailer method call.
I've seen/used them:
In a model method - bad coupling of related but seperate concerns?
In a callback in the model (such as after_save) - best separation as far as I can tell with my current level of knowledge.
In the controller action - just feels wrong, but are there situations were this would be the smartest way to structure the code?
If I want to know how to program I need to think like a programmer, so learning how you go about thinking through particular programming solutions is worth months of coding on my own in isolation. Thank you!
Late answer, but I want to rationalize on the subject:
Usually, in a web app, you want to send emails either as a direct reaction to a client. Or as a background task, in case we're talking about a newsletter/notification mail sort of thing.
The model is basically a data storage mapper. Its logic should encapsulate data-handling/communication with data storage handling. Therefore, inserting logic which does not relate to it is a bit tricky, and in most cases wrong. Let us take the example: User registers an account and should receive a confirmation email. In this case one could say, the confirmation email is a direct effect of the creation of a new account. Now, instead of doing it in the web app, try to create a user in the console. Sounds wrong to trigger a callback in that case, right? So, callback option scratched. Should we still write the method in the model? Well, if it's a direct effect of a user action/input, then it should stay in that workflow. I would write it in the controller after the user was successfully created. Directly. Replicating this logic in the model to be called in the controller anyways adds unnecessary modularity, and dependency of an Active Record model from Action Mailer. Try to consider sharing the model over many apps, in which some of them don't want Action Mailer for it. For the stated reasons, I'm of the opinion that the mailer calls should be where they make sense, and usually the model is not that place. Try to give me examples where it does make.
Well, depends.
I've used all of those options and your point about 'why should I put this where?' is good.
If it's something I want to happen every time a model is updated in a certain way, then I put it in the model. Maybe even in a callback in the model.
Sometimes you're just firing off a report; there's no updating of anything. In that case, I've normally got a resource with an index action that sends the report.
If the mailer isn't really related to the model that's being changed, I could see putting it in a callback. I don't do that very often. I'd be more likely to still encapsulate it in the model. I've done it, just not very often.
I'm aware it's been a while but best practices never die, right? :)
Email is by definition asynchronous communication (except for confirmation email, but even this one it should be a best practice to leave a delay before having to confirm).
Hence in my opinion, the most logical way to send it is :
in a background action (using Sidekiq or delayed_job)
in a callback method : "hey this action is successfully done, maybe we can tell the world now?"
Problem in Rails is that it is not too many callbacks (as in JS for instance): I personnaly find it dirty to have code like:
after_save :callback
def callback
if test_that_is_true_once_in_the_objects_life
Mailer.send_email()
end
end
So, if you really want to think like a programmer, the idea would be to set up some custom callback system in your app.
Eg.
def run_with_callback(action, callback_name)
if send(action)
delay.send(callback_name)
end
end
Or even creating an event system in your app would be a decent solution.
But in the end those solutions are pretty expensive in time so people end-up writing it inline after the action
def activate
[...]
user.save
Mailer.send_mail
respond_to
[...]
end
which is the closest fashion to callback in synchronous programming and results having Mailers call everywhere (in Model and in Controller).
There's several reasons why controllers are a good place for the mailers:
Emails that have nothing to do with a model.
If your emails depend on several models that dont know about each other.
Extracting models to an API should not mean reimplementing mailers.
Mailer content determined by request variables that you dont want to pass to the model.
If your business model requires a lot of diferent emails, model callbacks can stack.
If the email does not depend on the result of model computations.

Resources