Views, Mailers & Background Jobs
Rails views use ERB templates with layouts and partials. ActionMailer handles email delivery. ActiveJob provides a unified background job interface with adapters for Sidekiq, Delayed::Job, and others.
ERB Templates & Helpers
<!-- app/views/articles/index.html.erb -->
<% content_for :title, "Articles" %>
<h1>Articles</h1>
<% if @articles.any? %>
<div class="articles">
<% @articles.each do |article| %>
<%# Render a partial (app/views/articles/_article.html.erb) %>
<%= render article %>
<%# Equivalent: render partial: "article", locals: { article: article } %>
<% end %>
</div>
<!-- Pagination links (kaminari gem) -->
<%= paginate @articles %>
<% else %>
<p>No articles yet. <%= link_to "Create one", new_article_path %>.</p>
<% end %>
<!-- app/views/articles/_article.html.erb -->
<article class="article-card" id="article-<%= article.id %>">
<h2><%= link_to article.title, article_path(article) %></h2>
<p class="meta">
By <%= article.user.name %> ·
<%= time_tag article.created_at, article.created_at.strftime("%B %d, %Y") %>
</p>
<p><%= truncate(article.body, length: 200) %></p>
</article>
<!-- form_with generates a form that works with Rails UJS/Turbo -->
<!-- app/views/articles/_form.html.erb -->
<%= form_with model: @article do |f| %>
<% if @article.errors.any? %>
<div class="errors">
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.label :title %>
<%= f.text_field :title, class: "form-control" %>
<%= f.label :body %>
<%= f.text_area :body, rows: 10, class: "form-control" %>
<%= f.check_box :published %> <%= f.label :published %>
<%= f.submit class: "btn btn-primary" %>
<% end %>ActionMailer
# Generate a mailer
# rails generate mailer User welcome password_reset
# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
default from: "DevRecall <noreply@devrecall.com>"
def welcome
@user = params[:user]
@login_url = login_url
mail(
to: @user.email,
subject: "Welcome to DevRecall!"
)
# Renders app/views/user_mailer/welcome.html.erb + welcome.text.erb
end
def password_reset
@user = params[:user]
@token = params[:token]
@expires_at = 2.hours.from_now
mail(
to: @user.email,
subject: "Reset your password"
)
end
end
# app/views/user_mailer/welcome.html.erb
# <h1>Welcome, <%= @user.first_name %>!</h1>
# <p>Click here to get started: <%= link_to "Go to Dashboard", @login_url %></p>
# Deliver methods
UserMailer.with(user: @user).welcome.deliver_now # Synchronous (blocks request)
UserMailer.with(user: @user).welcome.deliver_later # Async via ActiveJob (preferred)
# config/environments/development.rb — use Letter Opener to preview emails in browser
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true
# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.sendgrid.net",
port: 587,
user_name: "apikey",
password: Rails.application.credentials.sendgrid_api_key,
authentication: :plain,
enable_starttls_auto: true
}ActiveJob & Rails Credentials
# Generate a job
# rails generate job ProcessPayment
# app/jobs/process_payment_job.rb
class ProcessPaymentJob < ApplicationJob
queue_as :default # Queue name
retry_on Stripe::RateLimitError, wait: 5.seconds, attempts: 3
discard_on ActiveRecord::RecordNotFound
def perform(order_id)
order = Order.find(order_id) # raises RecordNotFound → discarded
PaymentService.new(order).process!
OrderMailer.with(order: order).confirmation.deliver_later
end
end
# Enqueue a job
ProcessPaymentJob.perform_later(order.id)
ProcessPaymentJob.set(wait: 5.minutes).perform_later(order.id)
ProcessPaymentJob.set(wait_until: Date.tomorrow.noon).perform_later(order.id)
ProcessPaymentJob.perform_now(order.id) # Synchronous (for testing)
# Queue adapters (config/application.rb or environments)
config.active_job.queue_adapter = :sidekiq # Production
config.active_job.queue_adapter = :async # Development (in-process, no persistence)
config.active_job.queue_adapter = :test # Test (enqueues but does not run)
# Sidekiq setup (Gemfile: gem "sidekiq")
# config/initializers/sidekiq.rb
Sidekiq.configure_server { |c| c.redis = { url: ENV["REDIS_URL"] } }
Sidekiq.configure_client { |c| c.redis = { url: ENV["REDIS_URL"] } }
# config/application.rb: config.active_job.queue_adapter = :sidekiq
# Start: bundle exec sidekiq
# Rails Credentials — encrypted secrets, committed to git
# EDITOR="code --wait" rails credentials:edit # Open in VS Code
# Structure (config/credentials.yml.enc):
# secret_key_base: abc123...
# sendgrid_api_key: SG.xxx
# stripe:
# secret_key: sk_live_xxx
# webhook_secret: whsec_xxx
# Access in code
Rails.application.credentials.sendgrid_api_key
Rails.application.credentials.stripe[:secret_key]
# Environment-specific credentials
# rails credentials:edit --environment production
Rails.application.credentials.stripe[:secret_key] # reads from config/credentials/production.yml.encKeep your own version of these notes — editable, searchable, and organised by your stack.
Start free