An alternate approach to Avdi’s method of slimming down Rails controllers

I recently watched/read Avdi’s fantastic article Slimming down hefty Rails controllers AND models using domain model events on Ruby Tapas. PS – you should probably subscribe to Ruby Tapas.

While being amazed by Avdi’s refactoring skills and watching him plug in proven software development patterns, I couldn’t help but think about how I would have approached this problem.

Recently, I’ve been trying to be extremely adherent to “The Rails Way.” I’ve been using as few gems as possible, and just working with what Rails gives you out of the box. It’s been an eye-opening experience to say the least.

If you haven’t yet watched the episode, or read the accompanying article, essentially the problem is a long update method in the controller for a project management app. It has a bunch of nested conditionals with methods to send out updates via email or Websockets depending on what has been updated. Because so much of this depends on the logged-in user and other session-specific variables, breaking the functionality out into a “domain object” didn’t make a ton of sense.

What Avdi observed (pun intended) is that these are actually life-cycle events for a task and associated project.

Looking over this action again, we realize something: hidden in all these conditionals is a series of domain-model lifecycle events, each with different concrete actions which trigger on those events.

  1. There are some actions to perform anytime the task is successfully updated.
  2. There are actions to take when the task has moved from one project to another.
  3. There are actions to perform when the task is newly created.
  4. There are actions that happen when the task’s status has changed.
  5. There are actions for when the task has been reassigned.

He then proceeded to add observers to the Task model and subsequently send out notifications to listeners whenever a life-cycle event occurred.

Immediately, I see a lot more objects here. I see a Move, a StatusChange, and a Reassignment; all resources related to a Task. Only a traditional updating of attributes belongs in this method and controller.

As an aside, I’m not exactly sure why handling the case of a newly created task is in the update action of the controller instead of create, but I’ll assume there is something here that we don’t know. For simplicity, I’m just going to skip it.

Let’s start with some routes:


resources :tasks do
  resource :move, only: [:create]
  resource :status_change, only: [:create]
  resource :reassignment, only: [:create]
end

Now, instead of just hitting the update action, anytime something happens with a Task, we’ve got a very distinct route for each special case.

Because the controller in the example is just returning JSON, I’m going to assume that this is a Javascript front-end communicating with the Rails app. This would require a change in the front-end logic to POST to a subresource url instead of a PUT to the task url.

Here’s our first controller to handle the case of when a Task moves from one project to another.


# app/controllers/tasks/moves_controller.rb

class Tasks::MovesController < ApplicationController
  def create
    old_project_id = @task.project_id

    if @task.update_attributes params.require(:task).permit(:project_id)
      push_project_update old_project_id
      push_project_update @task.project_id

      respond_to do |format|
        format.json { render "show", status: :accepted }
      end
    else
      respond_with @task do |format|
        format.json { render @task.errors.messages, status: :unprocessable_entity }
      end
    end
  end
end

Now, let’s examine what needs to happen when a status change occurs.


# app/controllers/tasks/status_changes_controller.rb

class Tasks::StatusChangesController < ApplicationController
  def create
    old_status = @task.status

    if @task.update_attributes params.require(:task).permit(:status)
      notifee = @task.readers(false) - current_user
      if notifee
        mail_completion_notice(notifee) if @task.status == Status::COMPLETED
        mail_uncomplete_notice(notifee) if old_status == Status::COMPLETED
      end

      respond_to do |format|
        format.json { render "show", status: :accepted }
      end
    else
      respond_with @task do |format|
        format.json { render @task.errors.messages, status: :unprocessable_entity }
      end
    end
  end
end

I’ll leave it to the reader to come up with their own implementation of a Tasks::ReassignmentsController but I think you can see how this method works.

I’d also challenge you to start thinking very strictly in terms of REST and “The Rails Way.” This codebase is the perfect example that rarely does an app just have basic CRUD needs. More often, there are special cases, and paths that require a different response despite technically falling under one of the basic CRUD actions.

Think about updating a User in your own codebase. Does the same thing happen when they update their password as when they update their name? Or, are you sending a email in once case and not in the other?

Instead of nesting conditionals inside of your update action in your UsersController could their be a Users::PasswordsController? Start flushing out more and more sub-resources with small controllers for these special cases.

