Skip to content

Instantly share code, notes, and snippets.

@yosukehasumi
Created April 2, 2019 06:37
Show Gist options
  • Save yosukehasumi/417d602ab616acabde77dee05bd113bf to your computer and use it in GitHub Desktop.
Save yosukehasumi/417d602ab616acabde77dee05bd113bf to your computer and use it in GitHub Desktop.
Mailers

Implementing Mailers

Let's say we want our application to email us whenever we spend a certain amount on a particular category for the current month

Limits value

Let's create a column on our categories table that can store monthly limits.

bin/rails generate migration add_category_limits

and add a limit_cents column in the migration

class AddCategoryLimits < ActiveRecord::Migration[5.2]
  def change
    add_column :categories, :limit_cents, :integer
  end
end

Monetize limit_cents

Let's also monetize this column value using the 'money' gem from earlier, in app/models/category.rb add:

monetize :limit_cents

Update views

In app/views/categories/_form.html.erb let's add our limit_cents field to our form

<div class="form-group row">
  <div class="col-sm-2">
    <%= f.label :limit, "Spending Limit", { class: 'col-form-label' } %>
  </div>

  <div class="col-sm-10">
    <%= f.number_field :limit, { step: 0.01, class: 'form-control' } %>
  </div>
</div>

Controller

in app/controllers/categories_controller.rb we need to allow the new attribute through the strong params

def category_params
  params.require(:category).permit(:name, :colour, :limit)
end

Scopes

We haven't covered this much in class yet in any practical sense. But rails scopes can be very handy for creating reusable queries that keep your application clean and easily debuggable.

In the context of the mint app, we could use a scope that will always return the current month's transactions for this mailer, so let's add a scope to the transaction model. In app/models/transaction.rb add:

scope :this_month, -> { where('? <= date AND ? >= date', Time.current.beginning_of_month, Time.current.end_of_month)}

You can test this in console by typing Transaction.all.this_month which should return with transactions only for the current month

Create a mailer

Create a mailer that inherits the ApplicationMailer class so that we can add our own custom email methods. Create a file called app/mailers/user_mailer.rb

class UserMailer < ApplicationMailer
  def spending_notice
    user = params[:user]
    @category = params[:category]
    mail(to: user.email, subject: 'Spending Notice')
  end
end

These mailers will use the default layout found in app/views/layouts/mailer.html.erb but we need to add our own content to create a pretty email. In app/views/user_mailer/spending_notice.html.erb let's add some html.

<h1>Spending Notice</h1>

<p>You've exceeded the spending limit for this month in the category <%= @category.name %>!</p>

<p>A list of the following transactions have been logged</p>

<ul>
  <% @category.transactions.this_month.each do |transaction| %>
    <li><%= transaction.name %>: <%= humanized_money_with_symbol transaction.amount %></li>
  <% end %>
</ul>

<p>Total: <%= humanized_money_with_symbol @category.transactions.this_month.map(&:amount).sum %></p>

Next let's check that this even works the way we want UserMailer.with(user: User.first, category: Category.first).spending_notice.deliver_now

And you'll see in either your logs or your mailtrap.io account that we've successfully sent an email (rough but it works)

Hooking it up

Finally let's connect this up to something to have some sort of automation going. Whenever a transaction is created or update I'd like to send out a notice so I'm going to add a small little method to the transactions_controller.rb called maybe_notify, I'll place this below the private declaration.

private

def maybe_notify
  category = @transaction.category
  return unless category.limit
  month_total = category.transactions.this_month.map(&:amount).sum
  return if category.limit >= month_total
  UserMailer.with(user: current_user, category: category).spending_notice.deliver_now
end

And then add this method to both the create and update actions

def create
  @transaction = Transaction.new(transaction_params)
  if @transaction.save
    maybe_notify
    redirect_to transaction_path(@transaction)
  else
    render :new
  end
end

def update
  @transaction = Transaction.find(params[:id])
  if @transaction.update(transaction_params)
    maybe_notify
    redirect_to transaction_path(@transaction)
  else
    render :edit
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment