What is the right way to embed image into email using Rails? - ruby-on-rails

What is the right way to embed an image into email using Rails?

I combined the answer from Oksana with a custom helper approach and got the following to work quite nicely.
app/helpers/email_helper.rb
module EmailHelper
def email_image_tag(image, **options)
attachments[image] = File.read(Rails.root.join("app/assets/images/#{image}"))
image_tag attachments[image].url, **options
end
end
app/mailers/base_mailer.rb
class BaseMailer < ActionMailer::Base
helper(EmailHelper)
end
app/mailers/my_mailer.rb
class MyMailer < BaseMailer
def send_my_mail(email)
mail to: email, subject: "My Subject"
end
end
Then for example where I want to attach the company logo in my email layout file I would use
app/views/layouts/email.html.erb
<%= email_image_tag("company_logo.png") %>
Note the **options makes the tag more extensible but it will only work in ruby >=2. To make this work in ruby < 2 you will have to use the older way of handling key word options.
Update 03/25/2022: Rails 6 no longer supports add_template_helper, and now replaces it with helper, as explained by this answer that links to the commit that did so.

RAILS 5
In your mail method add your inline attachment pointing to your image:
class ConfirmationMailer < ActionMailer::Base
def confirmation_email
attachments.inline["logo.png"] = File.read("#{Rails.root}/app/assets/images/logo.png")
mail(to: email, subject: 'test subject')
end
end
Then in your mail html view an image_tag with the attachment url:
<%= image_tag(attachments['logo.png'].url) %>

Adding onto Oksana and tdubs' answers
The module tdubs wrote works on desktop, but for the mobile gmail client, the images appeared as attachments. To fix this, do this for the
app/helpers/email_helper.rb
module EmailHelper
def email_image_tag(image, **options)
attachments[image] = {
:data => File.read(Rails.root.join("app/assets/images/emails/#{image}")),
:mime_type => "image/png",
:encoding => "base64"
}
image_tag attachments[image].url, **options
end
end
For the rest, follow tdubs's answer.

After a lot of research i have found very cleaner way to embed image in email.
Just add following line in production.rb and development.rb
config.action_mailer.asset_host = 'YOUR HOST URL'
In your view embed image by using following code.
<%= image_tag('My Web Site Logo.png') %>
Note: Make sure to update YOUR HOST URL and My Web Site Logo.png in
above code.
For basic details of usage of Action Mailer, please refer to ActionMailer::Base.

Copy pasted from here
http://api.rubyonrails.org/classes/ActionMailer/Base.html#class-ActionMailer::Base-label-Inline+Attachments
Inline Attachments
You can also specify that a file should be displayed inline with other HTML. This is useful if you want to display a corporate logo or a photo.
class Notifier < ApplicationMailer
def welcome(recipient)
attachments.inline['photo.png'] = File.read('path/to/photo.png')
mail(to: recipient, subject: "Here is what we look like")
end
end
And then to reference the image in the view, you create a welcome.html.erb file and make a call to image_tag passing in the attachment you want to display and then call url on the attachment to get the relative content id path for the image source:
<h1>Please Don't Cringe</h1>
<%= image_tag attachments['photo.png'].url -%>
As we are using Action View's image_tag method, you can pass in any other options you want:
<h1>Please Don't Cringe</h1>
<%= image_tag attachments['photo.png'].url, alt: 'Our Photo', class: 'photo' -%>

In rails 6, if you use the solution of #Tyron, you'll need to replace add_template_helper with helper in the BaseMailer. So, it becomes:
class BaseMailer < ActionMailer::Base
helper(EmailHelper)
end

I don't know much about rails, but I've worked on projects in C# that create emails and then insert them in a users inbox via the Google APIs. To create the email, I had to generate the email string from scratch. If you enable multipart for the email, then the image bytes will be included in a multipart section using base64 encoding.
You may want to check out the TMail and RubyMail packages to see if they support these actions for you.

Related

Rendering an image from the Unsplash API in Rails