It may not follow the classic patterns, but I’ve enjoyed this method of thinking recently.

I’d love to hear what you think as well!

Bulk Tagging in Rails

Recently, I started using Postgres’ native array type to tag records in an eCommerce application. It was huge upgrade to move to this solution instead of having multiple tables storing related Tag records.

I’m going to pass on the “Intro to tagging” type post here, because it’s already been done better than I can.

But, there is an area that I have not found covered; bulk tagging.

For the sake of our article let’s assume that you’re running an online store, with a Product model that has a tags attribute. Periodically, you want to mark products as being a “sale” item, or a “best seller.” How can we add tags to multiple records at once?

A ProductTagger

Let’s throw aside the UI component for now, and just focus on our code. For the remainder of this post, we’ll build a new ProductTagger model to bulk tag records. We can start with 3 basic requirements:

  1. The tagger should add the tag to all records.
  2. The tagger should not add the same tag more than once.
  3. The tagger should not set each record to having the same set of tags.

As I have in previous posts, let’s also start with some failing tests that lead us to where we want to go. We’ll include 3 tests for the basic requirements described above.


# test/unit/product_tagger_test.rb

class ProductTaggerTest < ActiveSupport::TestCase

  def setup
    @shirt  = Product.create
    @shorts = Product.create
    @tagger = ProductTagger.new @shirt.id, @shorts.id
  end

  def test_tags_added_to_all_records
    @tagger.tag 'sale'

    assert_equal %w(sale), Product.find(@shirt.id).tags
    assert_equal %w(sale), Product.find(@shorts.id).tags
  end

  def test_tags_are_not_duplicated
    @tagger.tag 'sale'
    @tagger.tag 'sale'

    assert_equal %w(sale), Product.find(@shirt.id).tags
    assert_equal %w(sale), Product.find(@shorts.id).tags
  end

  def test_tags_not_set_identical
    @shirt.update_attributes tags: %w(sale)
    @shorts.update_attributes tags: %w(hot)

    @tagger.tag 'top'

    assert_equal %w(sale top), @shirt.tags
    assert_equal %w(hot top), @shorts.tags
  end
end

We can now start to build the ProductTagger model to make these tests green.


# app/models/product_tagger.rb

class ProductTagger
  attr_reader :product_ids

  def initialize(*product_ids)
    @product_ids = *product_ids
  end

  def tag(tag)
    product_ids.each do |id|
      product = Product.find id
      product.tags << tags
      product.save
    end
  end
end

Easy enough, right? But, I can already hear the screams. “You’re doing 2n queries!” You’re right, we do a find query, and an update query; FOR EACH RECORD. But, I didn’t say we were done…

Rails’ update_all Method

Our first helpful method is update_all provided by Rails. The full source is available here. The method is called on a relation, and can be passed a SQL string, an array, or a hash. For example, we could use the following to set the tags method.


# app/models/product_tagger.rb

def tag(tag)
  Product.find(product_ids).update_all tags: [tag]
end

This decreases our number of queries to one, but we now fail 2 of our tests. This will set the tags attribute, but overwrites the existing value. This is about as far as we can go with just Ruby/Rails. There is no way that I can find to do the work without iterating over the collection and updating one-by-one.

The Power of the Database

We’re already using some of the power of Postgres in the array type, let’s use even more of its features to handle these updates without so many queries.

The most helpful resource I found when solving this problem were, surprise, surprise, the Postgres docs.

The first Postgres function that jumped out at me was, array_append. This funtion takes 2 arguments.

  1. An array (the current tags field).
  2. An element to append to the array.

So, using our newfound function, let’s just append the tags attributes of each record.


# app/models/product_tagger.rb

def tag(tag)
  Product.find(product_ids).update_all "tags = array_append(tags, #{tag})"
end

Simple enough right? We are now only failing one test, the test that says that tags should not be duplicated. We’re able to continually append elements to the tags array.

The next Postgres function that has been incredibly useful to learn is, unnest. This function takes one argument, an existing array, and returns SQL rows of the values of each element in the array.

We can now use one of the more common bits of SQL, SELECT DISTINCT. We can unnest an array into a set of rows, select only those records that are unique, and then cast them back into an array.

