Words and Code

One writer’s journey from words to code.

Digging Into the Finder Object Pattern


Most developers aren’t ever completely happy with their code. I’m no exception to this stereotype — I almost always know that I could probably write a cleaner, more concise method, controller, or class. Usually, it’s a matter of not know the best tool to reach for to refactor my code; eventually, I learn a new pattern or form of encapuslating logic that I later use to make my old code a lot better.

But a few weeks ago, I wrote 100 lines of beautiful code. I’m talking about a goregous, straightforward, no-nonsense class that did a lot in a relatively few lines of Ruby. I still feel pretty proud of it (can you tell?), and part of the reason for this is because I also learned a new pattern while writing this class. I was actually pairing with another developer, and we wanted to try using a rather common Rails pattern to solve a problem we were running into again and again: messy queries in our controllers and models.

We both were familiar with the concept of “skinny controllers” and “fat models”, or the idea that your controllers shouldn’t be responsible for containing the logic specific to a model. However, we also didn’t want our models to get out of control in size, which is exactly what was starting to happen. So, we searched for a workaround, and found our answer in one of the most elegant patterns I’ve seen in awhile: finder objects. Finder objects are simple Ruby classes that encapuslate the logic behind querying the database, and they are hands down, my new favorite kind of Ruby object.

Scopin’ Down The Problem of Scopes


In our bookstore application, we have Order objects, which represent the orders that a user places in our system. However, these are books that we’re dealing with, and eventually we need to address the whole fulfillment process, which will need to be represented by a whole other set of models. For now, we’ll try to keep it as simple as possible. Let’s say that an Order has many Shipments, and each shipment represents a batch of Book objects (read: products) that need to be shipped out together.

Right off the bat, we know that our Shipment model will have some kind of state machine that will need to track the different stages of the shipment phase. Again, to keep it simple, let’s say that there are five different stages or states of a shipment:

  1. A shipment starts off as processing once an order has been placed.
  2. Once it has been processed, it needs a label with the shipping address information, so it transitions to the needs_label state.
  3. After it has a label generated, it’ll need a tracking number, so transitions to the needs_tracking state.
  4. Once it has a tracking number, it transitions to being ready for shipment.
  5. Finally, when the shipment is actually sent out of the warehouse and to our shipping service, it should be marked as shipped.

To be clear, this is a super simplified version of what would happen in a real-life application! Now, in our admin panel, let’s say that we have a page that will render all of our shipments, which should always ordered by when they were created, so that our admins know which shipments to process, and in which order.

We can take it a step further and say that our main admin panel page — which will correspond to the index action in our controller, should show our admins the most urgent shipments that need their attention as soon as they log in; in other words, these would be the shipments that were created more than a week ago, but still haven’t been processed.

I think we can all agree that basic Rails best practices should steer us away from doing something like this:

1
2
3
4
5
6
7
class ShipmentsController < ApplicationController
  def index
    @shipments = Shipment.where(state: :ready)
          .where('created_at <= ?', Time.zone.now + 7.days)
               .order(created_at: :desc)
  end
end

We definitely know that the controller really shouldn’t be responsible for querying for the correct Shipment objects. All our controller should have to do is just render the correct ones, and not go digging for them in the database!

Okay, so we’ll create some scopes for these queries instead. These scopes should live in our model, not in the controller, right? Let’s see what our model might look like if we take that approach:

1
2
3
4
5
6
7
8
9
class Shipment < ActiveRecord::Base
  belongs_to :order

  default_scope -> { order(created_at: :desc) }
  scope :shipped, -> { where(state: 'shipped') }
  scope :urgent, -> {
      where('created_at <= ?', Time.zone.now + 7.days)
      }
end

This cleans things up a decent bit! Scopes are actually part of the Active Record Query Interface, and they allow us to specify commonly-used queries which we can then actually reference and use in the form of method calls on our models themselves. They are really nothing more than defining class methods on a model:

1
2
3
4
5
class Shipment < ActiveRecord::Base
  def self.shipped
      where(state: 'shipped')
  end
end

However, they’re pretty wonderful because you can just chain them as method calls (Shipment.urgent.shipped), but all they do is execute the correct query for you:

1
2
Shipment.shipped
=> SELECT "shipments".* FROM "shipments" WHERE "shipments"."state" = "shipped"

But the good news about scopes is also the bad news: you can just keep adding them and adding them, and chaining them onto everything. Our Shipment class has only three scopes at the moment, one of them being the default scope. But, we’ll probably have a page that should only render only the shipments that are in the needs_label state, or a page that maps to a controller action which should only return shipments that are in the needs_tracking state. We could keep adding scopes and then have our controller actions call the scopes on the class to return the appropriate shipments from our database.

Or, we could try something a little different.

Finder Objects


Our scopes don’t actually add any new behavior to our model. As we saw earlier, they are nothing more than macros for querying for the correct rows from our shipments table in our database. So, it really doesn’t make sense for our model to contain all this logic that doesn’t add any new behavior to it.

