Halting the callback chains

On this article I will share a technique to find out more about callbacks in Ruby on Rails framework.

Callbacks are a common way of implicitly invoking actions when an event fires inside a system. Many web frameworks like Rails or Laravel depend on this mechanism to handle esoteric events. A chain of callbacks is created when multiple callbacks are being fired one after another. Finally, we will call halting such a chain when we stop the event propagation.

Halting a callback in Rails is simple if we use the abstraction provided by the framework. Specifically, calling

throw :abort

will halt the ActiveRecord callbacks.

In larger Rails systems where legacy code or questionable techniques, like depending in implicit ActiveRecord callbacks for domain specific logic, may have been accumulated, halting and debugging callbacks is not a trivial task. One way to approach such troubleshooting is by learning more about the callbacks of our system. To do that we will use the pry debugger.

The class methods we are looking for is:

_#{action_name}_callbacks # e.g., _destroy_callbacks

Then using select, map and filter I am able to list and iterate over any callback given an ActiveModel class:

User._destroy_callbacks.select {|cb| cb.kind == :after }.map(&:filter)

=>
[
  :destroy_user_friends,
  :build_ast,
  :disconnect_from_bank_system,
  :check_soft_delete,
  ...
]

Ruby Blocks

In this article we will discuss how default arguments work and analyze a situation where they can be useful.

The Ruby block

Ruby blocks are code that can be run by other code. All methods can accept blocks, but it’s up to them whether they do something with them or not.

3.times { puts 'hello world' }
3.times do
  puts 'hello world'
end

Blocks can take parameters

Ruby blocks accept parameters and can also initialize them with default values.

def call_block(&block)
  block.call
end

call_block do |greeting = "hi"|
  puts "Block value: '#{greeting}'"
end

In the example above, we initialize the local block variable greeting with the value “hi”.

The behavior can be demonstrated more easily if we use procs:

is_even_proc = Proc.new {|n=1| n%2 == 0 }

is_even_proc(2) # => true
is_even_proc()  # => false, which is the default behavior

Usefulness

For a real example let’s consider the shared examples feature from RSpec, the well know gem for behavior driven development inside the Ruby ecosystem. So, when we use shared_examples we can define a default value and thus avoid code duplication.

# pseudocode

shared_example success |cost=0|
  it "processes the order" do
    expect_any_instance_of(Product).to receive(cost)
  end
end

# Can be used with or without cost

it_behaves_like :success, 1
it_behaves_like :success

Conclusion

To sum up, Ruby allows blocks to receive parameters and enables its initialization with default values. This is applicable is some real world scenarios, such as DRYing up our RSpec suite.

Rack Sailing

In this article we will discuss how we can sail with Rack in a real-world problem.

What Is Rack

Rack is a minimalistic Ruby Web server Interface. As a matter of fact, we can use Rack to build web applications if we follow its protocol. Notably, the protocol is straightforward. We need a Ruby object that responds to the call method which returns a three-element Array:

# rack_hello_world.rb

require 'rack'
 
class HelloWorldApp
  def self.call(env)
    # 200 is the HTTP status code
    # the second element is the response HTTP header hash
    # finally the last element is the response body
    ['200', {'Content-Type' => 'text/html'}, ['A hello world rack app.']]
  end
end

Rack::Handler::WEBrick.run HelloWorldApp

To try the above rack application:

$ gem install rack
$ mkdir rack_hello_world
$ cd rack_hello_world

Write the rack_hello_world.rb file and execute:

$ ruby rack_hello_world.rb
$ curl -I http://localhost:8080

The Middleware

We can compose Rack applications together using middlewares. A middleware basically lets us wrap different inputs and outputs in order to integrate them into our problem-solving.

One common usage is in Rails. For example, Rails uses middlewares to wrap HTTP requests in a simple way.

Continuing from the previous example lets implement a middleware which will add some headers to our response:

# rack_hello_world.rb

# ...

class AddSomeHeaders
  def initialize(app)
    @app = app
  end

  def call(env)
    @app.call(env).tap { |status, headers, body| headers['X-awesome'] = true }
  end
end

app = Rack::Builder.new do
  use AddSomeHeaders
  run HelloWorldApp
end

Rack::Server.start app: app

The response headers:

$ curl -I http://localhost:8080
# => HTTP/1.1 200 OK
# => Content-Type: text/html
# => X-Awesome: true
# => Server: WEBrick/1.3.1 (Ruby/2.3.3/2016-11-21)
# => Date: Thu, 01 Jan 2018 21:29:53 GMT
# => Content-Length: 23
# => Connection: Keep-Alive

The Real World

For a real-world usage of a Rack middleware, we can open the Rails project.

# /actionpack/lib/action_dispatch/middleware/ssl.rb

# I simplified the call for the article's purpose
def call(env)
  request = Request.new env

  if request.ssl?
    @app.call(env).tap do |status, headers, body|
      set_hsts_header! headers
    end
  end
end

As a matter of fact, we can observe that the tap is being used. This basically enables us to manipulate the app object(here the response) in a clean way at a later time. To put it differently, the logic is that @app.call(env) will have to return before the tap block gets executed.

The above is equivalent to:

def call(env)
  request = Request.new env

  if request.ssl?
    res = @app.call(env)
    res[1] = set_hsts_header! res[1]
    res
  end
end

It’s important to realize that if we want to place a middleware before the ActionDispatch::SSL one we would have to tap(sic) into the object after the ActionDispatch::SSL middleware is done.

Indeed, we can observe this functionality if we extend our previous example:

# rack_hello_world.rb

# ...

class EditSomeHeaders
  def initialize(app)
    @app = app
  end

  def call(env)
    @app.call(env).tap do |status, headers, body|
      headers.delete('X-awesome')
      headers['X-double-awesome'] = 'true'
    end
  end
end

app = Rack::Builder.new do
  use EditSomeHeaders
  use AddSomeHeaders
  run HelloWorldApp
end

Rack::Handler::WEBrick.run app

Notice how EditSomeHeaders is being placed before AddSomeHeaders in the middleware stack.

To clarify, execute:

$ curl -I http://localhost:8080
# => HTTP/1.1 200 OK
# => Content-Type: text/html
# => X-Double-Awesome: true # => This has changed
# => Server: WEBrick/1.3.1 (Ruby/2.3.3/2016-11-21)
# => Date: Thu, 01 Jan 2018 22:24:29 GMT
# => Content-Length: 23
# => Connection: Keep-Alive

Meanwhile, the full code example is available on github.

Takeaway

In this article, we’ve explored the basic concepts of Rack, built a Rack middleware and investigated how Rails utilizes
Rack to enforce the SSL policy.

All things considered, reading the code under the hood of a famous or well-engineered library or framework can teach us new methodologies and ways of solving problems. In essence, going through the Rails source code taught us how to use the tap method to manipulate a Rack object that is being manipulated later in the stack.