So, our final implementation is as follows:


# app/models/product_tagger.rb

def tag(tag)
  Product.find(product_ids).
    update_all "tags = ARRAY(SELECT DISTINCT UNNEST(array_append(tags, #{tag})))"
end

Conclusion

And that’s it!

We now have an object that can smartly tag multiple objects, all in one query.

We’ve been able to use 2 very powerful tools, Rails and Postgres, to perform a task. It was a great learning experience to dive through the docs, because I knew something should be possible, I just didn’t know how.

Handling Money in Rails – Part III

This is Part III in a series of posts addressing the problems and solutions that I have encountered while building an eCommerce application. Part I and Part II are available here.

For everyone who’s been following along, we’ve already built a Payment class and explored various ways of “monetizing” the class. We’ve built our own accessor methods, and used ActiveRecord::Base‘s composed_of method.

We’re now at the point where we may have multiple classes that will all be dealing with money, and we’re looking for something a little less cumbersome.

Rails Magic

I still distinctly remember the first time I used Rails and I saw how easy it was to define relationships between models. The first time I used has_many, I was hooked; even if I didn’t know how it worked.

So, today, we’re going to define our own method monetize that will do all of the work that we did in our first two posts.

Our end goal will look like this:


# app/models/payment.rb

class Payment < ActiveRecord::Base
  monetize :amount
end

Very succinct, but I also believe very expressive. I think that most people dipping into this code will understand that the amount method will return some sort of “monetized” result. This is much cleaner than defining our own getters and setters, or even using the composed_of method which can be a little confusing.

They’re Just Class Methods!

The day that I realized that has_many, belongs_to, and acts_as_list were just class methods, was when I realized the power of being able to define my own.

So, let’s go ahead and define self.monetize that we can call on our payment class. As you’ll quickly see, I’ve made the decision to define my “monetized” methods using our original approach of having getters and setters. I could just as easily dynamically added composed_of blocks of code, but I’d like this to work on any Ruby object, not just those that inherit from ActiveRecord.


# app/models/payment.rb

monetize :amount

def self.monetize(field)
  define_method field do
    amount = send "#{field}_cents"
    currency = send "#{field}_currency"

    Money.new amount, currency
  end

  define_method "#{field}=" do |value|
    raise ArgumentError unless value.respond_to? :to_money
    money = value.to_money
    send "#{field}_cents=", money.cents
    send "#{field}_currency=", money.currency_as_string
    money
  end
end

This object should still pass all of our original tests with no issues.

How is This Any Different?!

I know, I know, this really didn’t do anything for us. We’re still defining the getters and setters on the class, but now we just have a new method that wraps it. But, we can now extract this method into its own Module and use it anywhere!

So, let’s start by pulling this block of code into its own file in our lib directory.


# lib/monetizable.rb

module Monetizeable
  def self.included(base)
    base.send :extend, ClassMethods
  end

  module ClassMethods
    def monetize(field)
      define_method field do
        amount = send "#{field}_cents"
        currency = send "#{field}_currency"

        Money.new amount, currency
      end

      define_method "#{field}=" do |value|
        raise ArgumentError unless value.respond_to? :to_money
        money = value.to_money
        send "#{field}_cents=", money.cents
        send "#{field}_currency=", money.currency_as_string
        money
      end
    end
  end
end


# app/models/payment.rb

class Payment < ActiveRecord::Base
  include Monetizable
  monetize :amount
end

Wrapping Up

We’ve now created a module that can be used in any class in our application that can “monetize” mutliple attributes. You could also hook into ActiveRecord with an initializer to include the module automatically.

If you’re curious, I’ve also put this into a small gem. Be sure to check out the lint test to easily ensure that your classes are properly monetized.

This is officially the end of our series about handling money in Rails. I’ll continue to post on a regular basis about all of the things I’ve learned along my journey.

Handling Money in Rails – Part II

This is Part II in a series of posts that will address the problems and solutions that I have encountered while building an eCommerce application. Part I

If you’re following our series, we’ve built a Payment class that has methods to set and get amount which is a Money type. Now we’ve reached the point of needing to persist data.

To review, our current implementation of Payment is:


# app/models/payment.rb

