---
title: Using `yield_self` for composable ActiveRecord relations
teaser: On the code-changing magic of `yield_self`.
tags: rails,ruby,web
author: Derek Prior
published_on: 2018-03-13
---

Ruby 2.5 introduces [`Object#yield_self`][yield_self], which can be thought of
as a close cousin to [`Object#tap`][tap]. Where `tap` executes a block returning
the value it's called on, `yield_self` yields the object its called on into the
supplied block, returning the result of the block.

Many have compared `yield_self` to [Elixir's pipe operator][pipe], `|>`, and
while I use and enjoy the pipe operator in Elixir, I had a hard time envisioning
how I'd use `yield_self` in my Ruby code. However, days after my client project
was updated to Ruby 2.5, an opportunity to use `yield_self` just about smacked
me in the face. Let's walk through it together.

The project I'm working on makes use of several query objects. The query objects
take a base relation and some parameters, ultimately producing relation that
generally represnts a fairly complex SQL query. In this application, each of the
query objects has the following shape:

```ruby
class QueryObjectName
  def self.call(base_relation, params)
    new(base_relation, params).call
  end

  def initialize(base_relation, params)
    @base_relation = base_relation
    @params = params
  end

  def call
    # do the work
  end

  private

  attr_reader :base_relation, :params
end
```

The `call` method on many of these objects had become rather complex, with
various clauses being added to the relation based on the value of different
parameters. A simplified version of the `call` method on one such object looked
like this:

```ruby
def call
  patients_with_care_periods = base_relation.joins(:care_periods)

  patients_at_provider = if params.care_provider_id.present?
    patients_with_care_periods.
      where(care_periods: { care_provider_id: params.care_provider_id })
  else
    patients_with_care_periods
  end

  patients_at_provider_from_hospital = if params.hospital_id.present?
    patients_at_provider.
      where(care_periods: { hospital_id: params.hospital_id })
  else
    patients_at_provider
  end

  if params.discharge_period.present?
    patients_at_provider_from_hospital.
      joins(:hospital_visit).
      where(hospital_visits: { end_on: params.discharge_period }
  else
    patients_at_provider_from_hospital
  end
end
```

I found this code confusing for a number of reasons, chief among them being the
local variable assignments. For example, just prior to the last `if`,
`patients_at_provider_from_hospital` is the most "up to date relation" we're
working on, but that name is misleading. Depending on the value of particular
parameters, the relation with that name may not say anything about the care
provider or the hospital.

While extracting the code to appropriately named private methods could clean
this up a bit, it would also leave a confusing string of nested method calls.
Then I remembered `yield_self`! Rewriting the code to use my new friend made it
look like this:

```ruby
def call
  base_relation.
    joins(:care_periods).
    yield_self do |relation|
      if params.care_provider_id.present?
        relation.where(care_periods: { care_provider_id: params.care_provider_id })
      else
        relation
      end
    end.yield_self do |relation|
      if params.hospital_id.present?
        relation.where(care_periods: { hospital_id: params.hospital_id })
      else
        relation
      end
    end.yield_self do |reation|
      if params.discharge_period.present?
        relation.
          joins(:hospital_visit).
          where(hospital_visits: { end_on: params.discharge_period }
      else
        relation
      end
    end
end
```

Hmm. Well, we've rid ourselves of those confusing names, but this code certainly
doesn't bring me joy. To take the next step, we're going to need the
underappreciated [`method`][method] method in combination with `&` which will
convert the method to a `Proc`.

```ruby
def call
  base_relation.
    joins(:care_periods).
    yield_self(&method(:care_provider_clause)).
    yield_self(&method(:hospital_clause)).
    yield_self(&method(:discharge_period_clause))
end

private

def care_provider_clause(relation)
  if params.care_provider_id.present?
    relation.where(care_periods: { care_provider_id: params.care_provider_id })
  else
    relation
  end
end

def hospital_clause(relation)
  if params.hospital_id.present?
    relation.where(care_periods: { hospital_id: params.hospital_id })
  else
    relation
  end
end

def discharge_period_clause(relation)
  if params.discharge_period.present?
    relation.
      joins(:hospital_visit).
      where(hospital_visits: { end_on: params.discharge_period }
  else
    relation
  end
end
```

<aside class="info">
Since
<a href="https://www.ruby-lang.org/en/news/2018/12/25/ruby-2-6-0-released/">
  version 2.6
</a> Ruby has an alias for <code>yield_self</code> called <code>then</code>.
Using it alongside numbered parameters (<a>introduced in 2.7</a>), the method
<code>call</code> above could also be written as:

<div class="highlight"><pre class="highlight ruby"><code><span class="k">def</span> <span class="nf">call</span>
  <span class="n">base_relation</span><span class="p">.</span>
    <span class="nf">joins</span><span class="p">(</span><span class="ss">:care_periods</span><span class="p">).</span>
    <span class="nf">then</span> <span class="p">{</span> <span class="n">care_provider_clause</span> <span class="n">_1</span> <span class="p">}.</span>
    <span class="nf">then</span> <span class="p">{</span> <span class="n">hospital_clause</span> <span class="n">_1</span> <span class="p">}.</span>
    <span class="nf">then</span> <span class="p">{</span> <span class="n">discharge_period_clause</span> <span class="n">_1</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre></div>

Check
<a href="https://thoughtbot.com/blog/pipelining-without-pipes-in-ruby">
  this article
</a>
for more details on how to use these features in operation pipelines.
</aside>

This code brought me joy. [Marie Kondo] would encourage me to keep this code. I
believe this code is more readable at each step, even accounting for the
possible unfamiliarity with `yield_self` and `&method`. One can reasonably
expect that `yield_self` will become increasingly familiar to Ruby developers
and with that, perhaps `&method` will find happy new users as well.

[yield_self]: http://ruby-doc.org/core-2.5.0/Object.html#method-i-yield_self
[tap]: http://ruby-doc.org/core-2.5.0/Object.html#method-i-tap
[pipe]: https://elixirschool.com/en/lessons/basics/pipe-operator/
[method]: http://ruby-doc.org/core-2.5.0/Object.html#method-i-method
[Marie Kondo]: https://konmari.com/about/the-method
