Is there anything similar to delayed_job, but without workers? - ruby-on-rails-3

In my app, when a user signs up, they are sent a confirmation email. I use delayed_job, to make the process of sending email go in background.
But disadvantage of using delayed_job is having a worker all the time. And having a worker for this is expensive.
Is there something other than delayed_job, that will make email sending go in background.
Here is my controller code snippet.
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
UserMailer.delay.registration_confirmation(#user)
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.js
The point is I am having 20-40 signups in a day. That means at most the the queue is busy for about 60 seconds and I will have to pay for the the entire day, which is very impractical. Some other nice approach.

I recommend setting up a cron that will run once every hour and send out e-mails. You'll have to create a new model, QueuedEmail or something similar, and then instead of using ActionMailer right away, save it as a QueuedEmail. This is basically the same thing that delayed_job does, but you'll have more control over when it gets run, and crons don't take up much memory.
The cron should be to script/rails runner, and you should have a method in your QueuedEmail model that will send out all pending e-mails. I recommend whenever to generate crontabs (quick to setup and very easy to use). The cron will look something like this (this example is set to run once a day at 2am, you can look up how to adjust the intervals, I don't know off the top of my head):
0 2 * * * /bin/bash -l -c 'cd /RAILS_ROOT/ && script/rails runner -e production '\''QueuedEmail.send_pending'\'' >> log/cron.log 2>&1'
Or in Whenever:
every :day, :at => '2 am' do
runner "QueuedEmail.send_pending"
end
QueuedEmail#send_pending
class QueuedEmail < ActiveRecord::Base
def send_pending
all.each do |email|
params = your_parsing_method(email.params)
record = your_parsing_method(email.record)
email.destroy if UserMailer.registration_confirmation(record, params).deliver
end
end
end
UserController#create
if #user = User.create(params[:user])
User.delay_email(#user, params[:user])
redirect_to user_path(#user), :notice => "Created User. E-mail will be sent within the hour."
end
User#delay_email
def delay_email(record, params)
QueuedEmail.create(:record => your_db_formatting_method(record), :params => your_db_formatting_method(params.to_s)) # or use something built in, like to_s.
end
None of this code is tested and is probably quite broken, but it's the general idea that matters. You could also go one step further and extend the Rails ActionMailer, but that is far more advanced.

If you are using a job only to send "signup mails" you probably be better not using a separated job for that (considering you have very low amount of emails to send).
This will simplify your configuration and will reduce your costs.
A separated job is useful if you have a task that takes a lot of time to be concluded or you have a very busy site, as it's not the case of either, it's a good idea to just send regular emails.

Related

How can I call a controller/view action from a mailer?

In my rails application I've created a business daily report. There is some non-trivial logic for showing it (all kind of customizable parameters that are used for filtering in the model, a controller that calls that model and some non-trivial view for it, for example, some of the columns are row-spanning over several rows).
Now I wish to send this report nightly (with fixed parameters), in addition to the user ability to generate a customize report in my web site. Of course, I wish not to re-write/duplicate my work, including the view.
My question is how can I call the controller action from my mailer so that it will be as if the page was requested by a user (without sending a get request as a browser, which I wish to avoid, of course)?
In answer to your question is if you are generating some sort of pdf report then go with using the wicke_pdf gem does exactly that generates pdfs. To send a report on a nightly basis the best thing for this is to implement some sort of cron job that runs at a particular time which you can do using the whenever gem. You can do something like:
schedule.rb
every :day, :at => '12:00am'
runner User.send_report
end
With this at hand you can see that you call the send_report method sits inside the User model class as shown below:
User.rb
class User < ActiveRecord::Base
def self.send_report
ReportMailer.report_pdf(#user).deliver
end
end
Inside send_report we call the mailer being ReportMailer which is the name of the class for our mailer and the method being report_pdf and pass in the user. BUT remember this is an example I have here I am not sure the exact specified information you want in a report.
Mailer
class ReportMailer< ActionMailer::Base
default :from => DEFAULT_FROM
def report_pdf(user)
#user = user
mail(:subject => "Overtime", :to => user.email) do |format|
format.text # renders report.text.erb for body of email
format.pdf do
attachments["report.pdf"] = WickedPdf.new.pdf_from_string(
render_to_string(:pdf => "report",:template => 'report/index.pdf.erb',
:layouts => "pdf.html"))
end
end
end
end
Inside the mailer there are a variety of things going on but the most important part is inside the format.pdf block that uses a variety of wicked_pdf methods (this is assuming that you are using wicked_pdf btw. Inside the block you create a new WickedPDF pdf object and render it to a string. Then provide it with the name of the report, the template and the layout. It is important that you create a template. This usually will where the report will be displaying from. The file type is a .pdf.erb this means that when this view or report is generated in the view the embedded ruby tags are being parsed in and the output is going to be a pdf format.
UserController
def report
#user = User.scoped
if params[:format] == 'pdf'
#Do some stuff here
User.send_report(#users)
end
respond_to do |format|
format.html
format.pdf do
render :pdf => "#{Date.today.strftime('%B')} Report",
:header => {:html => {:template => 'layouts/pdf.html.erb'}}
end
end
end
The key thing you asked that I picked up on.
how can I call the controller action from my mailer
In the controller simply collate a scope of Users, then check the format is a pdf, providing it is do some stuff. Then it will run the method send_report which I earlier highlighted in the user model class (Btw in your words this is the controller calling the model). Then inside the respond block for this there is a format.pdf so that you can generate the pdf. Once again note that you need a template for the core design of the pdf, which is similar to how rails generates an application.html.erb in the layouts. However here we have a pdf.html.erb defined. So that this can be called anywhere again in your application should you want to generate another pdf in your application somewhere else.
Think I've provided a substantial amount of information to set you off in the right direction.

Automatic execute action in rails

I want to automatic execution action in other controller at certain time.
I read more similar posts on this site, and all of them tell about whenever or delayed job.
But i don't understand how it works=(
This is an example of what i want.
def create
#user = User.find(params[:user_id])
#client = #user.clients.create(params[:client])
if #client.save
redirect_to user_clients_path(#user)
else
render :action => 'new'
end
Timeout(#client.event_time)
redirect_to :controller => :some_controller, action => some_action
end
It's a little difficult to understand what effect you want:
If what you want is a timeout action to take place if the user hasn't responded in a certain amount of time, then cleanest way to do it would be to write some javascript on the client side -- set a timer which fires an action if the timer expires, clear the timer if the user presses a button.
If what you want is to display a page for a few seconds after creating the Client and then switch to a new page, you can put a meta-refresh in the first page you display and have it automatically redirect to the desired page after #client.event_time seconds.

working on rails 3 tutorial by hartl and stuck on 12.3.4 - New Status Feed

I'm stuck on the last exercise in the Hartlt book 12.3.4 where you define the status feed for yourself and the users you are following. I'm a Rails newbie so just let me know if I need to post additional information.
When I tail the development log I can see will_paginate fire the SQL to gather the initial records, and the initial page looks fine when it is served up. When I click a link to go to any another page, it appears will_paginate doesn't fire the SQL to get retrieve more data from the database as the next page is server up fine, but there is no data.
There are also no new entries in the development log and maybe I'm wrong, but I think this indicates will_paginate didn't hit the database.
I tried to include all the relevant code snippits below. Happy to send anything that's missing.
Here is my pages_controller.rb
def home
#title = "Home"
if signed_in?
#micropost = Micropost.new
#feed_items = current_user.feed.paginate(:page => params[:page])
end
end
Here is my user.rb
def feed
Micropost.from_users_followed_by(self)
end
Here is my microposts.rb
scope :from_users_followed_by, lambda { |user| followed_by(user) }
private
def self.followed_by(user)
following_ids = %(SELECT followed_id FROM relationships
WHERE follower_id = :user_id)
where("user_id IN (#{following_ids}) OR user_id = :user_id",
:user_id => user)
end
NOTE: In the video tutorial (chapter 12, time = 02:06:00) and the book (page 517, listing 12.44) Harlt uses the variable "followed_ids" instead of "following_ids". In the virgin sample code you can download from his site the variable name is "following_ids", and I have tried it both ways - but it fails.
Bottom line - the dev log shows will_paginate retrieving the first batch of data, but it never goes back to the database for additional data.
Can anyone suggest what I can take a look at to resolve my problem?
Many thanks.
do you have more records than will fit on one page?
If not, it may be that will_paginate is doing a count first.
Then deciding not to load any records.
Get into the console and try it out.
$ rails console
user = User.find(23)
# the same user you're logged in as
user.feed.count
# is it more than one page?
user.feed.paginate(:page => 1)
# 20 records?
user.feed.paginate(:page => 2)
# nothing?
Also:
in your example scope :from_users_followed_by, lambda { |user| followed_by(user) } is redundant.
You may as well just say;
def feed
Micropost.followed_by(self)
end

Failing to test Devise with Capybara

I'm building a Rails 3 app using Devise, with Capybara for UI testing. The following test is failing:
class AuthenticationTest < ActionController::IntegrationTest
def setup
#user = User.create!(:email => 'test#example.com',
:password => 'testtest',
:password_confirmation => 'testtest')
#user.save!
Capybara.reset_sessions!
end
test "sign_in" do
# this proves the user exists in the database ...
assert_equal 1, User.count
assert_equal 'test#example.com', User.first.email
# ... but we still can't log in ...
visit '/users/sign_in'
assert page.has_content?('Sign in')
fill_in :user_email, :with => 'test#example.com'
fill_in :user_password, :with => 'testtest'
click_button('user_submit')
# ... because this test fails
assert page.has_content?('Signed in successfully.')
end
end
... but I have no idea why. As you can see from the code, the user is being created in the database; I'm using the same approach to create the user as I did in seeds.rb.
If I run the test through the debugger, I can see the user in the database and verify that the page is loading. But still the authentication fails; I can verify this because if I change the assertion to test for the failure case, the test passes:
# verify that the authentication actually failed
assert page.has_content?('Invalid email or password.')
I'm used to Rails 2, & using Selenium for this sort of testing, so I suspect I'm doing something daft. Could someone please point me in the right direction here?
I was having the same issue and found a thread with a solution:
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
For the DatabaseCleaner stuff to work you'll need to include the database_cleaner gem. If you haven't used it before, you may need to rake db:test:prepare before rerunning your tests. I hope this works for you, too!
I've run into a similar problem before. Setting the password directly has some weird effects because it's supposed to be encrypted and stored with a salt--sometimes it works for me and other times it doesn't. I have a hard time remembering which specific cases were problematic. I'd recommend the following, in this order (for simplicity)
Verify that the password field is getting filled in properly and passed as the right param (not necessary if you're using Devise's autogenerated view and haven't touched it)
if your site can run in development mode (i.e. no log in bugs), then just boot it up and log in manually
If not, insert debugger as the first line in your sessions_controller. Then check params and make sure the password is correct and in params[:user][:password].
If you didn't override Devise's sessions_controller, then you can find your Devise path with bundle show devise. Then look for the create action within (devise path)/app/controllers/devise/sessions_controller.rb
Change your test setup to create a user through the web interface, to ensure the password gets set properly, then try running your test again
I had the same issue with a setup fairly similar to yours. In my case, switching to ActiveRecord sessions in the initializer solved the problem.
Additionally, make sure you call #user.skip_confirmation! if you are using the "confirmable" module in devise.

Help debugging my session? Rails 3 ActionDispatch::Cookies::CookieOverflow

Even though I'm pretty sure I know why this error gets raised, I don't seem to know why or how my session is exceeding the 4KB limit...
My app was working fine, but once I deliberately started adding bugs to see if my transactions were rolling back I started getting this error.
To give some background, I'm busy coding a tournament application that (in this section) will create the tournament and then add some tournament legs based on the number of teams as well as populate the the tournament with some 'ghost fixtures' once the legs have been created.
The flash[:tournament] was working correctly before; using a tournament object, I have access to any AR validation errors as well as data that has been entered on the previous page to create the tournament.
TournamentController.rb
begin
<other code>
Tournament.transaction do
tournament.save!
Tournament.generate_legs tournament
Tournament.generate_ghost_fixtures tournament
end
flash[:notice] = "Tournament created!"
redirect_to :action => :index
rescue Exception => e
flash[:tournament] = tournament
redirect_to :action => :new, :notice => "There was an error!"
end
Tournament.rb
self.generate_ghost_fixtures(tournament)
<other code>
#Generate the ghost fixtures
#tournament_legs is a has_many association
tournament_legs_array = tournament.tournament_legs
tournament_legs_array.each do |leg|
number_of_fixtures = matches[leg.leg_code]
#For the first round of a 32 team tournament, this block will run 16 times to create the matches
number_of_fixtures.times do |n|
Fixture.creatse!(:tournament_leg_id => leg.id, :match_code => "#{leg.leg_code}-#{n+1}")
end
end
end
I can do nothing but speculate as to why my session variable is exceeding 4KB??
Is it possible that the tournament object I pass through the flash variable contains all the associations as well?
Here is the dump of my session once I get the error.
Hope this is enough info to help me out :)
Thanks
Session Dump
_csrf_token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
flash: {:tournament=>#<Tournament id: nil, tournament_name: "asd", tournament_description: "asdasd", game_id: 1, number_of_teams: 16, start_date: "2011-04-30 00:00:00", tournament_style: "single elimination", tournament_status: "Drafting", active: true, created_at: "2011-04-30 10:07:28", updated_at: "2011-04-30 10:07:28">}
player_id: 1
session_id: "4e5119cbaee3d5d09111f49cf47aa8fa"
About dependencies, it is possible. Also save an ActiveRecord instance in the session is not a recommended aproach. You should save only the id. If you need it in all your requests use a before filter to retrieve it.
You can read more why is a bad idea at: http://asciicasts.com/episodes/13-dangers-of-model-in-session
The generally accepted and recommended approach is to not use a redirect on error, but a direct render instead. The standard "controller formula" is this:
def create
#tournament = Tournament.new(params[:tournament])
if #tournament.save
redirect ...
else
render 'new' # which will have access to the errors on the #tournament object and any other instance variable you may define
end
end
class Tournament < ActiveRecord::Base
before_create :set_up_legs
end
On successful saving, you can drop all instance variables (thereby wiping the in-memory state) and redirect to another page. On failure (or exception) you keep the object in memory and render a view template instead (typically the 'new' or 'edit' form page). If you're using standard Rails validation and error handling, then the object will have an errors array that you can just display.
I'd also recommend you use ActiveRecord associations which automatically give you transactions. If you push all this into the model, e.g. a "set_up_legs" method or something, then you can use ActiveRecord error handling. This is part of the "skinny controller, fat model" paradigm.
in session_store.rb, uncomment the last line with :active_record_store
Now restart the server
I would convert the exception to string before assigning it to flash[:tournament] with 'to_s'.
I had the same error and it seems assigning an exception object to a session variabla like flash means it takes the whole stack trace with it into the session. Try it, worked for me.