class Payment
  attr_reader :amount_cents, :amount_currency

  def initialize
    @amount_cents = 0
    @amount_currency = 'USD'
  end

  def amount=(amount_value)
    raise ArgumentError unless amount_value.respond_to? :to_money
    money = amount_value.to_money
    @amount_cents = money.cents
    @amount_currency = money.currency_as_string
    money
  end

  def amount
    Money.new amount_cents, amount_currency
  end
end

A Migration

The first thing we’ll need is a quick migration to actually be able to persist our payments.


class CreatePayments < ActiveRecord::Migration
  def up
    create_table :payments do |t|
      t.integer :amount_cents,    null: false, default: 0
      t.string  :amount_currency, null: false

      t.timestamps
    end
  end

  def down
    drop_table :payments
  end
end

Composition with composed_of

Ok, now we just need to inherit from ActiveRecord::Base and get to saving and retriving this data from our database. We’re also going to make use of composed_of to eliminate our setters and getters. the full documentation is also available at api.rubyonrails.org.

In essense the method is passed 2 parameters:

  1. The name of the attribute, as a symbol
  2. An options hash

The attribute param should be obvious in our case (:amount), and we’ll look at the options in a little more detail.

The first option we’ll look at is the :class_name, which again should fairly obviously be “Money” since we always want our amount to be an instance of Money.


# app/models/payment.rb

composed_of :amount, class_name: 'Money'

Mapping the Attributes

Next, we’ll look at the :mapping option. This one is a little less apparent from the start, but makes a bit of sense as we examine it more closely.

The :mapping key takes an array of arrays. Each array is a one-to-one mapping with the first item being the attribute of the model class, and the second item being the attribute of the target class, in our case: Money.

The 2 attributes we need to set on our Payment class are amount_cents and amount_currency. These match-up with the cents and currency_as_string attributes on the Money class, respectively.

Attribute in Payment Attribute in Money
amount_cents cents
amount_currency currency_as_string

So, about halfway through, our method call should look like this:


# app/models/payment.rb

composed_of :amount,
  class_name: 'Money',
  mapping: [ %w(amount_cents cents), %w(amount_currency currency_as_string) ]

Getter = Constructor

The next option that we need to set is, :constructor. This key takes either a method name or a Proc that is called when getting our amount. The method or Proc is passed arguments for each of the attributes set in our :mapping arrays (amount_cents, and amount_currency).

Notice that the constructor is essentially the same implementation as the original amount method definition.


# app/models/payment.rb

composed_of :amount,
  class_name: 'Money',
  mapping: [ %w(amount_cents cents), %w(amount_currency currency_as_string) ],
  constructor: Proc.new { |cents, currency| Money.new(cents, currency) }

Setter = Converter

The final option that we need to set is, :converter. This key takes either the name of a method on the class that was set in our earlier :class_name option, or a Proc that is called when setting the amount attribute. This Proc will be called with one argument (the new value to assign to amount), and only if the argument passed is not an instance of :class_name (Money).

Ok, we’re finally ready to finish off our method call and have a fully monetized amount attribute for Payment. Again, notice that this is the same implementation as our original amount= method.


# app/models/payment.rb

composed_of :amount,
  class_name: 'Money',
  mapping: [ %w(amount_cents cents), %w(amount_currency currency_as_string) ],
  constructor: Proc.new { |cents, currency| Money.new(cents, currency) },
  converter: Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(
    ArgumentError, "Can't convert #{value.class} to `Money`"
  ) }

Success! We have a Payment class that knows how to represent an amount as actual money, but can save to any database that only knows about integers and strings.

All of the tests from our earlier suite should still be green, and we’ve pushed all of the converting to ActiveRecord.

Hopefully, you’ve learned a little about the not commonly discussed composed_of method, and are already envisioning ways to compose your models of multiple different types of classes without worrying about serialzing data into the database.

While this is can come in very handily, it can also be cumbersome to use in multiple models. For example, we would most definitely be violating DRY if we had classes representing Payment, Product, LineItem, Order that all had essentially the same composed_of method call. This doesn’t even mention the possibility of having multiple attributes on a model that need to monetized.

So, next time we’ll look into creating our own monetize class method to take care of all of this for us.

Handling Money in Rails – Part I

