ActionMailer instance method being used like class method - ruby-on-rails

Here is a brief snippet from the guide on ActionMailer
class UserMailer < ActionMailer::Base
default :from => "notifications#example.com"
def welcome_email(user)
#user = user
#url = "http://example.com/login"
mail(:to => user.email,
:subject => "Welcome to My Awesome Site")
end
end
And in the controller
class UsersController < ApplicationController
# POST /users
# POST /users.xml
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
# Tell the UserMailer to send a welcome Email after save
UserMailer.welcome_email(#user).deliver
format.html { redirect_to(#user, :notice => 'User was successfully created.') }
format.xml { render :xml => #user, :status => :created, :location => #user }
else
format.html { render :action => "new" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
end
So why is Rails trying to confuse rubyist with instance methods as class methods? I assume they've overridden missing method, but it just serves to confuse! Or am I missing something here?
ie why not define welcome_email as def self.welcome_email(user)

If it was #self.welcome_email you'd have to create an instance of the class yourself, which requires some configuration for all the default params etc. Rails is just providing factory methods of the same name.
From a quick look at the source code, you're right, it does seem to use method_missing, where the mailer is actually created with:
mailer = TheMailer.new(:welcome_email, *args)
Rails does a lot of "voodoo" things like this, generally just to save the amount of code you write. Just changing #welcome_email to be a class method would not give you an instance.

Related

Validation fails if redirect after save is anywhere other than the saved object

I am trying to make it so that when a user signs up (i.e: a new user is created) it will redirect them to the tutorial. However, when a user signs up it will give an error message saying that the username and email must be unique (even if they are) and render the 'new' page again.
This works fine if I redirect to #user instead.
This is my controller:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
login(#user)
format.html { redirect_to "static/tutorial", success: 'Congratulations on starting your journey!' }
format.json { render json: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
And these are the relevant lines in User.rb:
validates_confirmation_of :plain_password
validates_presence_of :name, :username, :email
validates_presence_of :plain_password, :on => :create
validates_uniqueness_of :email, :username
When I see that I am quite scared
validates_presence_of :plain_password, :on => :create
Are you saving unencrypted password in your database ?
Regarding your issue you may want to consider using respond_to / respond_with
class UsersController < ApplicationController
respond_to :html, :json
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
# set sessions
end
respond_with #user, location: 'static/tutorial'
end
end
Read this blog post http://bendyworks.com/geekville/tutorials/2012/6/respond-with-an-explanation
I figured it out - partly.
I needed to add a route to the tutorial in my routes file:
match "tutorial" => "static#tutorial"
And then redirect to that instead of a string:
format.html { redirect_to tutorial_path, success: 'Congratulations on starting your journey!' }
There is probably a full explanation to the theory behind this but I'll leave that for someone else to answer. This is how I solved it.

Wrong naming convention?

I've never used Unit Test before but only Rspec. So maybe here's some silly mistake.
I have CountriesController:
class CountriesController < ApplicationController
def create
#country = Country.new(params[:country])
respond_to do |format|
if #country.save
format.html { redirect_to(#country, :notice => 'Country was successfully created.') }
format.xml { render :xml => #country, :status => :created, :location => #country }
else
format.html { render :action => "new" }
format.xml { render :xml => #country.errors, :status => :unprocessable_entity }
end
end
end
end
and test countries_controller_test.rb for it :
class CountriesControllerTest < ActionController::TestCase
should_not_respond_to_actions :new => :get, :destroy => :get
setup do
#country = countries(:one)
end
test "should create country" do
assert_difference('Country.count') do
post :create, :country => #country.attributes.merge({ :code => Time.now.to_s })
end
assert_redirected_to country_path(assigns(:country))
end
...
end
As far as I know the name convention, everything looks ok for me but I got an error:
1) Error:
test_should_create_country(CountriesControllerTest):
RuntimeError: #controller is nil: make sure you set it in your test's setup method.
What might be a problem here? Thanks
Where is the test's #controller supposed to come from ? If it's created by ActionController::TestCase, then you might want to call the superclass's setup in your own setup function ?

Rails: undefined method `total_price' for nil:NilClass

I'm building a concert ticket sales application with Rails 3.0.4, working primarily with the Agile Web Development tutorial (http://pragprog.com/titles/rails3/agile-web-development-with-rails) and trying to incorporate Ryan Bate's order purchase method (http://railscasts.com/episodes/146-paypal-express-checkout). Everything works with the following in orders_controller.rb:
def create
#order = Order.new(params[:order])
#order.add_line_items_from_cart(current_cart)
#order.ip_address = request.remote_ip
respond_to do |format|
if #order.save
Notifier.order_received(#order).deliver
format.html { redirect_to(calendar_url, :notice => 'Thank you for your order.') }
format.xml { render :xml => #order, :status => :created, :location => #order }
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
else
format.html { render :action => "new" }
format.xml { render :xml => #order.errors, :status => :unprocessable_entity }
end
end
But when I add "&& #order.purchase" to the conditional clause, with the order.rb model as follows:
class Order < ActiveRecord::Base
#...
belongs_to :cart
#...
def price_in_cents
(cart.total_price*100).round
end
def purchase
response = GATEWAY.purchase(price_in_cents, credit_card, purchase_options)
cart.update_attribute(:purchased_at, Time.now) if response.success?
response.success?
end
#...
end
I receive an "undefined method `total_price' for nil:NilClass" error. I can get around this by adding
#order = current_cart.build_order(params[:order])
to the orders "create" method, but this messes up the "order_received" notification by somehow preventing the pertinent order information (in this case "#order.line_items") from rendering in the e-mail text.
The "cart" object is being set to nil somewhere along the way, but removing
Cart.destroy(session[:cart_id])
from the order "create" method does not fix the problem.
Anyone got a clue for this noob?
It doesn't look like the Cart object is ever actually specified in the belongs_to relation, you need to either do #order.cart = current_cart, or current_cart.order = Order.new, or something along those lines.

ActiveResource error handling

I have been searching for a while and yet I am not able to find a satisfactory answer as yet. I have two apps. FrontApp and BackApp. FrontApp has an active-resource which mimics a model in BackApp. All the model level validations live in BackApp and I need to handle those BackApp validations in FrontApp.
I have following active-resource code:
class RemoteUser < ActiveResource::Base
self.site = SITE
self.format = :json
self.element_name = "user"
end
This mimics a model which is as follows
class User < ActiveRecord::Base
attr_accessor :username, :password
validates_presence_of :username
validates_presence_of :password
end
Whenever I create a new RemoteUser in front app; I call .save on it. for example:
user = RemoteSession.new(:username => "user", :password => "")
user.save
However, since the password is blank, I need to pass back the errors to FrontApp from BackApp. This is not happening. I just don't understand how to do that successfully. This must be a common integration scenario; but there doesn't seem to be a good documentation for it?
My restful controller that acts as a proxy is as follows:
class UsersController < ActionController::Base
def create
respond_to do |format|
format.json do
user = User.new(:username => params[:username], :password => params[:password])
if user.save
render :json => user
else
render :json => user.errors, :status => :unprocessable_entity
end
end
end
end
end
What is it that I am missing? Any help will be much appreciated.
Cheers
From rails source code I figured out that the reason ActiveResource didn't get errors was because I wasn't assigning errors to "errors" tag in json. It's undocumented but required. :)
So my code should have been:
render :json => {:errors => user.errors}, :status => :unprocessable_entity
In the code:
class UsersController < ActionController::Base
def create
respond_to do |format|
format.json do
user = User.new(:username => params[:username], :password => params[:password])
if user.save
render :json => user
else
render :json => user.errors, :status => :unprocessable_entity
end
end
end
end
end
try to replace
user = User.new(:username => params[:username], :password => params[:password])
with
user = User.new(params[:user])
Your active-resource model pass the params like the hash above:
:user => { :username => "xpto", :password => "yst" }
This solution worked for me: https://stackoverflow.com/a/10051362/311744
update action:
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json {
render json: #user.errors, status: :unprocessable_entity }
end
end
end
Calling controller:
#remote_user = RemoteUser.find(params[:id])
if (#remote_user.update_attributes(params[:remote_user]))
redirect_to([:admin, #remote_user], notice: 'Remote user was successfully updated.')
else
flash[:error] = #remote_user.errors.full_messages
render action: 'edit'
end

Simple Authlogic Question: JSON login?

Working from the railscast #160 base code I've set up a very simple site that allows me to log in, out and register an account. (Its almost identical except that I've removed the 'username' from the users migrate table and relevant views so only an email address is required)
I'm now trying to create a new log in action so that I can log in via JSON.
I'd like to be able to send a get request to http://app:3000/apilogin?email=my#email.com&password=p4ssw0rd and have the rails app store the IP address the request came from (if the log in was correct) and send a relevant response (in JSON).
So far I have added a section to controllers/user_sessions_controller.rb so that it goes:
class UserSessionsController < ApplicationController
#...
def new_api
respond_to do |format|
format.json
end
end
end
To routes.rb:
map.apilogin "apilogin", :controller => "user_sessions", :action => "new_api"
But I'm at a loss as to what to put in views/user_sessions/new_api.json.rb! Can you help?
You don't need to define a view at all - just return appropriate json from the controller.
def new_api
#user_session = UserSession.new({:email => params[:email], :password => params[:password]})
respond_to do |format|
if #user_session.save
format.json { render :json => {:success => true} }
else
format.json { render :json => {:success => false, :message => 'incorrect username or password'}, :status => :unauthorized }
end
end
end
You can do something like this:
def new_api
respond_to do |format|
format.json { render :json => user.slice(:name).to_json }
end
end
Or you can also generate the JSON in views/user_sessions/new_api.json.erb as you would write normal erb code. Not a good idea though:
{"name":"<%= #user.name %>"}

Resources