How to modify actionmailer email html_part before sending - ruby-on-rails

I have everything at the point where I'm about to send out the email but I need to modify all links to include Google Analytics attributes. The problem is that if I try and read/write the html_part.body of the email, the entire html string somehow becomes encoded and doesn't display the email properly (i.e. <html> becomes <html>). I have logged the html_part.body.raw_source in the logger and it shows as proper unencoded HTML, it's only when the email is actually sent does the encoding occur.
EBlast.rb (ActionMailer)
def main(m, args={})
# Parse content attachment references (they don't use helpers like the layout does)
# and modify HTML in other ways
m.prep_for_email self
#email = m # Needed for helper methods in view
mail_args = {
:to => #user.email,
:subject => m.subject,
:template_path => 'e_blast',
:template_name => 'no_template'
}
mail_args[:template_name] = 'main' if m.needs_template?
m.prep_for_sending mail(mail_args)
end
Email.rb
def prep_for_sending(mail_object)
if mail_object.html_part
# If I simply do a 'return mail_object', the email sends just fine...
# but the url trackers aren't applied.
# Replace the content with the entire generated html
self.content = mail_object.html_part.body.decoded
# Add Google analytics tracker info to links in content
apply_url_tracker :source => "Eblast Generator", :medium => :email
# Replace the html_part contents
mail_object.html_part.body = content
# At this point, mail_object.html_part.body contains the entire
# HTML string, unencoded. But when I send the email, it gets its
# entities converted and the email is screwed.
end
# Send off email
mail_object
end

