I would like to send what is normally a view as the body of an email.
Whats the simplest way to do this?
eg: I have pages/reports.html.erb and I want to send the page that you'd see if you visited that path as the body of an email.
Is there some way to take the html that is rendered and assign it to the body variable of an email?
Additionally, I'd like to include the PDF version as an attachment. (I'm using wicked_pdf)
def reports
#email = params[:email]
#something here?
respond_to do |format|
format.html
format.pdf do
render :pdf => "usage_report"
end
end
end
note: I'm using rails 3.1 with mongoid 2 for the DB and sendgrid on heroku if that helps
EDIT: What I ended up doing:
replace #something here? with:
email_obj = {}
email_obj.to = params[:email]
email_obj.from = 'reports#company.com'
email_obj.subject = 'Report'
email_obj.body = render_to_string(:template => "pages/reports", :layout => false)
ReportsMailer.deliver_report(email_obj).deliver
and in the mailer class mailers/reports_mailer.rb
class ReportsMailer < ActionMailer::Base
default from: "from#example.com"
def deliver_report(email)
#email = email
mail( :to => #email.to,
:subject => #email.subject,
:from => #email.from)
end
end
and in reports_mailer/deliver_report.html.erb
<%= render :inline => #email.body %>
The rails mailer views are stored in a sub folder in views by the name of the mailer just like controller views. It is hard to say if this will work completely for you not knowing what the view/controller for that method looks like but what you could do is take the rendering for that page and turn it into a partial which you can then render in both the controller view and the mailer view as such:
given a partial: _my_partial.html.erb
within the views:
render "my_partial"
Using the partial you can even pass any variable that would be necessary for the view to be rendered to it with:
render "my_partial", :variable => variable
Update:
This may be of interest as well: Rendering a Rails view to string for email
Related
I would like to use an instance or variable like below in the mailer subject.
So I would like to get the user name or some other user detail in the subject, I tried several thing but I receive an error every time. I can't find documentation about this either.
mail(:to => "test#gmail, :subject => "current_user.name") or "<%= current_user.name %>"
class PositionMailer < ActionMailer::Base
default from: "test#gmail.com"
layout 'mailer'
def general_message(position, barge_name)
#position = position
#barge_name = barge_name
mail(:to => "1234#gmail.com, :subject => "#{barge_name}")
end
end
controller
def create
#position = Position.new(position_params)
if #position.save!
redirect_to #position
PositionMailer.general_message(#position, #barge_name).deliver
To pass a variable inside quotes you need to do it like this "#{variable.value}
:subject => "#{current_user.name}"
should do it if it has access to current_user
You will have to pass current_user to the mailer along with the position value.
So wherever you are calling that from add current_user, probably looks something like this.
PositionMailer.general_message(position, current_user).deliver
I am unable to get an email to generate when I try to specify a layout for a pdf I want to attach to an email using Wicked PDF. This is a rails 4 app.
When I use the following code, the application successfully sends the email with the pdf attachment, but without the desired styling:
def invoice_created(invoice_id)
#invoice = Invoice.find(invoice_id)
#subscription = Subscription.find(#invoice.subscription_id)
attachments['invoice.pdf'] = WickedPdf.new.pdf_from_string(
render_to_string(:pdf => "invoice", :template => 'invoices/show.pdf.erb'))
mail(to: 'you#example.com', subject: "Your Invoice is Ready")
end
But when I add the layout, nothing generates.
def invoice_created(invoice_id)
#invoice = Invoice.find(invoice_id)
#subscription = Subscription.find(#invoice.subscription_id)
attachments['invoice.pdf'] = WickedPdf.new.pdf_from_string(
render_to_string(:pdf => "invoice",
:template => 'invoices/show.pdf.erb',
:layout => 'pdf.html.erb'))
mail(to: 'you#example.com', subject: "Your Invoice is Ready")
end
In my controller, I display the pdf in the browser and it correctly uses the pdf.html.erb layout:
def show
#invoice = current_account.invoices.find(params[:id])
respond_to do |format|
format.html
format.pdf do
render :pdf => 'file_name',
:template => 'invoices/show.pdf.erb',
:layout => 'pdf.html.erb'
end
end
end
I'm not seeing any obvious errors in the dev log, other than the email file not being created.
I am seeing the following so it looks like it sees the layout but won't show it.
Rendered invoices/show.pdf.erb within layouts/pdf.html.erb (13.7ms)
Could there a problem with conflicting layouts?
If I try to generate several emails with the layout specified, then remove the layout code from the mailer, restart the server, request a new email, then the emails I previously requested get sent.
I tried adding
:show_as_html => params[:debug].present?
to the mailer to see if it would shed some light but this also caused the email not to be sent.
The problem code was in pdf layout:
<%= csrf_meta_tags %>
Not sure why I didn't receive any error messages in my app. I have my application set up to display errors with gem 'better_errors' which didn't display any errors. I set up a new application just to play with wicked_pdf, but didn add better_errors to the gem file.
Now I received the following error message:
undefined method `protect_against_forgery?'
Once this line was removed from the layouts/pdf/html.erb file the app correctly attached the styled pdf.
Here's a link to the sample wicked_pdf example I used to find the solution: https://github.com/stevejc/WickedPDF-Example
I know I could define instance variables e.g:
def user_register(username, email)
#username = username
#email = email
mail(:to => email, :subject => "Welcome!", :template_name => "reg_#{I18n.locale}")
end
But, is there a way to use local variables instead, just like passing :locals to partials?
As ronalchn pointed out, it's the render that has :locals, not the mail method. So, you need a direct access to the render method in order to pass the locals.
You can give a block to the mail and that way gain access to the render method, something like this:
mail(to: "your_mail#example.com", subject: "Test passing locals to view from mailer") do |format|
format.html {
render locals: { recipient_name: "John D." }
}
end
And now you should be able to use "Hello <%= recipient_name %>"
All options available in the mail method can be found at http://api.rubyonrails.org/classes/ActionMailer/Base.html#method-i-mail.
We know that render has a :locals option. However we can see that there is no :locals option available for mail. Therefore, no, there is no better way than to use instance variables (unless you want to use something hideous like globals or persistent database objects - don't do this).
Instance variables are what you are meant to use.
In Rails 5, you simply have to define instance variables using # in your method. You no longer have access to the locals property for this purpose.
class UserMailer < ApplicationMailer
def welcome_email(user_id:, to_email:, user_full_name:, token:)
# Mail template variables
#user = User.find_by(id: user_id)
#token = token
mail(:to => to_email,
:subject => MAILER_SUBJECTS_WELCOME,
:template_path => "user_mailer",
:template_name => "welcome_email")
end
end
Then you can just access them in your email template using <%= #user %> and <%= #token %>
You can actually use the locals option with mail, it's just a bit confusing and inconsistent as to how.
Once you use :locals you can then access these locals in the mail template using instance variables, e.g.
:locals => { :name => 'Jane' }
and then in the template
Dear <%= #name %>,
i want to render an action from another controller, but i get the error:
undefined method `formats' for nil:NilClass
<script>
$("#validate_company").live("keyup", function() {
$("#company_info").html("<%= escape_javascript(render(:controller => 'live_validation', :action => 'validate_client_company')) %>");
});
</script>
This is the Controller:
class LiveValidationsController < ApplicationController
def validate_client_company
if params[:first_name].length > 0
#client = Client.find_by_company(params[:company])
if #client.nil?
#message = "<img src='/images/accepted_48.png' alt='Valid Username'/>"
else
#message = "<img src='/images/cancel_48.png' alt='Invalid Username' /> Name taken"
end
else
#message = ""
end
render :partial => "message"
end
end
The partial _message is just
<%= #message %>
You seem to be mixing up stuff.
You have html inside your controller-method? That should be in your view. For each controller-method, normally a view with the same name is rendered, except if you explicitly call render from within your controller-method.
You do not write html in your controller. You write html in the view, and sometimes you have helpers to make your views more readable.
Secondly, in your first piece of code, which is some view-code, i hope. The view is prepared at server-side and then sent to the client. You can render another view, a partial, from a view. But this does not load data live.
How i would fix this. Inside your views where you want to dynamically render the validation:
<script>
$("#validate_company").live("keyup", function() {
$("#company_info").load("<%= url_for :controller => 'live_validations', :action => 'validate_client_company' %>");
});
</script>
Then inside your controller you write:
class LiveValidationsController < ApplicationController
def validate_client_company
if params[:first_name].length > 0
#client = Client.find_by_company(params[:company])
#error = #client.nil? ? :valid_username : :invalid_username
else
#error = nil
end
render :partial => "message", :layout => false
end
end
Inside your app/helper/live_validations_helper.rb you add a method
def get_validation_message(error)
if error == :invalid_username
image_tag('/images/cancel_48.png', :alt => 'Invalid Username') + "Name taken"
elsif error == :valid_username
image_tag('/images/accepted_48.png', :alt => 'Valid Username')
end
end
and inside your message view you write something like:
<%= get_validation_message(#error) %>
render :action => does not run the associated controller method.
It simply renders the template that Rails would, by default, have associated with the action. That is to say, if the action validate_client_company simply called render without passing any arguments, Rails would look for a template in a folder with the same name as the controller and with a name with the same name as the action. Calling render :action => simply looks for that same template and renders it.
Best guess is that Rails cannot find a template named validate_client_company. I expect that none exists, because the action validate_client_company renders a partial named message instead of rendering a template with the default name for that action.
In the action which renders the script, you need to set up the instance variables and then in the template for that action you need to use the following:
render :partial => 'live_validations/message'
It certainly makes sense to have mini MVC stacks within the larger MVC stack, so that you can run sub-actions within running larger actions. For this scenario, you may wish to look at Cells. However, you cannot do this with Rails by itself.
You probably want something like this:
<script>
$("#validate_company").live("keyup", function() {
$("#company_info").load("<%= url_for :controller => 'live_validation', :action => 'validate_client_company' %>");
});
</script>
But the rest of your code is kinda of messed up... Rendering a partial from inside a controller?
Got it to work!
<script>
$("#validate_company").live("keyup", function() {
$("#company_info").load("/live_validations/validate_client_company",{company:$('#validate_company').val()});
});
</script>
I am trying to use an existing partial in an actionmailer template, something like..
My merchant_offer.txt.html.erb
<%= render :partial => "offers/offer", :locals => {:offer => #offer} %>
Notifier.rb (my mailer class):
def merchant_offer(offer)
subject "New Offer from #{offer.merchant.name}"
from "xxx#gmail.com"
recipients xxx#
sent_on Time.now
body :offer => offer
end
The offer partial in in another view folder called offers
But it throws a missing tempalate error.
Is there a way to re-use existing view partial in in mailer tempalates?
Thanks
You should be able to render partial from mailer templates.
I believe the error is in your merchant_offer view. Try renaming 'merchant_offer.txt.html.erb' to 'merchant_offer.html.erb'