It would be nice, however, if we could apply the rule of “separation of concerns” here, and have an entire class whose sole responsibility would be to dig up the correct objects based on the parameters we were querying by. Really, this class should do nothing more than find the right objects. You might even say that instances of this class are just…finder objects! (Get it? Man, I really hope you got it.)

Anyways, how might this class look? Well, we don’t need any of the functionality from ActiveRecord, so we can create it as just a Plain Old Ruby Class:

1
2
class ShipmentFinder
end

Next, we’ll want to tell our finder object which models to query for, so it will know how to construct our queries, and which table to query. Since we never really want to have this method accessible elsewhere, we can make it a private class method that will only be called from the context of another ShipmentFinder class method:

1
2
3
4
5
6
7
8
class ShipmentFinder
  class << self
      private
      def resource_class
          ::Shipment
      end
    end
end

Now, we can use Rails’ arel DSL to construct a basic query. To do this, we’ll need a method that returns the table, which can also be a private method since we’ll never want to call it explicitly:

1
2
3
4
5
6
7
8
9
10
11
12
13
class ShipmentFinder
    class << self
      private

      def table
        resource_class.arel_table
      end

      def resource_class
        ::Shipment
      end
    end
end

Now, we can write as many specific queries as we want. For example, we could write a urgent_needs_tracking class method that could constructs a query using two private methods, needs_tracking and is_ready:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class ShipmentFinder
    class << self
      def urgent_needs_tracking
        query = needs_tracking.and(is_ready)

        resource_class.where(query)
      end

      def shipped
        resource_class.where(state: 'shipped')
      end

      private

      def urgent
          resource_class.where('created_at <= ?', Time.zone.now + 7.days))
      end

      def is_ready
        table[:state].eq('ready')
      end

      def needs_tracking
        table[:state].eq('needs_tracking')
      end

      def needs_label
        table[:state].eq('needs_label')
      end

      def table
        resource_class.arel_table
      end

      def resource_class
        ::Shipment
      end
    end
end

Now we can rely on our ShipmentFinder class to find our shipments that have been created more than 7 days ago, and still don’t have a tracking number. We also can add more functionality that can be used in specific instances, which is exactly what we’ve done with our needs_label and is_ready private methods. We can use arel to construct queries using those methods to scope down what objects are actually returned. We don’t have to do anything fancy, if we don’t want to. Take a look at that shipped method — this is just using a simple where arel method to construct a query that reads like this: SELECT "shipments".* FROM "shipments" WHERE "shipments"."state" = "shipped".

Finally, the last step: it’s time for us to actually call on our finder object to do its job!

Cleaner Querying, Cleaner Controllers

Let’s bring it all together by going back to our ShipmentsController and add our new finder object into it. Our index action should now be able to account for different types of shipments that our admins might want to query for. For now, we’ll construct our controller to accept a query parameter in our params hash that will be that status of the types of shipments we want to return. Depending on the frontend framework we’re using, this might be a dropdown or checkbox option that will set a value on the status key in our params hash.

Our controller action could now be rewritten to look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
class ShipmentsController < ApplicationController
  def index
      status = params[:status]
      status_method = status.to_sym

      if ShipmentFinder.respond_to?(status_method)
          @shipments = ShipmentFinder.send(status_method)
      else
          @shipments = Shipment.all
      end
  end
end

Here, we’re accessing the query param from params[:status], and turning it into a symbol (status_method). Next, we’re using Ruby’s super handy respond_to? method, and sending our status_method symbol to our ShipmentFinder. So, if our admin selects an option that sends the query param urgent_needs_tracking, we are telling our finder object to call that method, and execute that query. The return value of the query executed by our ShipmentFinder’s urgent_needs_tracking method is what will be set as the instance variable @shipments for the duration of this action on the controller.

If no query param is set, or if our ShipmentFinder doesn’t have a method that maps to a query param, we’re just returning all of our Shipment objects by default.

This is quite an improvement from our earlier code, which had our controller digging for rows in a database that it really didn’t have anything to do with. Now, we’ve separated our concerns into a new finder object, which exists as its own query interface on its own. It’s also worth noting that sometimes, creating a finder object can be overkill, and sometimes, if we have a lot of finder objects, we’d probably want to abstract a lot of this functionality out into a BaseFinder class, which our finder objects could inherit from. But this is definitely a great start. No more digging for us!


tl;dr?

  • The finder object pattern helps keep your model logic strictly related to a class’ behavior, while also keeping your controller’s skinny. Since they are nothing more than plain old Ruby classes, finder objects don’t need to inherit from ActiveRecord::Base, and should be responsible for nothing more than executing queries. Read more about them on this fantastic blog post.
  • Scopes are a great tool to use if a finder object seems like more work than it’s really worth, given the size and context of your application. Read more about scopes in the Rails documentation.
  • Want to see more examples of implementing finder objects? Check out this slidedeck series.