Rails email preview for users in production - ruby

Context
Gems like mail_view, mailcatcher, rails_email_preview, etc. seem to be more developer-oriented (a way to debug a template). But I need something that will be used by the trusted users of my rails app in production.
My app is a project management app, where project managers can update the status of their projects, operations during which emails must be sent to project contractors, developers, clients, etc.
The project manager must be able to tell whether or not he wants to send an email (this is easy), and be able to customize to some extent the message content (not the design, only specific text parts should be enough). They DO want to have some control over the email about to be sent, ie, they need a preview of the email they customized. Why ?
Project Managers are trusted users/programmers, and I let them add HTML as custom parts of the email (We are talking about a small-scale app, and the project managers are all trusted employees). But a closing tag is easily forgotten, so I want to provide them with a mean to check that nothing is wrong. Eg. that the text does not all appear as <h2> just because they forgot a closing </h2>
Some email templates already include some info about what the PM is writing about, and the PM may not be aware of it (understand : may be too drunk to remember it). An email preview is just a way to avoid duplicate sentences (like two times Hello M. President,)
CSS styles are applied to the email. It can be hard to anticipate the effect of tags like <h2>, etc. So I need to render the email with the CSS
REMARKS
Previsualize & Send button
Project managers have access to a form that will feed the content to my Rails app. I am thinking on having both a normal submit button, and a previsualize button. I will probably use some tricks given by this SO question to differentiate the behaviours of the 2 buttons
Letter_opener : a nice gem, but exclusive ?
I am using letter_opener for debug (so different context), but this is typically the preview I'd like to show to the project manager. However, in order to be used, letter_opener requires to modify action_mailer configuration config.action_mailer.delivery_method = :sendmail # (or :letter_opener). So I can only previews emails, or send them for real, not both ? I would accept a solution that would let me choose whether to use letter_opener or send the email for real
Small Editor ?
Instead of blindly trusting my project managers' ability to write basic html without forgetting closing tag, maybe you could recommend a nice WYSIWYG editor that would show the content of my f.text_area() ?
This would be a bonus, not an actual answer to my question
Email rendering engine ?
I am now aware that different email clients can render the email client differently. I will ignore this for now. So the way the preview is rendered doesn't matter. I like the rendering of letter_opener however
Current Code
View > Controller > Mailer
my_email_view.html.erb
<%= form_tag some_mailing_list_path %>
<%= fields_for :email do |f| %>
<!-- f.text_field(:subject, ....), etc -->
<% end %>
<%= submit_tag("Send email") %>
<%= submit_tag("Preview") %>
<% end %>
my_controller.rb
before_action :prep_email # Strong parameters, define #mail with form contents
# Handles the POST
def some_action
check(:to, :from, :subject) # I check the parameters in private functions
if email_alright? # Above checks will raise a flag if something went wrong
if Rails.env.production?
MailingListsMailer.my_action(#mail).deliver_later
else
MailingListsMailer.my_action(#mail).deliver_now
end
flash.notice = "Email sent"
redirect_to :back
else
redirect_to :back
end
end
mailing_list_mailer.rb
def my_action(message)
format_mail_params(message) # Will set more variables
#etude = etude
#include_reference = message[:include_reference]
#include_description = message[:include_description]
dst = Proc.new { read_emails_file }
mail(
to: dst,
from: message[:from],
subject: #subject_full)
end

Question update: based on your pseudocode, this is a simple case of creating a status update model and emailing the update to a mailing list.
There are several ways you can go about it, but I'd suggest that you keep things simple and avoid using gems.
<%= link_to "New update", new_status_update_path, class: 'button' %>
Model
class StatusUpdate
belongs_to :sender, class_name: 'User'
belongs_to :mailing_list
end
Controller
class StatusUpdateController
def new
#status_update = StatusUpdate.new
end
def create
#status_update = StatusUpdate.create(status_update_params)
#status_update.mailing_list = MailingList.where(whichever_mailing_list)
if #status_update.save
redirect_to :action => "preview", :status_update => #status_update
end
end
def preview
#status_update = StatusUpdate.where(id: params[:id]).first
#mailing_list = MailingList.where(id: #status_update.mailing_list_id)
end
def send
#status_update = StatusUpdate.where(id:params[:status_update_id]).first
Mailer.status_update_email(#status_update).deliver
end
end
status_updates/new.html.erb
<%= simple_form_for(#status_update) do |f| %>
<%= f.input :title %>
<%= f.input :content, as: :text %>
<%= f.button :submit, 'Post update' %>
<% end %>
status_updates/preview.html.erb
<h1>Preview</h1>
<%= simple_form_for(#status_update, :url => url_for(:controller => 'StatusUpdateController, :action => 'send') do |f| %>
<%= f.input :subject %>
<div class="email-render-container">
<%= #status_update.content %>
</div>
<p>Make changes</p>
<%= f.input :content, as: :text %>
<%= f.button :submit, 'Approve and send emails' %>
<% end %>
If I were you, I'd do away with the preview feature. If you're
loading content from a template and all you're worried about are
potential duplicate content, just do this:
Controller
class StatusUpdateController
def new
#status_update = StatusUpdate.new
template = UpdateTemplate.where(however_you_assign_the_template)
#status_update.content = template.content
end
def create
#status_update = StatusUpdate.create(status_update_params)
#status_update.mailing_list = MailingList.where(whichever_mailing_list)
if #status_update.save
Mailer.status_update_email(#status_update).deliver
end
end
end
and style the new status update form with css to simulate writing on the actual email template. You'll save your users and yourself a lot of time.
wysiwyg editor
Never trust the end user with the ability to write html. Depending on your needs, I find https://www.froala.com/wysiwyg-editor easy to deploy.
differentiating buttons
Just use a preview icon with a label on your button and/or a subtitle under your button to differentiate your buttons. You don't need much command logic in your view.
Alternatively, if you think that the preview is important to your end users, just use the "preview" button as the next logical step instead of presenting your users with too many unnecessary choices.
Suggestions
Adopting a front end framework like Angularjs makes this sort of use case almost trivially easy, but it may be overkill and comes with steep learning curve if you're not familiar with it.

Take a look at letter_opener gem. It was created by Ryan Bates, the Railscasts guy.

Related

How to implement link_to, AJAX, remote: true, respond_to without rendering new url Rails 6

I'm trying to implement adding upvote and downvotes to an app, submit the controller actions remotely using a button or link_to, and refresh a count section with AJAX.
Somehow upvoting or downvoting always redirects to a path of a member. When I use head :no_content, I can't submit the form aka link_to. Having some respond_to do | f |... also just renders the action URL.
Thus, remote: true is kind of not working, as I have another controller using <%= form_for([entry, entry.review], remote: true, :authenticity_token => true) do |f| %> and it works perfectly.
I have tried implementing here, here, here, and this tutorial and this but nothing seems to work.
I'm using the acts_as_votable. Everything works except the routing functionality with AJAX
# routes.rb
resources :entries do
member do
post 'upvote'
post 'unupvote'
post 'downvote'
post 'undownvote'
end
end
# entries_controller.rb
def upvote
#entry.liked_by current_user
end
def unupvote
#entry.unliked_by current_user
end
def downvote
#entry.disliked_by current_user
end
def undownvote
#entry.undisliked_by current_user
end
# entries/index.html.erb
...
<%= button_to upvote_entry_path(entry.id), remote: true,
class: "btn",
id: "upvote-button-#{ entry.id }" do %>
<i class="bi bi-hand-thumbs-up" style="color: #ababab;" id="upvote-<%= entry.id %>"></i>
<small id="upcount-<%= entry.id %>" >
<%= render 'entries/upvote', entry: entry %>
</small>
<% end %>
...
# entries/_upvote.html.erb
<%= entry.get_upvotes.size %>
# entries/upvote.js.erb
$('#upvote-<%= entry.id %>').className('bi bi-hand-thumbs-up-fill');
$('#upvote-<%= entry.id %>').attr('href', '/entries/<%= entry.id %>/unupvote');
$('#upcount-<%= entry.id %>').html('<%=j render 'entries/upvote', entry: entry %>');
Edit, I have changed my link_to to button_to, changed the routes to post
Not sure if your a tag is inside other elements but you might want to consider using a button_to instead of a link_to.
So far the temporary solution, not probably recommended but still does the job programming practice, is hiding and unhiding certain elements on specific conditions.
Upon user interaction (e.g onClick), unhide and hide elements while updating values that resemble the controller update. Simply simulate a refresh and update the form paths/controller actions they point to.
This will mostly work better with boolean check-box forms, likes or dislikes where variables will only alternate between two instances at max. Hence you don't have to write much javascript to handle pseudo outputs.
But still, I'm sure there's a better method for example WebSockets through Action Cable. That can probably be overkill but the closest to realtime updates. respond_to just doesn't seem to work

function to change attribute using a button

I am creating a small college project in Ruby on Rails, and I came across a problem: I have a table named Person and another called Tools.
People have many tools and each tool and possessed by a person.
I need to add a feature that allows the loan of tools between people of the system. For this, I created an attribute called 'loan' in the Tools table, which has a value of 0 means that the tool not this borrowed and if it is 1, that this borrowed. In view show the tools, I created a button with the function to borrow the tool. My problem is how do to make this button change the attribute 'loan' from the Tools table from 0 to 1 and then reverse it. Someone would have a solution or a better suggestion?
I would create 2 routes name "barrow" and "retrieve" like this:
#config/routes.rb
get '/tool/:id/barrow' => 'tools#barrow', as: :barrow
get '/tool/:id/retrieve' => 'tools#retrieve', as: :retrieve
Now in your view you can do something like
# tool.index.html.erb
...
<% if tool.loan %>
<%= link_to retrieve_path(tool), 'Retrieve', class: 'btn ...' %>
<% else %>
<%= link_to barrow_path(tool), 'Barrow', class: 'btn ...' %>
<% end %>
...
now you have to create the controller action you have to create the retrieve and barrow methods
#app/controllers/tool_controller.rb
class UserController < ApplicationController
...
def retrieve
#tool = Tool.find_by(params[:tool])
#tool.update_attributes(:loan, 0)
end
def barrow
#tool = Tool.find_by(params[:tool])
#tool.update_attributes(:loan, 1)
end
...
end
I hope that this help you

How to upload a file temporarily in Rails 3?

I'm creating CSV-upload functionality for a site of mine.
I'm looking to upload a file, parse it, and then dispose of it.
I know I can upload and save a file using Paperclip, but that seems a bit like overkill.
All I need to do is parse the uploaded file and never save it.
How would I go about doing this in Rails 3?
Note: I'd prefer to do the uploading manually without using an external gem so I can learn how to process works, but any suggestions are welcome.
Thanks!
Use the file_field helper in your form, then in your controller you can use File.Write and File.read to save the file.
E.g. View
<%= form_for #ticket do |f| %>
<%= f.file_field :uploaded_file %>
<% end %>
Controller
def upload
uploaded = params[:ticket][:uploaded_file]
File.open(<insert_filename_here>, 'w') do |file|
file.write(uploaded.read)
end
end
Edit: Just saw #klochner's comment, that link says pretty much what I have said so follow that: RubyOnRails Guides: Uploading Files.
Paste this in your model
def parse_file
File.open(uploaded/file/path, 'w') do |f| # Feed path that user gives in some way
## Parse here
end
end
this in view
<%=form_for #page, :multipart => true do |f|%>
<ul><li><%= f.label :file%></li>
<li><%= f.file_field :uploaded_file%></li></ul>
<%end%>
Let me know if this works. If it fails figure out a way to feed path of uploaded_file in parse_file method (the definite way which will work is storing file location in db and picking up from there, but it is not the right way to do this thing). Otherwise, I guess it should work.
Complete Example
Take, for example, uploading an import file containing contacts. You don't need to store this import file, just process it and discard it.
Routes
routes.rb
resources :contacts do
collection do
get 'import/new', to: :new_import # import_new_contacts_path
post :import, on: :collection # import_contacts_path
end
end
Form
views/contacts/new_import.html.erb
<%= form_for #contacts, url: import_contacts_path, html: { multipart: true } do |f| %>
<%= f.file_field :import_file %>
<% end %>
Controller
controllers/contacts_controller.rb
def new_import
end
def import
begin
Contact.import( params[:contacts][:import_file] )
flash[:success] = "<strong>Contacts Imported!</strong>"
redirect_to contacts_path
rescue => exception
flash[:error] = "There was a problem importing that contacts file.<br>
<strong>#{exception.message}</strong><br>"
redirect_to import_new_contacts_path
end
end
Contact Model
models/contact.rb
def import import_file
File.foreach( import_file.path ).with_index do |line, index|
# Process each line.
# For any errors just raise an error with a message like this:
# raise "There is a duplicate in row #{index + 1}."
# And your controller will redirect the user and show a flash message.
end
end

Using Padrino form helpers and formbuilder - getting started

I've jumped into learning Ruby by going straight to Padrino with Haml.
Most of the Padrino documentation assumes a high-level of knowledge of Ruby/Sinatra etc...
I am looking for samples that I can browse to see how things work. One specific scenario is doing a simple form. On my main (index) page I want a "sign up" edit box with button.
#app.rb
...
get :index, :map => "/" do
#user = "test"
haml: index
end
get :signup, :map => "/signup" do
render :haml, "%p email:" + params[:email]
end
...
In my view:
#index.haml
...
#signup
-form_for #user, '/signup', :id => 'signup' do |f|
= f.text_field_block :email
= f.submit_block "Sign up!", :class => 'button'
...
This does not work. The render in (/signup) never does anything.
Note, I know that I need to define my model etc...; but I'm building to to that in my learning.
Instead of just telling me what I'm doing wrong here, what I'd really like is a fairly complete Padrino sample app that uses forms (the blog sample only covers a small part of Padrino's surface area).
Where can I find tons of great Padrino samples? :-)
EDIT
The answer below was helpful in pointing me at more samples. But I'm still not finding any joy with what's wrong with my code above.
I've changed this slightly in my hacking and I'm still not getting the :email param passed correctly:
#index.haml
...
#signup
- form_for :User, url(:signup, :create), :method => 'post' do |f|
= f.text_field_block :email
= f.submit_block "Sign up!"
...
#signup.rb
...
post :create do
#user = User.new(params[:email])
...
end
EDIT Added Model:
#user.rb
class User
include DataMapper::Resource
property :id, Serial
property :name, String
property :email, String
...
end
When this runs, params[:email] is always nil. I've compared this to bunches of other samples and I can't see what the heck I'm doing wrong. Help!
You can browse some example sites here: https://github.com/padrino/padrino-framework/wiki/Projects-using-Padrino
Or you can browse sources of padrinorb.com here: https://github.com/padrino/padrino-web
The best way also is to generate admin: padrino g admin where you should see how forms works.
The tag form perform by default post actions unless you specify :method => :get|:put|:delete so in your controller you must change :get into :post
post :signup, :map => "/signup" do ...
Since you are using form_for :user params are in params[:user] so to get email you need to puts params[:user][:email]

Rails 3 - validation rules and HTML tags in the message

validates_presence_of :city, :message => "City is
required item!"
I would like to ask you if is possible to insert some HTML tags into the error message, for example if I would want to have a word "City" bold.
Thank you
If you're using traditional 'errorExplanation' div in the beginning of the page, you can use something like this:
<% #some_entity.errors.full_messages.each do |msg| %>
<li><%= msg.html_safe %></li>
<% end %>
In the other case, if you're using ActionView::Base.field_error_proc, you can past html_safe in appropriate place of that code.
Try :message => "<b>City</b> is required item!".html_safe.

Resources