The post A Production-Ready Rails 5 Email Workflow with Mailer Previews, Premailer and ActiveJob appeared first on Hacker Notes.
]]>I will walk through my complete email stack and workflow, which I have used very effectively across several production apps.
As of Rails 5, the project generator will drop the following ApplicationMailer implementation into your project tree:
# in app/mailers/application_mailer.rb class ApplicationMailer < ActionMailer::Base default from: '[email protected]' layout 'mailer' end
Perhaps the only notable omission from Rails’ stock email workflow is CSS inlining, but that is easily addressed with the premailer-rails gem, which we will take care of before we go any further:
# in your Gemfile, add premailer-rails with... gem 'premailer-rails'
We should then run bundle install
and restart our server with bin/rails server
. We can now add a mailer.sass stylesheet to the base of our app/assets/stylesheets directory:
// in app/assets/stylesheets/mailer.sass body padding: 0 background: #f7f7f7 text-align: center .email-table width: 538px padding: 20px background: #fff margin: 40px auto text-align: left font-family: 'Helvetica Neue' h1 font-size: 20px p margin-top: 0 font-size: 15px
In order for premailer to pull and inline these styles in production, we will need to add this file to our precompile list:
# add in config/initializers/assets.rb... Rails.application.config.assets.precompile += %w( mailer.css )
Rails’ project generator will drop a mailer layout into app/views/mailers/mailer.html.erb. Now all we have to do to add inline styles to our mailers is to reference this stylesheet from the layout with an ordinary stylesheet_link_tag. With this, premailer will implicitly handle inlining when the message is rendered and delivered.
<!-- in app/views/layouts/mailer.html.erb --> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <!-- * simply referencing the stylesheet with this stylesheet_link_tag * --> <!-- * is enough for premailer to pull-in and inline your styles * --> <%= stylesheet_link_tag "mailer" %> </head> <body> <%= yield %> </body> </html>
Let’s go ahead and subclass ApplicationMailer now with something concrete. For our purposes here, let’s create a UserMailer
with a welcome
action to deliver a welcome email to new registrants.
# in app/mailers/user_mailer.rb class UserMailer < ApplicationMailer def welcome(user) @user = user mail(to: @user.email, subject: "Welcome to our site!") end end
…and a corresponding template:
<!-- in app/views/user_mailer/welcome.html.erb --> <table class="email-table"> <tbody> <tr class="header-row"> <td> <h1>Welcome, <%= @user.name %>!</h1> </td> </tr> <tr class="body-row"> <td> <p>Thanks for your registering! We appreciate your interest.</p> </td> </tr> </tbody> </table>
Now in order to work with this mailer as we would an ordinary view all we have to do is create a mailer preview for it. We can do this by dropping a file into test/mailers/previews:
# in test/mailers/previews/user_mailer_preview.rb class UserMailerPreview < ActionMailer::Preview def welcome user = OpenStruct.new(email: "[email protected]", name: "John Doe") UserMailer.welcome(user) end end
Note that we are passing an OpenStruct with a few carefully defined attributes to the mailer here. In reality you will probably want to pull an actual record from your database via ActiveRecord or use a factory or fixture.
You can now navigate to http://localhost:3000/rails/mailers, where you will find an index of defined mailer previews, which at the moment will just show the above-defined UserMailer#welcome preview.
…and if you click through the link to “welcome” you will find a preview of the mailer, complete with inlined styles:
You can now work with your mailer and template in-browser as if they were an ordinary controller action and view.
The final piece to Rails’ email workflow worth noting is ActiveJob. As of Rails 4.2 ActionMailer has included an elegant ActiveJob integration in deliver_later. When you wish to deliver an email in your application and would have previously invoked deliver, instead invoke deliver_later and the delivery will be queued up in ActiveJob for asynchronous execution. For example…
UserMailer.welcome(@user).deliver_later
That is all there is to it.
The post A Production-Ready Rails 5 Email Workflow with Mailer Previews, Premailer and ActiveJob appeared first on Hacker Notes.
]]>The post Validation Contexts in ActiveRecord appeared first on Hacker Notes.
]]>What is a validation context, precisely? It is a way to constrain a model validation to a particular usage context for a record. This is similar to what you might achieve with something like state_machine, but far more lightweight.
Let’s say we have an application where we want to dispense gift cards to select users. Administrators can manage an inventory of gift cards and then invite users to claim them by filling out a form at a tokenized link.
A schema for such a feature might look as follows:
class DefineSchema < ActiveRecord::Migration[5.0] def change create_table :gift_cards do |t| t.string :code t.string :token t.string :name t.string :email t.datetime :claimed_at t.timestamps end end end
Different validation rules apply in different contexts. In the admin-editing context (which we’ll consider the default context) the record is valid so long as it has a code. In the user-claiming scenario, the gift card is only valid if it has been assigned a name, email and the user has supplied a valid confirmation of its token.
We can tie these contexts to model validations by supplying the :on
option to our validates
and validate
calls. Our gift card model might therefore look as follows:
class GiftCard < ApplicationRecord attr_accessor :token_confirmation validates :code, presence: true # Contextual validations: validates :name, presence: true, on: :claim validates :email, presence: true, format: /\A\S+@.+\.\S+\z/, on: :claim validate :token_match?, on: :claim before_create :generate_token scope :unclaimed, ->{ where(claimed_at: nil) } def claim(attrs={}) self.attributes = attrs.merge(claimed_at: Time.now) save(context: :claim) end private def token_match? unless token_confirmation == token errors[:base] << "You are not authorized to claim this gift card" end end # generate a random token for this Gift Card (i.e. for token link authorization) def generate_token self.token = SecureRandom.hex(10) end end
The beauty of validation contexts for use cases like we have here is how declarative and readable they are and how foolproof they become once we’re further up in the stack. To drive this point home, let’s have a look at how skinny the controller and UI layers we build around this model to handle the full user flow are.
class GiftCards::ClaimsController < ApplicationController before_action :find_gift_card def new @gift_card.token_confirmation = params[:token_confirmation] end def create if @gift_card.claim(gift_card_params) GiftCardMailer.claim_notification(@gift_card).deliver_later redirect_to gift_card_claim_path(@gift_card) else render :new end end def show # default render end private def gift_card_params params.fetch(:gift_card, {}).permit(:name, :email, :token_confirmation) end def find_gift_card @gift_card = GiftCard.find(params[:gift_card_id]) end end
This minimal controller implementation can handle our entire flow of presenting a claim form, processing and validating user input, delivering an email and presenting the user a success page when they are done. The minimal form implementation below is enough to take all the requisite input, as well as keep the token that the user came into the flow with in scope (note: this is using simple_form):
<div class="row"> <div class="col-md-4 col-md-offset-4"> <%= simple_form_for @gift_card, url: gift_card_claims_path(@gift_card), method: :post do |f| %> <%= f.error :base %> <%= f.input :name %> <%= f.input :email %> <%= f.input :token_confirmation, as: :hidden %> <%= f.button :submit, "Claim Gift Card" %> <% end %> </div> </div>
It is worth noting that as of Rails 4.1, the on
option to validate
/validates
can now take multiple contexts. This is welcome flexibility and in my opinion even further reduces the number of real-world use cases for heavyweight solutions like state_machine.
The post Validation Contexts in ActiveRecord appeared first on Hacker Notes.
]]>The post Less Code appeared first on Hacker Notes.
]]>I belive that, for the overwhelming majority of domains, it is the tool – if weilded with healthy reverence for its conventions and idioms – which leads to the most manageable, consistent, non-redundant and all-around sane codebases.
Nearly every “scaling” or “complexity” gripe that I have heard leveled at Rails is in fact a complaint about non-idiomatic use of the framework. I still routinely stumble on codebases that make zero use of key-based fragment caching (an innovation that elevated Rails far above many other frameworks in performance when it came to real-life application – not just framework benchmarking), poor or incorrect use of the ActiveRecord query interface, poor domain modeling (failing to see when validations or logic belong on relations, for instance, and not in the related objects), failure to design properly DRY controllers (failure to use exception-based control flow and filters where appropriate), heavy integration of and reliance on obviously poorly maintained dependencies, and much, much more.
So accepting now that one can – and, in fact, people often do – make horrible messes of applications they implement with Rails, I will continue to devote myself to its idiomatic application until something comes along that does it all better in the real world. And my real world metrics of success are modest: “is there less code?”, “is it easier to read?”, “does it break less?”. Academic cases for purity should always take a back seat if they result in an outcome that is many times worse by these practical metrics, which is often the reality.
In this time when choices at all levels of your stack abound like never before, it pays to be mindful of what your real-world metrics of success are, and to honestly weigh how closely your stack is aligned with them.
The post Less Code appeared first on Hacker Notes.
]]>The post Pattern Vision Redux appeared first on Hacker Notes.
]]>Every item in the above list is a real example from the wild.
Universally the consequence of introducing less-than-idiomatic patterns to an otherwise idiomatic Rails application has been degraded productivity, more work, more bugs. Ususally some justification is made along the lines of “Rails was built for a certain limited type of application, and our application is obviously not one of them. It’s far more complicated.” Ususally this is an excuse for not fully learning or attempting to apply Rails’ idioms, or even for flat-out misunderstanding them. Usually the app has become so complicated because of a rolling snowball of decisions to make it so, not because it innately is. Usually the root of the problem is some type of Pattern Vision.
Some people fear ActiveRecord. I have sat through hours-long engineering meetings where a couple of developers have pontificated at length about how unhygenic it is to embed logic in your persistence layer. In fact, this isn’t at all what ActiveRecord does — your persistence layer is the database, ActiveRecord sits atop it. It’s ActiveRecord’s job is to enforce your application’s domain constraints in a single place.
I will gladly sacrifice some amount of theoretical “correctness” in architecture for a 10x reduction in quantity of code and complexity. Reductions of that scale are the wonderful reality that Rails brought to light by embracing the practical and not giving a fuck about architectural “wankery”, as DHH calls it. It always makes me sad to see the wonderful gifts that Rails’ brought us with its idioms ignored or thrown to the wayside in service of imagined problems.
The post Pattern Vision Redux appeared first on Hacker Notes.
]]>The post Russian Doll Caching in Rails 4 appeared first on Hacker Notes.
]]>
At Chloe + Isabel we have weekly developer talks. Below are the slides from one I gave detailing the “russian doll” caching strategy in enough depth, I feel, to bring most Rails developers to a strong enough understanding to work with the techinique.
The post Russian Doll Caching in Rails 4 appeared first on Hacker Notes.
]]>