I have extensively researched this matter both on Stack Overflow and Google but found nothing conclusive. Since I'm completely new to the concept of API usage within Rails I have to ask for some advice.
I have followed the procedure from the github page
I have included the Unsplash helper in application_helper.rb as follows
def show_photo
Unsplash::Photo.find("tAKXap853rY")
end
and simply added
<%= image_tag show_photo %>
in my view.
This returns an object (So connectivity is good)
<img src="/images/#<Unsplash::Photo:0x007fc4b2f953c0>" alt="#
<unsplash::photo:0x007fc4b2f953c0>">
I'm aware that Rails is looking for a picture in the assets/images folder
How do I parse the inbound JSON and render it in my Rails view?
You can access to the urls key within the OpenStruct attributes in the Photo object that includes the raw, full, regular, small and thumb sizes, also as keys.
So, just to test you could use the raw one, like:
<%= image_tag Unsplash::Photo.find('tAKXap853rY')[:urls][:raw] %>
Or I think you could modify your method to accept one parameter which is the size key of the image, like:
module ApplicationHelper
def show_photo(size)
Unsplash::Photo.find("tAKXap853rY")[:urls][size.to_sym]
end
end
Then:
<%= show_photo('raw') %> # 'full', 'regular', etc ...
further to this solution I am trying to display the photographer's name by using the user.name method.
In the console I can get the following :
photo = Unsplash::Photo.find("tAKXap853rY")
photo.user.name
will return
=> "Alejandro Escamilla".
But in RAILS :
def show_photo(size)
#photo =Unsplash::Photo.find("tAKXap853rY")[:urls][size.to_sym]
end
just trying to display the name in my view like:
<%= #photo.user.name %> will return "user undefined method".
The .user.name is accessible in the console but not in rails! What have I missed? Thanks

Email compose view in rails

