pass extra instance vars to devise_invitable email template - ruby-on-rails-3

I'm overriding the devise_invitable controller and in my create method I'd like to pass extra values to the invitations_instructions email template. For example group name, has anyone been successful at this, if so please give me some clues here.
what I've tried...
my #group in my Users::InvitesController < Devise::InvitationsController create method is undefined in the email template.
tried to add :skip_invitation => true in my create and then send the email manually like...
self.resource = resource_class.invite!(params[resource_name], current_inviter, :skip_invitation => true)
::Devise.mailer.invitation_instructions(self.resource).deliver
but this gives the wrong number of arguments so there is something I'm not reading correctly from the documentation.
UPDATE - possible solution
The only way appears to be this, but I'm curious if there is a better way that uses the templates provided and devise mailer
in my /app/controller/users/InvitesController#create
(inherits from InvitationsController)
self.resource = resource_class.invite!(params[resource_name], current_inviter) do |u|
u.skip_invitation = true
end
UserMailer.invitation_instructions(self.resource, current_inviter, #object).deliver
where UserMailer is my general (standard) action mailer and goes something like...
def invitation_instructions(resource, inviter, object)
#resource = resource
#object = object
#inviter = inviter
mail(:to => resource.email, :subject => 'New invitation from ' + inviter.first_name)
end

There is a cleaner way to achieve the solution that you're looking for, and that is to use Devise's own procedures for overriding mailer templates.
First create a custom mailer that extends from Devise::Mailer:
class MyMailer < Devise::Mailer
helper :application # gives access to all helpers defined within `application_helper`.
include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`
default template_path: 'devise/mailer' # to make sure that your mailer uses the devise views
end
Then, in config/initializers/devise.rb, set config.mailer to "MyMailer". This is going to allow you to override ANY email that devise sends out and customize to your liking.
Then for you, you can override invitable_instructions like this:
def invitation_instructions(record, token, opts={})
# Determine a way to set object -- likely through a query of some type
#object = get_object_for(record)
opts[:subject] = 'New invitation from ' + inviter.first_name
super
end
The main sticking point from your example was passing in extra data to set #group/#object. To do that, I would personally go with some type of query within the mailer (not clean, but it is encapsulated and therefore less "magical") to retrieve those objects.
Additionally, if you wanted to use custom email templates instead of devise's, you can simply add them to the app/views/my_mailer/ directory and devise will prefer emails in that directory over emails from the gem.

Related

Rails + Devise - Session Controller explaination

I am playing around with Devise in a project, and am just trying to better understand how it all works. The Sessions controller in particular is doing a few things that I don't understand:
class Devise::SessionsController < ApplicationController
def new
# What benefit is this providing over just "resource_class.new"?
self.resource = resource_class.new(sign_in_params)
clean_up_passwords(resource)
# What is "serialize_options" doing in the responder?
respond_with(resource, serialize_options(resource))
end
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
...
protected
...
def serialize_options(resource)
methods = resource_class.authentication_keys.dup
methods = methods.keys if methods.is_a?(Hash)
methods << :password if resource.respond_to?(:password)
{ :methods => methods, :only => [:password] }
end
def sign_in_params
devise_parameter_sanitizer.sanitize(:sign_in)
end
end
I assume that these methods are adding some sort of security. I'd just like to know what exactly they are protecting against.
The implementation of devise_parameter_sanitizer is creating a ParameterSanitizer instance. I often find looking at tests to be helpful in understanding the purpose of code; and this test I think illustrates it best-- since you're creating a new user, you don't want to allow users to assign any value they want to any parameter they want, so sanitize means "strip out any attributes other than the ones we need for this action". If this wasn't here, and you had a role attribute, a user could send a specially-crafted POST request to make themselves an admin when signing up for your site. So this protects against what's called, in the general case, a mass assignment vulnerability. Rails 3 and Rails 4 protect against this in different ways but the protections can still be turned off, and I'm guessing Devise is trying to set some good-practice defaults.
The serialize_options method is creating a hash of options to support rendering to XML or JSON. I found this out by looking at the implementation of responds_with, which calls extract_options! which uses the last argument as options if the last argument is a Hash. The documentation for responds_with says "All options given to #respond_with are sent to the underlying responder", so I looked at ActionController::Responder, whose documentation explains the process it takes to look for a template that matches the format, then if that isn't found, calling to_#{format}, then calling to_format. There's a view for HTML, and ActiveRecord objects respond to to_xml and to_json. Those methods use those options:
The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
attributes included, and work similar to the +attributes+ method.
To include the result of some method calls on the model use <tt>:methods</tt>.
So this keeps you from exposing more information than you might want to if someone uses the XML or JSON format.

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.

Add variable to a devise message

I need to pass a variable to a devise message, devise.registrations.signed_up like :
devise.en.yml:
signed_up: "Welcome to %{my_var}"
app/controllers/users/registrations_controller.rb:
def create
set_flash_message :notice, :signed_up, :app_name => "my app name"
super
end
It gives the error: missing interpolation argument because in the super class the set_flash_message sets the message without the variable.
Is there a way to this with devise?
You might want to hack into the def create of Devise::RegistrationsController directly instead of subclassing it and set the flash message there itself.
So, In a way, instead of calling super you can simply paste in the code from the devise create method and set the flash message to whatever you want there itself.

Changing devise flash messages from notice to error

I know that you can override the default devise controllers and I did so for the Registrations and Sessions Controller. I know that you can also change the text for the flash messages in devise under locale. However, I am not sure how to change the type of flash message showing for the sessions controller when there is an invalid combination of username and password.
The create method looks like
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
I suspect that the validation is done during the follow call
warden.authenticate!(auth_options)
But this is where I am not sure how to overwrite that in my app.
Also, I think it is a complex override for such a simple use case of changing the color of a flash notice.
Any insights would be much appreciated.
Thanks!
Nick
You can do it with custom failure app. As you can see this flash message is setting right here So you can change it in your custom failure app.
So at first you inherit your failure app from Devise's one:
class CustomFailure < Devise::FailureApp
def recall
env["PATH_INFO"] = attempted_path
flash.now[:error] = i18n_message(:invalid)
self.response = recall_app(warden_options[:recall]).call(env)
end
end
place this file somewhere in your app and say Devise to use it like this (config/initializers/devise.rb):
config.warden do |manager|
manager.failure_app = CustomFailure
end

RoR 3.0.9 - Passing a variable from routes.rb to application_controller.rb or viceversa

I have a multi site project that changes from one to another by changing two variables, one is inside routes.rb and the other in application_controller.rb. Is it possible to pass variables between these files so that I only have to change a parameter to achieve the change required?
On my routes.rb file I use this variable to assign the correct controller the routes it should use. For example:
def showsite
"mysite1"
end
root :to => "#{showsite}#index"
And on application_controller.rb I use the same parameter to get the domain of the site, some layouts it should use and another things. For example:
before_filter :set_defaults
def showsite
"mysite1"
end
def set_defaults
if "#{showsite}" == "mysite1"
#domain = 'mysite1.com'
elsif "#{showsite}" == "mysite2"
#domain = 'mysite2.com'
else
#domain.nil?
end
end
def special_layout
"#{showsite}"
end
Every time I want to show a different version of the project I need to change two variables. I know its not a lot but I have to do it many times a day. Im pretty new on RoR, if there is a better solution please guide me to it. Thanks!
Why not use a Rails Initializer. E.g. the following:
File: `config/initializers/showsite.rb`
with the following content:
MyApp::Application.config.showsite = 'mysite1'
Then you should be able to use:
def showsite
MyApp::Application.config.showsite
end
and similar in your routes file.