Let's say we want our application to email us whenever we spend a certain amount on a particular category for the current month
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
Let's also monetize this column value using the 'money' gem from earlier, in app/models/category.rb
add:
monetize :limit_cents
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>
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
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 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)
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