I have created and (hopefully set up) a mailer.
Instead of sending templates, I would like to email the content of a form textarea in a view.
I need a view to edit the message, which should be sent to the controller which in turn calls the send_mail method in my mailer.
Class Notifier < ActionMailer::Base
default from: "from#example.com"
def send_email( email, subject, body )
mail(
:to => email,
:subject => subject
) do |format|
format.text { render :text => body }
end
end
end
This is my view:
<%= form_for(:post, :url => {:action => 'send'}) do |f| %>
<%= f.text_field(:title, class: 'form-control')%>
<%= f.text_area(:content, rows: 15)%>
<%= f.button "Submit", type: 'submit' %>
<% end %>
The problem is that when generating a mailer with rails g mailer Notifier
I get a notifier.rb in mailers and a notifier folder in views. However I have no view controller for the notifier views.
Question: How do I make the link between my view and sending the input text as email?
You need to create a controller which handles your view, and in that controller you need to call the mailer somewhat like this: (you'll need to change the names of your form fields to match the params in the call or vice versa)
Notifier::send_email( params[:email], params[:subject], params[:body]).deliver
I'd recommend to check out these RailsCasts:
http://railscasts.com/episodes/61-sending-email-revised
http://railscasts.com/episodes/61-sending-email
http://railscasts.com/episodes/206-action-mailer-in-rails-3
This might be a good place to make a non-ActiveRecord model. I understand that right now a problem is solved and this is a bit beyond the scope, but it's useful, so why not?
I suggest you look at pattern 3 in this article and build a form model (Notification?) that encapsulates the process of storing form contents, validating them and sending the actual email. Note that the implementations in the article are pretty much out of date, Rails 4 introduced ActiveModel::Model that facilitates the process.
Pros:
Another class defined mostly in declarative style, easy to read and find
Can be easily and cleanly laid out by SimpleForm or Rails' form helpers
Gets all the benefits of a traditional Rails model, like validations (and errors if they fail)
Semantic, code looks consistent with the rest of the app working with DB or whatever
Cons:
Another class, can be considered overengineering
More code, some more work, ease of maintenance is arguable
Another object to create in controllers that render this form
Once it's done, the process of making it work is pretty much the same as making any other resource to work. And I assume, that this mailer sits on its separate page.
Routes:
resource :notification, only: [:create] do
get :new, path: "" # A matter of taste, really
# You may use the default `new` route
# with a path `notifications/new`
end
Controller:
class NotificationsController
def new
#notification = Notification.new
end
def create
#notification = Notification.new(notification_params)
if #notification.send
# success! redirect somewhere?
else
render :new # render the form again with the errors
end
end
private
def notification_params
params.require(:notification).permit(:email, :subject, :body)
end
end
You will also need a view for the new action that renders the #notification into a form. Only new, create doesn't need its own. And now for the fun part, model:
class Notification # Yep, it inherits nothing!
include ActiveModel::Model
attr_reader :email, :subject, :body
validates :email,
presence: true # You might want to validate its format?
validates :subject,
presence: true, length: {in: 0..100} # Too long subjects are annoying
validates :body,
presence: true
def persisted?
false # I have no idea why, but it's defined in the article, no harm done
# I'd love to hear the explaination about this though
end
def send
if valid? # no objections from validations?
# Alright, send it already!
Notifier.send_mail(email, subject, body).deliver
# thanks for this line go to #Daniel and his answer
true
else
false
end
end
end
And, finally, a pro tip: Rails 4.2 (bleeding edge right now!) introduced ActiveJob, that's integrated with mailers. By replacing a call to deliver method with a call to deliver_later you will enqueue the email for sending by the background task processor as described here (edge guides, subject to change quite soon). I don't really think it's about time to use it everywhere (too new), but consider this for future projects.
Do I really think it's good? Yeah, I really do, I've refactored a user password changer to look this way, the code has become easier to navigate and look at.
So think of your notifier.rb as a controller. In which you defined the #send_mail. This means that in views/notifier you should add a send_mail.html.haml (erb/slim... matter of taste) which will be the body of the mail.
Now from the controller that receives your form you just need to call
Notifier.send_mail(email, subject, body).deliver

Wrap images from text field into image_tag

I would like to create a helper or change the model.rb to behave like:
Post model has a text field, which contains urls, all separated with new lines.
On create or update, rails scans this field, when it finds url like:
http://i.imgur.com/imanimage.png
http://i.imgur.com/anotherimage.png
Then it leaves them as is, but in the show action it renders them as:
= image_tag('http://i.imgur.com/imanimage.png', class: 'my-image-class')
= image_tag('http://i.imgur.com/anotherimage.png', class: 'my-image-class')
Probably a helper method could do this.
Does the text field store anything else but the urls? If it does, you should consider creating helper using regex to get all urls and change these to <img src=''/>,
If not, you can use in haml/erb file.
Model.text.each_line do |m|
image_tag(m, class: 'my_img')
end
Reference: http://ruby-doc.org/core-2.1.0/String.html#method-i-lines
If you're saving these URLs in your database, you it should be quite a simple procedure:
#app/models/image_url.rb
Class ImageURL < ActiveRecord::Base
end
#app/controllers/image_urls_controller.rb
def show
#url = ImageURL.find(params[:id])
end
#app/views/image_urls/show.html.erb
<%= image_tag(#url, class: 'my-image-class')
If you add some more context to your question, I'll be able to give you a more refined answer!

How to generate and link to an arbitrary file?

Here's what I'm trying to do. Let's say the user is looking at the foo view for the foo action of the bar controller, and I've got a variable called #userName.
bar_controller.rb
class BarController
def foo
#userName = getUserName();
end
foo.html.erb
Hi mom!
I want to create a filed called <%= #userName %>.myExt with the information Hi, I'm <%= #userName %>! in it and put a link to it in the view. How do I do this?
i.e. final:
bar_controller.rb
def foo
#userName = getUserName();
create_myExt_file(#userName);
foo.html.erb
Hi mom! Click <%= generate link to #userName.myExt, "here" %> to view!
<#userName>.myExt
Hi, I'm <#userName>!
Ideally the #userName.myExt file doesn't have to actually be written to the hard drive, but could be created from a template or something. I don't know how to do this!
Thanks!
First, generate the file as a string, such as:
s = get_file_contents
Then, in your controller, send it to the client, along with a suggested filename:
send_data s, :filename => 'example.text'
Finally, to use an ERB template, you can just render_to_string.

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