I am learning how to send emails with ActionMailer in rails, and I am having trouble passing parameters into my OrderMailerPreview class. I need to pass an order_id into the new_order method so I can look up the appropriate order. Here is the code:
# Preview all emails at http://localhost:3000/rails/mailers/order_mailer
class OrderMailerPreview < ActionMailer::Preview
# Preview this email at http://localhost:3000/rails/mailers/order_mailer/new_order
def new_order(order_id)
#order = Shoppe::Order.find(order_id)
OrderMailer.new_order(order)
end
end
How can I get that order_id into this method?
ActionMailer::Preview is simply made to render previews with fake or seeded data and does not support param passing as far as I know. Rather it simply uses the url to locate files based on class / method. This corresponds with its intended use as a development aid.
If you need to use dynamic data in the mailer previews or for example make the preview viewable to end users you should instead create a controller which renders the mailer view.
class OrderMailerPreviewController < ApplicationController
# GET /mailers_previews/orders/:id
def show
#order = Order.find(params[:id])
render "mailers/order", layout: nil
end
end
Related
I'm very new to Rails, and I'm a little overwhelmed where I do simple things like create an API call. I've set up a route at /reports which has this controller:
class ReportsController < ApplicationController
#client = # Api-accessing gem
#all_reports = []
def self.request_report
begin
puts "Step 1:"
step1 = #client.request_report(opts = {"max_count" => 1})
step1_result = step1.parse
puts "Done!"
puts step1_result
rescue Excon::Errors::ServiceUnavailable => e
puts "Didn't work"
logger.warn e.response.message
retry
end
end # End request_report
request_report
end
This correctly calls the external API when I first load the /reports route, but when I refresh the page the code isn't re-run.
Perhaps I'm misunderstanding what controllers are used for? Am I meant to be putting this code somewhere else? Or is there a caching issue?
The only public API of controller are the actions which respond to a HTTP request. In your case get "/reports" => "reports#request_report" is a route which corresponds to the action request_report.
However actions are instance methods, not class methods:
class ReportsController
def request_report # self.request_report would make this a class method!
# #todo get reports from somewhere and
# return some sort of response.
end
# any method call here happens when the class is evaluated.
end
You are declaring the action as a class method and then calling it when the ReportsController class is evaluated. Sorry to say but just about everything about your controller is wrong.
The Rails convention would be to call the action index.
Controllers in Rails should only be instantiated by the router (or your test framework). So they are definatly the wrong place to put resuable bits and bobs. If you ever see someone doing ReportsController.new.foo or ReportsController.foo - fire them on the spot.
So where do you put external API calls?
If its a pretty trivial one-off you can place it in private method in your controller.
Some place API calls on the model layer - however that is debatable since ActiveRecord models already are supercharged to the gills with powers and responsibilities.
One solution that has worked well for me is Service Objects. They are easy to test and have a clear single responsibility.
class RequestReportService
def initalize(client)
#client = client
end
def call(opts = {})
begin
return #client.request_report(opts.merge("max_count" => 1))
rescue Excon::Errors::ServiceUnavailable => e
nil
end
end
end
class ReportsController
def index
#reports = RequestReportService.new(#client).call
end
end
To add to #max's excellent answer, you need to appreciate that Rails is based on a stateless protocol (HTTP)...
each request message can [only] be understood in isolation.
This means that if you want to create a set of controller actions, you have to appreciate that each call is going to create a new instance of your classes etc. This, coupled with the idea of a RESTful set of actions, should give you a basis from which to build your functionality.
--
#config/routes
scope constraints: { subdomain: "api" } do
resources :reports #-> http://api.url.com/reports
end
#app/controllers/reports_controller.rb
class ReportsController < ApplicationController
respond_to :json #-> requires "responders" gem
def index #-> instance method
#reports = Report.all
respond_with #reports #-> all reports
end
def show
#report = Report.find params[:id]
respond_with #report
end
end
I'll leave the service object stuff as I have no experience with it.
--
If you're pulling from an external API, you have several considerations:
Calls ideally need to be asynchronous (unless you use multi-threading)
Calls need to be made in the instance method
Your current pattern calls the API on the class, which is why you can't refresh it:
class ReportsController < ApplicationController
#client = # Api-accessing gem
#client is only invoked (I don't know why it works, as it should be a class variable) with the class.
So if you send a new request (which creates an instance of ReportsController), #client is going to be declared that one time.
To get it working correctly, #client needs to be defined with each instance method:
class ReportsController < ApplicationController
def index
#client = # Api-accessing gem
This way, each time you invoke ReportsController#index, a new API call will be made. Might seem trivial, but the data scope is massive.
Finally, you need to read up about MVC (Model View Controller):
This will show you how controllers are meant to be used in Rails applications etc.
Well I actually never seen anyone code like this in a rails controller. Rails is a mvp framework. Controller are use to negotiate between your model and the views. First of all, if you routed correctly to your controller like
get "/reports" => "request_report#reports"
your controller should have a method like the following
def request_report
#client = Client.find(params[:id])
end
And then the controller will render and display the view in your app/views/reports/request_report.html.erb with access to the #client variable you just search from your database.
I am not sure why you are calling the block request_report at the bottom of the page, it just doesn't make sense in a controller. And you certainly don't really need to write self in front of a controller method.
def self.request_report
your code
end
As for where to put your api controller, usually for an api controller, we can create new folders under controllers, so the structure will be like
app/controllers/api/v1/your_api_controller.rb
Then in your_api_controller.rb you will need to add namespace infront of your controller like this.
class Api::V1::ReportsController < ActionController::Base
end
It is the same with your routes, you will add namespace in your route.rb
namespace :api do
namespace :v1 do
get "/reports" => "request_report#reports"
end
end
I have the following working Preview class:
class UserMailerPreview < ActionMailer::Preview
def invite
USerMailer.invite
end
end
I'm trying to pass paramaters to the method like so:
localhost:3000/rails/mailers/user_mailer/invite?key1=some_value
The server seems to receive them:
Parameters: {"key1"=>"some_value", "path"=>"user_mailer/invite"}
But when trying to access them with the hash params, I get an error.
Can I access these parameters in a Preview method and if so - how?
I dug into the code behind the mailer preview system and discovered that, unfortunately, none of the request parameters are passed to the preview class, and are thus inaccessible to the preview.
The relevant controller action is in railties: Rails::MailersControlller#preview. Here, you can see it calling ActionMailer::Preview#call and just passing the name of the "email" (ie: the appropriate method in the preview).
I hacked my way through this one today and came up with this solution and blog post on extending ActionMailer.
# config/initializers/mailer_injection.rb
# This allows `request` to be accessed from ActionMailer Previews
# And #request to be accessed from rendered view templates
# Easy to inject any other variables like current_user here as well
module MailerInjection
def inject(hash)
hash.keys.each do |key|
define_method key.to_sym do
eval " ##{key} = hash[key] "
end
end
end
end
class ActionMailer::Preview
extend MailerInjection
end
class ActionMailer::Base
extend MailerInjection
end
class ActionController::Base
before_filter :inject_request
def inject_request
ActionMailer::Preview.inject({ request: request })
ActionMailer::Base.inject({ request: request })
end
end
Ok, so my main issue is I have implemented Mailboxer into our project to handle messaging and I am trying to write tests for it. However, I keep stumbling over and over again. I have attempted several different stub/mocks but have made no progress.
We have a conversations_controller.rb that relies on before_filters for setting all the instance variables necessary for doing each action. Then in the controller actions, the instance variables are referenced directly to do any sort of action or to return specific data.
Here is an example of our index action which returns all conversations in the "box" that is specified in the before_filter, of the mailbox also specified in another before_filter:
class ConversationsController < ::ApplicationController
before_filter :get_user_mailbox, only: [:index, :new_message, :show_message, :mark_as_read, :mark_as_unread, :create_message, :reply_message, :update, :destroy_message, :untrash]
before_filter :get_box
def index
if #box.eql? "inbox"
#conversations = #mailbox.inbox
elsif #box.eql? "sentbox"
#conversations = #mailbox.sentbox
else
#conversations = #mailbox.trash
end
end
And before filters:
private
def get_user_mailbox
#user = User.where(:user_name => user.user_name.downcase).where(:email => user.email.downcase).first_or_create
#mailbox = #user.mailbox if #user
end
def get_box
if params[:box].blank? or !["inbox","sentbox","trash"].include?params[:box]
params[:box] = 'inbox'
end
#box = params[:box]
end
So I guess I have 2 questions in one. First, how to I get my tests to generate the correct data #mailbox, #user, and #box that is needed for the index action. Next, how do I pass the fake parameter to set #box to different "inbox/sentbox/trash". I have tried controller.index({box: "inbox"}) but always get "wrong arguments 1 for 0" messages.
I have tried the following in various different ways, but always get nil:class errors which means that my instance variables are definitely not being set properly.
describe "GET 'index' returns correct mailbox box" do
before :each do
#user = User.where(:user_name => 'test').where(:email => 'test#test.com').first_or_create
#mailbox = #user.mailbox
end
it "#index returns inbox when box = 'inbox'" do
mock_model User
User.stub_chain(:where, :where).and_return(#user)
controller.index.should == #mailbox.inbox
end
end
Filters and callbacks are hard to test and debug. Avoid them when possible.
In this case I don't think your before_filter is necessary, thus no need to test it. The better home for the methods is model.
Check my refacoring:
class User < ActiveRecord::Base
delegate :inbox, :sentbox, :trash, to: :mailbox
end
class ConversationsController < ::ApplicationController
def index
#conversations = current_user.send get_box
end
private
def get_box
# your code
end
end
That's all. Should be enough.
You can then test regularly.
First of all, read the oficial documentation for rails testing: using data for testing and passing parameters to controlers is explained there.
To generate data for your tests you can:
fill your test database with some mailbox and users using rails fixtures or use something like factory girl
use mock objects to fake data. I personally use the mocha gem but there are others
I tend to use a combination of both, prefering mock objects when possible and falling back to factory girl when mocking needs too much code.
I am trying my hand on rails (4). I have done some Sinatra earlier.
I have a signup form, in which user can fill out his organization name, address and his own name, password etc. I have two tables - Users and Organizations, those table get populated with Signup data. So, I have two active records model users and organizations. My controllers looks as follows:
class SignupsController < ApplicationController
# GET /signup
def new
# show html form
end
# POST /signup
def create
#signup = Registration.register(signup_params)
respond_to do |format|
if #signup.save
format.html { redirect_to #signup, notice: 'Signup was successfully created.' }
else
format.html { render action: 'new' }
end
end
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def signup_params
params[:signup]
end
end
I do not have any Registration model (table in db). What I am looking for such Registration model (should I call it model?) where I can have something like:
class Registration
def self.register params
o = Organization.new params
u = User.new o.id, params
self.send_welcome_email params[:email]
end
def send_welcome_email email
#send email here
end
end
1) So where should I keep this Registration class?
2) Is this a correct approach for such situation? If not, then what is the best way to do it?
3) Having such class will effect running any unit tests?
4) I see there is file, signup_helper.rb what is the use of that in SignupsController
You can do include ActiveModel::Model in your model, and it will behave as a normal Model. You will be able to do validations, callbacks. Anything other than persisting to a database.
Have a look at this for more info.
class Registration
include ActiveModel::Model
def self.register params
o = Organization.new params
u = User.new o.id, params
self.send_welcome_email params[:email]
end
def send_welcome_email email
#send email here
end
end
To answer your questions:
1) I would move the Registration class to a Signup module (as it relates to the signup use case) in app/models:
# apps/models/signup/registration.rb
module Signup
class Registration
...
end
end
2) I like your approach. It's something I would do. It is not the "Rails way" but in my opinion it is superior.
3) No. Having a PORO (plain old ruby object) is currently fine for any unit tests and you can easily test it.
4) Helpers are an (in my opinion) old and obsolete way to share functionality between views (and controllers). Don't use them unless there is absolutely no other way (maybe some Library demands it ... ). It is alway better to use POROs like you did.
You don't have to include ActiveModel::Model unless you need it's functionality. E.g. validations. You can include individual pieces of its functionality in this case. E.g
include ActiveModel::Validations
I'm struggling a bit to find the right place for a helper method. The method basicly 'inspects' a User-model object and should return some information about the 'progress' of the user, eg. "You need to add pictures", "Fill out your address" or "Add your e-mail-adress". None of the conditions I'm checking for are required, it's just like a "This is your profile completeness"-functionality as seen on LinkedIn etc.
Each of these 'actions' have a URL, where the user can complete the action, eg. a URL to the page where they can upload a profile photo if that is missing.
Since I need access to my named routes helpers (eg. new_user_image_path) I'm having a hard time figuring out the Rails-way of structuring the code.
I'd like to return an object with a DSL like this:
class UserCompleteness
def initialize(user)
end
def actions
# Returns an array of actions to be completed
end
def percent
# Returns a 'profile completeness' percentage
end
end
And user it with something like: #completeness = user_completeness(current_user)
However, if I'm adding this to my application_helper I don't have access to my named routes helpers. Same goes if I add it to my User-model.
Where should I place this kind of helper method?
This is a similar problem to that of Mailers. They are models, and should not cross the MVC boundaries, but need to generate views. Try this:
class UserCompleteness
include ActionController::UrlWriter
def initialize(user)
end
def actions
# Returns an array of actions to be completed
new_user_image_path(user)
end
def percent
# Returns a 'profile completeness' percentage
end
end
But be aware you are breaking MVC encapsulation, which might make testing more difficult. If you can get away with some methods in the users helper instead of a class that might be better.
From the little i got your question i think you want a method which you can used in Controller as well as Views.
To Accomplish this simple add method in application_controller.rb and named it hepler_method
Example:-
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
#current_user ||= User.find_by_id(session[:user])
end
end
you can use method current_user in both Controller as well as views