Design Decision using ActionMailer - ruby-on-rails

I'm confused about what design decision I need to make.
I know the fundamental use case of ActionMailer in that you would:
Create a class inheriting from ActionMaier
define a method in there of what to send and who to send it to and from who, etc
This is where I deviate and lose understanding.
Typically, you would call the method on your mailer model in the controller of a certain action, for ex-
def create
#user = User.new(params[:user])
if #user.save
MyMailerClass.send_signup_email(#user).deliver
redirect_to #user
else
render :new
end
end
What if for example I want to send an email based on if the user hasn't signed in in X days. I'm sure there are a number of ways to do this but curious of the rails best practice way, as I'm not sure where to look. This is close I don't want it to send depending on the time of day just when the certain condition is met.
Thanks.

It sounds like you want a cron job that runs periodically to check certain conditions and based on those conditions, send an email. This isn't really too far off from what you do with a standard mailer, except rather than sending it from the controller, you would be sending it from a separate script. I usually write a rake task for this:
task :send_reminder_emails => :environment do
users = User.where('last_login <= ?', 10.days.ago)
users.each do |user|
MyMailerClass.reminder(user).deliver
end
end
And you would run it with rake send_reminder_emails. There are tons of examples available for how to setup a cron job if you aren't familiar with how to do that.

Related

Using current user in Rails in a model method

I'm currently trying to implement simple audit for users (just for destroy method). This way I know if the user has been deleted by an admin or user deleted itself. I wanted to add deleted_by_id column to my model.
I was thinking to use before_destroy, and to retrieve the user info like described in this post :
http://www.zorched.net/2007/05/29/making-session-data-available-to-models-in-ruby-on-rails/
module UserInfo
def current_user
Thread.current[:user]
end
def self.current_user=(user)
Thread.current[:user] = user
end
end
But this article is from 2007, I'm not sure will this work in multithreaded and is there something more up to date on this topic, has anyone done something like this lately to pass on the experience?
Using that technique would certainly work, but will violate the principle that wants the Model unaware of the controller state.
If you need to know who is responsible for a deletion, the correct approach is to pass such information as parameter.
Instead of using callbacks and threads (both represents unnecessary complexity in this case) simply define a new method in your model
class User
def delete_user(actor)
self.deleted_by_id = actor.id
# do what you need to do with the record
# such as .destroy or whatever
end
end
Then in your controller simply call
#user.delete_user(current_user)
This approach:
respects the MVC pattern
can be easily tested in isolation with minimal dependencies (it's a model method)
expose a custom API instead of coupling your app to ActiveRecord API
You can use paranoia gem to make soft deletes. And then I suggest destroying users through some kind of service. Check, really basic example below:
class UserDestroyService
def initialize(user, destroyer)
#user = user
#destroyer = destroyer
end
def perform
#user.deleted_by_id = #destroyer.id
#user.destroy
end
end
UserDestroyService.new(user, current_user).perform

Create Rails model with argument of associated model?

I have two models, User and PushupReminder, and a method create_a_reminder in my PushupReminder controller (is that the best place to put it?) that I want to have create a new instance of a PushupReminder for a given user when I pass it a user ID. I have the association via the user_id column working correctly in my PushupReminder table and I've tested that I can both create reminders & send the reminder email correctly via the Rails console.
Here is a snippet of the model code:
class User < ActiveRecord::Base
has_many :pushup_reminders
end
class PushupReminder < ActiveRecord::Base
belongs_to :user
end
And the create_a_reminder method:
def create_a_reminder(user)
#user = User.find(user)
#reminder = PushupReminder.create(:user_id => #user.id, :completed => false, :num_pushups => #user.pushups_per_reminder, :when_sent => Time.now)
PushupReminderMailer.reminder_email(#user).deliver
end
I'm at a loss for how to run that create_a_reminder method in my code for a given user (eventually will be in a cron job for all my users). If someone could help me get my thinking on the right track, I'd really appreciate it.
Thanks!
Edit: I've posted a sample Rails app here demonstrating the stuff I'm talking about in my answer. I've also posted a new commit, complete with comments that demonstrates how to handle pushup reminders when they're also available in a non-nested fashion.
Paul's on the right track, for sure. You'll want this create functionality in two places, the second being important if you want to run this as a cron job.
In your PushupRemindersController, as a nested resource for a User; for the sake of creating pushup reminders via the web.
In a rake task, which will be run as a cron job.
Most of the code you need is already provided for you by Rails, and most of it you've already got set in your ActiveRecord associations. For #1, in routes.rb, setup nested routes...
# Creates routes like...
# /users/<user_id>/pushup_reminders
# /users/<user_id>/pushup_reminders/new
# /users/<user_id>/pushup_reminders/<id>
resources :users do
resources :pushup_reminders
end
And your PushupRemindersController should look something like...
class PushupRemindersController < ApplicationController
before_filter :get_user
# Most of this you'll already have.
def index
#pushup_reminders = #user.pushup_reminders
respond_with #pushup_reminders
end
# This is the important one.
def create
attrs = {
:completed => false,
:num_pushups => #user.pushups_per_reminder,
:when_sent => Time.now
}
#pushup_reminder = #user.pushup_reminders.create(attrs)
respond_with #pushup_reminder
end
# This will handle getting the user from the params, thanks to the `before_filter`.
def get_user
#user = User.find(params[:user_id])
end
end
Of course, you'll have a new action that will present a web form to a user, etc. etc.
For the second use case, the cron task, set it up as a Rake task in your lib/tasks directory of your project. This gives you free reign to setup an action that gets hit whenever you need, via a cron task. You'll have full access to all your Rails models and so forth, just like a controller action. The real trick is this: if you've got crazy custom logic for setting up reminders, move it to an action in the PushupReminder model. That way you can fire off a creation method from a rake task, and one from the controller, and you don't have to repeat writing any of your creation logic. Remember, don't repeat yourself (DRY)!
One gem I've found quite useful in setting up cron tasks is the whenever gem. Write your site-specific cron jobs in Ruby, and get the exact output of what you'd need to paste into a cron tab (and if you're deploying via Capistrano, total hands-off management of cron jobs)!
Try setting your attr_accessible to :user instead of :user_id.
attr_accessible :user
An even better way to do this however would be to do
#user.pushup_reminders.create
That way the user_id is automatically assigned.
Use nested routes like this:
:resources :users do
:resources :pushup_reminders
end
This will give you params[:user_id] & params[:id] so you can find your objects in the db.
If you know your user via sessions, you won't need to nest your routes and can use that to save things instead.
Using restful routes, I would recommend using the create action in the pushup_reminders controller. This would be the most conventional and Restful way to do this kind of object creation.
def create
#user = User.find(params[:user_id]
#reminder = #user.pushup_reminders.create()
end
If you need to check whether object creation was successful, try using .new and .save

DelayedJob email where to have 'who to email' logic

In my app I have certain events that trigger a lot of emails (~100). Obviously sending them immediately is not an option, so I'm using DelayedJob to queue them up and send them after the request is processed. I've now found that the logic to determine WHICH 100 people to email is heavy enough that it takes a while to run, so I'd like to DelayedJob that process as well. Where should this logic go? (model? mailer?) Sending mail from the model just feels wrong. Is there a best practice here?
You should write a class that represents the job. Not a model class, not a controller class: a job class.
# app/jobs/mail_job.rb
class MailJob
attr_accessor :first_option, :second_option
def initialize(first_option, second_option)
self.first_option = first_option
self.second_option = second_option
end
def perform
accounts = Account.where("some_key" => first_option).to_a
# more complicated stuff goes here
accounts.each do |account|
AccountMailer.hello_message(account).deliver
account.mark_hello_delivered!
end
end
end
job = MailJob.new(params["first"], params["second"])
Delayed::Job.enqueue(job)

When does code belong in the model in ruby on rails?

Currently putting in a lot of rails 3 practice and I was working on an authentication system and was following a tutorial on railscasts. Ryan from railscasts done a sort of update to that tutorial with some minor changes to take advantage of rails 3.1
e.g. has_secure_password
So some of the code in my Sessions_controller changed to:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_username(params[:username])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_path, :notice => "Logged In"
else
flash.now.alert = "Invalid Credentials"
render "new"
end
end
def destroy
session[:user_id] = nil
redirect_to root_path, :notice =>"Logged Out"
end
end
What I would like to know is if some of the code in the create method/action should be in the model? Is it good or bad practice to have this code there?
What rules should I be following as I want to learn the correct way and not pick up bad habits because I've gone past that part of learning a framework where things start to make sense much more usually than they don't.
Advice is appreciate..
What I would like to know in particular is..
1. When does a programmer know when code belongs in the model? How does he/she make that decision?
This is one of the most important questions in OO programming.
It's all about responsibilities. Place code in your model if you think that model is responsible for that piece of functionality.
In your example you see that:
The SessionController is only responsible creating and destroying the the user's session.
The User is responsible for authentication.
All business logic goes into your models. Your controllers takes care of populating your views, handling the user's input and sending the user on their way. View simply display information, hardly contain any logic (if any).
Also: take a look at existing projects for inspiration (for example, Shopify).
My advice:
In User model (pseudocode):
function authenticate(username, pass) {
/*get user by name
return user_id (or user object if you need some user data in view) if auth ok, otherwise false
*/
}
I think you should always keep controllers small as possible.
The most important think in OOP is encapsulation so you should write all operation on user in user class, return to client code (in this case controller) only what controller need to do his job - add user id to session.

what is the best way to make a newsletter in rails3

Is there a gem to do this, and if not, what is the best approach. I'm assuming i'd store the emails in a newsletter database, and would want a form that emails everyone at once. thanks!
No gem for this that I know of.
Actually, due to processing issues, the best way would be to use external bulk email service provider via provider's API.
However, building your own newsletter system is no different from building your regular MVC. Only addition are mailers and mailer views.
(1) So you create model that deals with registration data (:name, :email, :etc...) and model that deals with newsletter itself.
(2) Controller that deals with it (CRUD) + (:send). Send takes each recipient, sends data to mailer which creates email and then sends it.
def send
#newsletter = Newsletter.find(:params['id'])
#recipients = Recipient.all
#recipients.each do |recipient|
Newsletter.newsletter_email(recipient, #newsletter).deliver
end
end
(3) Mailer builds an email, makes some changes in each email if you want to do something like that and returns it to controller action send for delivery.
class Newsletter < ActionMailer::Base
default :from => "my_email#example.com", :content_type => "multipart/mixed"
def newsletter_email(recipient, newsletter)
# these are instance variables for newsletter view
#newsletter = newsletter
#recipient = recipient
mail(:to => recipient.email, :subject => newsletter.subject)
end
end
(4) Ofc, you need mailer views which are just like regular views. Well in multipart there is:
newsletter_email.html.erb (or haml)
newsletter_email.txt.erb
When you want to send both html and text only emails. Instance variables are defined in mailer ofc.
And... that is it. Well you could use delayed job to send them since sending more than several hundred emails can take a while. Several ms times n can be a lot of time.
Have fun.
Please check the maktoub gem there is a blog post over it.
No gem that I know of too and building on #Krule's answer, here's a screencast of setting up mailers in Rails.
How to create, preview and send email from your rails app
I was looking for something similar when I found this. I think with a bit of customization, it can easily be used to create newsletter emails too.
Save money! Spend somewhere else.

Resources