Looks like I'm answering my own question again - I'm on a roll this week.
Apparently setting the body directly creates some odd attribute called 'body_raw' instead of replacing the raw_contents of the html_part. So basically I ended up having a duplicate part embedded in the mail object (I don't know why it does this). Creating a separate Mail::Part and assigning it to html_part just added another part instead of replacing html_part! WTF?!
New Edit: Scratch my last remark about String.replace. It looked like it was working but when I went to another computer and tested it, the same problem of duplication occurred.
Another Edit: Finally?
Before I executed the apply_url_tracker method I had reset the content of the email (for the purposes of changing all the links in the rendered view). I don't have any idea why that screws with the Mail object considering the message should already have been rendered but changing my methodology to the following has fixed the duplication of email parts and their subsequent 'reencoding'. I no longer change the content attribute, I only change the html_part:
def prep_for_sending(message)
if message.html_part
# Replace the html raw_source
message.html_part.body.raw_source.replace apply_url_tracker(message.html_part.body.decoded, :source => "Eblast Generator", :medium => :email)
end
message
end
Clarification:
Even though the call to mail() produces a Mail object with fully rendered HTML/Text parts (i.e., fully rendered views), changing the attribute that is USED by those views (in my case, the 'content' attribute) screws up the final send. Don't modify your model before sending, JUST MODIFY THE MAIL PART DIRECTLY.

Related

Sanitizing Trix input

Under Rails 5.1 I am using Trix to allow users to edit their 'legal conditions'. Then I am trying to sanitize this 'legal' parameter in my controller before the user record is updated, but end up with :
undefined method `sanitize'
Here the code :
params[:user][:legal] = sanitize params[:user][:legal], tags: %w(strong div strong br li ul)
def user_params
params.require(:user).permit(:presentation, :linktowebsite, :legal)
end
Don't see anything different than normal usage shown here : http://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html
You are not using sanitize correctly. sanitize is used in the view, not in the controller.
To use it correctly, your model should allow a field to save html input from the user, but you want to "clean" it when it's used in the view so that unsafe or non-whitelisted tags/attributes is prevented from being sent/displayed to the user.
If you are looking to remove html tags/attributes before it gets saved, you may want to look at strip_tags.
strip_tags("Strip <i>these</i> tags!")
# => Strip these tags!
strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
# => Bold no more! See more here...
strip_tags("<div id='top-bar'>Welcome to my website!</div>")
# => Welcome to my website!

Why do I have to specify text format to render template with ActionMailer?

I'm using ActionMailer 3.0.7
According to the docs plain text emails are the default. So if I have an EnquiryNotifier mailer with a notify method then I expect that app/views/enquiry_notifier/notify.text.plain.erb will be rendered.
If I simply use mail(someparams) within the notify method then the body of the email is empty.
I read that ActionMailer is meant to scan the view directory to look for all types of templates.
However, if I specify the format within a block and do
mail(:to => 'somebody', :subject => 'something') do |format|
format.text
end
then my template notify.text.plain.erb does get rendered.
Maybe unrelated: If I don't specify the format but rename the template to notify.erb then it works but the email is sent as text/html.
Here's what seems to work for me in rails 3.0.6:
I don't specify a format at all in my mailer class, I just let it find the view automatically.
I name my view "notify.text.erb"

Sending an html attachment with ActionMailer 3

I am trying to send an email which contains a single HTML attachment. The problem is that the attachment html is appearing in the body of the email, and the view is showing up as the attachment. Pretty much the opposite of what I expected.
I am able to send attachments of other types properly, but when trying to send a single attachment that is HTML, it consistently is displayed rather than 'attached'.
class Notifier < ActionMailer::Base
default :from => "from#example.com"
def welcome(email)
attachments['this is an html file.html'] = "<b>yeah this is html!</b>"
mail(:to => email, :subject => "Attempting an attachment")
end
end
And my app/views/notifier/welcome.html.erb
Hi there! This is <b>html</b> within a view
The resulting email looks like this:
(notice the attachment html is actually displayed in the body of the email)
This is a feature of many email clients that if they can display attachments inline they will, so the user doesn't have to open a separate program just to see what's inside. To be honest though it's more meant for images.
If you want to send an html file, you're going to need to add it to a zip or other archive and send that instead.

rails mailer with different layouts

I use one layout for all my emails in my Notifier model (20+ emails)... however sometimes I just want to send a plain text email with no layout or html at all. I can't seem to be able to figure out how? If I try to send a plain text email i still get the layout, and all the HTML in the email.
I'm using Rails 2.3.8.
I read about this monkey patch here... but it seemed to indicate a newer version of rails had over come this? And I don't really wanna monkey patch if I can avoid one.
Rails - setting multiple layouts for a multipart email with mailer templates
layout "email" # use email.text.(html|plain).erb as the layout
def welcome_email(property)
subject 'New Signup'
recipients property.email
from 'welcome#test.com'
body :property => property
content_type "text/html"
end
def send_inquiry(inquire)
subject "#{inquire.the_subject}"
recipients inquire.ob.email
from "Test on behalf of #{inquire.name} <#{inquire.email}>"
body :inquire => inquire
content_type "text/plain"
end
I also have 2 files.
email.text.html.erb
email.text.plain.erb
It always uses text.html.erb... even if the content_type is "text/plain"
edit: Figured it out, the layouts follow a different naming scheme to the email templates. Just rename them as follows:
layout.text.html.erb => layout.html.erb
layout.text.plain.erb => layout.text.erb
I also made the mistake of manually defining the parts, if you use this:
part :content_type => 'text/plain',
:body => render_message('my_template')
Then Rails can't determine the content_type for your part and it assumes it's HTML.
After I changed those two things it worked for me!
original reply follows..
I've struggled with this question many times in the past, usually ending up with some sort of non-dry quick and dirty solution. I always thought I was the only one with this problem because Google turns up exactly nothing useful on the subject.
This time I decided to dig into Rails to figure it out but so far without much success, but maybe my findings will help someone else figure this out.
What I found was that in ActionMailer::Base the #render_message method is tasked with determining the proper content_type and should assign it to #current_template_content_type. #default_template_format then either returns the proper mime type for the layout or, if #current_template_content_type isn't set, it will default to :html.
This is what ActionMailer::Base#render_message looks like in my app (2.3.5)
def render_message(method_name, body)
if method_name.respond_to?(:content_type)
#current_template_content_type = method_name.content_type
end
render :file => method_name, :body => body
ensure
#current_template_content_type = nil
end
The trouble is that method_name appears to be a string (the name of the local view, in my case "new_password.text.html") and strings of course do not respond_to #content_type, meaning #current_template_content_type will always remain nil, and so the #default_template_format will always default to :html.
Not much closer to an actual solution, I know. The ActionMailer internals are just too opaque for me.
OK, not sure if this works, but it seems the default content_type is text/plain, so you would only need to set the content type if you want something other than text/plain.
Try this:
def send_inquiry(inquire)
subject "#{inquire.the_subject}"
recipients inquire.ob.email
from "Test on behalf of #{inquire.name} <#{inquire.email}>"
body :inquire => inquire
end
I still think you should consider this:
layout "email", :except => [:send_inquiry]
I would use the above because the plain text email does not seem to have a 'layout', only the actual content you want to send.
I found this that I think could be useful.
http://blog.blazingcloud.net/2009/11/17/simple-email-form-with-actionmailer/
He makes use of renaming the view templates for different content types.

How do I get the base URL (e.g. http://localhost:3000) of my Rails app?

I'm using Paperclip to allow users to attach things, and then I'm sending an email and wanting to attach the file to the email. I'm trying to read the file in and add it as an attachment, like so:
# models/touchpoint_mailer.rb
class TouchpointMailer < ActionMailer::Base
def notification_email(touchpoint)
recipients "me#myemail.com"
from "Touchpoint Customer Portal <portal#touchpointclients.com>"
content_type "multipart/alternative"
subject "New Touchpoint Request"
sent_on Time.now
body :touchpoint => touchpoint
# Add any attachments the user has included
touchpoint.assets.each do |asset|
attachment :content_type => asset.file_content_type,
:body => File.read(asset.url)
end
end
end
This gives me the following error No such file or directory - /system/files/7/original/image.png?1254497688 with the stack trace saying it's the call to File.read. When I visit the show.html.erb page, and click on the link to the image, which is something like http://localhost:3000/system/files/7/original/image.png?1254497688, the image is displayed fine.
How can I fix this problem?
Typically root_url should provide this.
File.read is expecting a file path, not a url though. If you are generating the images, you should call the image generating code and return the bytes of the generated image instead of calling File.read(…)
asset.url returns the URL to the file. This is usually /system/classname/xx/xx/style/filename.ext. You'd put this in an image_tag.
You want asset.path. It returns the full path to the file, which will usually be something like /home/username/railsapp/public/system/classname/xx/xx/style/filename.ext
HTH.
request.env["HTTP_HOST"]
I don't know why this one line of code is so elusive on the web. Seems like it should be up front and center.
as ZiggyTheHamster is saying: the asset.url is the generated url that would be used on webpages (which is why you're getting the unix-style directory slashes, as pointed out in the comments.)
asset.path should give you the OS-aware path to the file, but even that isn't needed with paperclip.
Paperclip::Attachment is already an IOStream.
You just need :body => asset like so:
touchpoint.assets.each do |asset|
attachment :content_type => asset.file_content_type,
:body => asset
end

Resources