This is Part I in a series of posts that will address benefits of using an actual Money type in your Ruby or Rails projects.

So, you have a working eCommerce application with customers, products, shopping carts, and line items, now you need to start collecting and managing money in droves and droves.

No problem, right? Just add a Payment class with an amount, and start solving the next problem! But what type of attribute should #amount be?

There are many ways to handle representing money in your system: Fixnum, Float, String, but which will work best?

Most tutorials and examples that I see regarding handling money suggest that you use a Fixnum integer to represent the amount in cents of a product price, a payment amount, or any other object that may need to represent money.

It’s also been suggested to use a Float so we get a closer representation of what money typically looks like. However, anytime this approach is used you’ll see extra code needed to handle precision, and to correct rounding errors.

In almost all cases, when displaying this information to a user, the Rails view helper number_to_currency is used. This should be our first hint that something is a bit off. We just asked Rails to display a number as currency… Shouldn’t there be a way to have a currency object that knows how it should display itself?

Also, when dealing with user input, we can expect to receive a wide range of values that all represent the same amount.

  • 15
  • $15
  • 15.00
  • $15.00

Instead of imposing strict input validations, and using something along the lines of validates_numericality_of our users should be able to enter any of the above and have it just work.

There just seem to be a mountain of issues and edge cases that come with using primitive Ruby constructs to represent a non-primitive object.

This is without even mentioning the possibility of needing to handle multiple currencies, or dealing with exchanging one currency for another.

Luckily, we do have access to Money objects in Ruby; there is a widely accepted library [Money][1]. Similar to how you’re already probably handling amounts, this object accepts an amount in cents, and an optional currency type.

A Failing Test

So, let’s get started by writing a quick test to describe how a future Payment class would handle money amounts. We’re going to test various forms of input that we listed earlier that may make their way through our application at some point.


# test/unit/payment_test.rb

require 'test_helper'

class PaymentTest < MiniTest::Unit::TestCase
  def setup
    @payment = Payment.new
    @fifteen_dollars = Money.new '1500', 'USD'
  end

  def test_string
    @payment.amount = '15'
    assert_equal @fifteen_dollars, @payment.amount
  end

  def test_string_with_symbol
    @payment.amount = '$15'
    assert_equal @fifteen_dollars, @payment.amount
  end

  def test_string_decimal
    @payment.amount = '15.00'
    assert_equal @fifteen_dollars, @payment.amount
  end

  def test_string_decimal_with_symbol
    @payment.amount = '$15.00'
    assert_equal @fifteen_dollars, @payment.amount
  end

  def test_integer
    @payment.amount = 15
    assert_equal @fifteen_dollars, @payment.amount
  end

  def test_decimal
    @payment.amount = 15.00
    assert_equal @fifteen_dollars, @payment.amount
  end
end

A fairly straight forward test that should most obviously fail. Our job is, just as obviously, to get it to pass.

Making It Pass

We're going to start with the most basic Ruby object to represent payments in our system and the behavior they need to encapsulate.


# app/models/payment.rb

class Payment attr_reader :amount_cents, :amount_currency

  def initialize
    @amount_cents = 0
    @amount_currency = 'USD'
  end

  def amount
  end

  def amount=(amount_value)
  end
end

This class should get us passed the first rule of TDD: make the test green or change the error message. We have changed the error message, but now it's time to make it green.

Our first step will be to make our application "money aware" by requiring the gem. As always, this is as simple as adding:


# Gemfile

gem 'money', '~> 5.1.0'

Now to implement #amount=. This needs to take an input value, and set the appropriate instance variables to be used when retrieving the amount.

Again the Money gem will handle this for us in most cases. They have very kindly provided #to_money to coerce any Numeric or String object into money. With this help, our method becomes trivial to implement.


# payment.rb

def amount=(amount_value)
  raise ArgumentError unless amount_value.respond_to? :to_money
  money = amount_value.to_money
  @amount_cents = money.cents
  @amount_currency = money.currency_as_string money
end

Since we also know that a Money object just needs an amount in cents and a currency type, again our amount method becomes simple to implement as well.


# payment.rb

def amount
  Money.new amount_cents, amount_currency
end

Just like that, we should have passing tests and an object that knows how to handle money!

That's it for Part I.