In previous articles I shared how I moved a solution to a problem into a general tool.

Building your own tools helps you avoid solving the same problem over and over again. Not only does it give you more power over the challenges in your system, but it gives you a point of communication about how a problem is solved.

By building tools around your patterns you'll be able to assign a common language to how you understand it's solution. Team members are better able to pass along understanding by using and manipulating the tools of their trade rather than reexplaining a solution and repeating the same workarounds.

We can compress our ideas and solutions into a simpler language by building up the Ruby code that supports it.

Here's the code:

module ProcessLater def later(which_method) later_class.enqueue(initializer_arguments, 'trigger_method' => which_method) end private def later_class self.class.const_get(:Later) end class Later < Que::Job # create the class lever accessor get the related class class << self attr_accessor :class_to_run end # create the instance method to access it def class_to_run self.class.class_to_run end def run(*args) options = args.pop # get the hash passed to enqueue self.class_to_run.new(args).send(options['trigger_method']) end end def self.included(klass) # create the unnamed class which inherits what we need later_class = Class.new(::ProcessLater::Later) # name the class we just created klass.const_set(:Later, later_class) # assign the class_to_run variable to hold a reference later_class.class_to_run = klass end end

I showed how I'd use this code with this sample:

class SomeProcess include ProcessLater def initialize(some_id) @initializer_arguments = [some_id] @object = User.find(some_id) end attr_reader :initializer_arguments def call # perform some long-running action end end

Unfortunately EVERY class that uses ProcessLater will need to implement initializer_arguments . What will happen if you forget to implement it? Errors? Failing background jobs?

Ruby's Comparable library is an example of one that requires a method to be defined in order to be used properly, so it's not an unprecedented idea.

Dangerous combination: implicit dependencies and confusing failures

The Comparable library is a fantastic tool in Ruby's standard library. By defining one method, you gain many other useful methods for comparing and otherwise organizing your objects.

But here's an example of what happens when you don't define that required method:

# in a file called compare.rb class CompareData include Comparable def initialize(data) @data = data end end first = CompareData.new('A') second = CompareData.new('B') first < second # => compare.rb:12:in `<': comparison of CompareData with CompareData failed (ArgumentError)

comparison of CompareData with CompareData failed (ArgumentError) isn't a helpful error message. It even tells me the problem is in the < data-preserve-html-node="true" method and it's an ArgumentError , but it's actually not really there.

If you're new to using Comparable, this is a surprising result and the message tells you nothing about what to do to fix it.

If you know how to use Comparable, you'd immedately spot the problem in our small class: there's no <=> method (often called the "spaceship operator").

The Comparable library has an implicit dependency on <=> in classes where it is used.

We can fix our code by defining it:

# in a file called compare.rb class CompareData include Comparable def initialize(data) @data = data end attr_reader :data def <=>(other) data <=> other.data end end first = CompareData.new('A') second = CompareData.new('B') first < second # => true

After what could have been a lot of head scratching, we've got our comparable data working. Thanks to our knowledge of that implicit dependency, we got past it quickly.

Built-in dependency warning system

Although it's true that the documentation for Comparable says The class must define the <=> operator , it's always nice to know that the code itself will complain in useful ways when you're using it the wrong way.

Sometimes we like to dive into working with the code to get a feel for how things work. Comparable and libraries like it that have implicit dependencies don't lend themselves to playful interaction to discover it's uses.

I mentioned this implicit dependency in the previous article:

The downside with this is that we have this implicit dependency on the initializer_arguments method. There are ways around that and techniques to use to ensure we do that without failure but for the sake of this article and the goal of creating this generalized library: that'll do.

But really, that won't do. Requiring developers to implement a method to use this ProcessLater library isn't bad, but there should be a very clear error to occur if they do forget.

Documentation can be provided (and it should!) but I want the concrete feedback I get from direct interaction with it. I'd hate to have developers spend time toying with a problem only to remember hours later that they forgot the most important part.

Better yet, I'd like to provide them with a way to ensure that they don't forget.

We could check for the method we need when the module is included:

module ProcessLater def self.included(klass) unless klass.method_defined?(:initializer_arguments) raise "Oops! You need to define `initializer_arguments' to initialize this class in the background." end end end class SomeProcess include ProcessLater end # => RuntimeError: Oops! You need to define `initializer_arguments' to initialize this class in the background.

That's helpful noise. And it should be easy to fix:

class SomeProcess include ProcessLater def initializer_arguments # ... end end # => RuntimeError: Oops! You need to define `initializer_arguments' to initialize this class in the background.

Wait a minute! What happened!?

When the Ruby virtual machine processes this code, it executes from the top to the bottom.

The included hook is fired before the required method is defined.

We could include the library after the method definition:

class SomeProcess def initializer_arguments # ... end include ProcessLater end # => SomeProcess

Although that works, other developers will find this to be a weird way of putting things together. Ruby developers tend to expect modules at the top of the source file. Although this example is small, it is, afterall, just an example so we should expect that a real world file would be much larger than just these few lines. Finding dependecies included at the bottom of the file would be a surprise, or perhaps we might not find them at all when first reading.

Everything in it's right place

Let's keep the included module at the top of the file to prevent confusion and make our dependencies clear.

We can automatically define the initializer_arguments method and return an empty array:

module ProcessLater def initializer_arguments; []; end end

But that would do away with the helpful noise when we forget to set it.

One way to ensure that the values are set is to intercept the object initialization. I've written about managing the initialize method before but here's how it can be done:

module ProcessLater def new(*args) instance = allocate instance.instance_variable_set(:@initializer_arguments, args) instance.send(:initialize, *args.flatten) instance end end

The new method on a class is a factory which allocates a space in memory for the object, runs initialize on it, then returns the instance. We can change this method to also set the @initializer_arguments variable.

But this also requires that we change the structure of our module.

Because we want to use a class method ( new ) we need to extend our class with a module instead of including it.

Our ProcessLater module already makes use of the included hook, so we can do what we need there. But first, let's make a module to use under the namespace of ProcessLater .

module ProcessLater module Initializer def new(*args) instance = allocate instance.instance_variable_set(:@initializer_arguments, args) instance.send(:initialize, *args.flatten) instance end end end

Next, we can add a line to the included hook to wire up this new feature:

module ProcessLater def self.included(klass) later_class = Class.new(::ProcessLater::Later) klass.const_set(:Later, later_class) # extend the klass with our Initializer klass.extend(Initializer) later_class.class_to_run = klass end end

The final change, is to make sure that all objects which implement this module, have the initializer_arguments method to access the variable that our Initializer sets.

module ProcessLater attr_reader :initializer_arguments end

No longer possible to forget

Our library will now intercept calls to new and store the arguments on the instance allowing them to be passed into our background job.

Developers won't find themselves in a situation where they could forget to store the arguments for the background job.

Here's what it's like to use it:

class SomeProcess include ProcessLater def initialize(some_id) @some_id = some_id end attr_reader :some_id def call # ... end end

That's a lot simpler than adding a line in every initialize method to store an implicitly required @initializer_arguments variable.

Although developers on your team will no longer find themselves in a situation to forget something crucial, you may still not like overriding the new method like this. That's might be a valid concern for your team, and I have an alternative approach to create a custom and explicit initialize method next time.

For now, however, we can see that Ruby gives us the power to make our code easy to run in the background, but Ruby gives us what we need to automatically manage our dependencies as well.

What this means for other developers

When we write software, we are not only solving a technical or business problem, but we're introducing potential for our fellow developers to succeed or fail.

This can be an important factor in how your team communicates about your work and the code required to do it.

It may be acceptable to have a library like Comparable which implicitly requires a method to be defined. Or perhaps something like that might fall through the cracks and cause bugs too easily.

If we build tools that implicitly require things, it's useful to automatically provide them.

Ready to go

We finally have a tool that can be passed along to others without much fear that they'll run into surprising errors.

Our ProcessLater library is ready to include in our classes. We can take our long-running processes and isolate them in the background by including our module and using our later method on the instance:

class ComplexCalculation include ProcessLater # ...existing code for this class omitted... end ComplexCalculation.new(with, whatever, arguments).later(method_to_run)

This gives us a way to reevaluate the code which might be slow or otherwise time consuming and make a decision to run it later. As developers come together to discuss application performance issues, we'll have a new tool in our vocabulary of potential techniques to overcome the challenges.

Finally, here's the complete library:

module ProcessLater def later(which_method) later_class.enqueue(initializer_arguments, 'trigger_method' => which_method) end attr_reader :initializer_arguments private def later_class self.class.const_get(:Later) end class Later < Que::Job # create the class lever accessor get the related class class << self attr_accessor :class_to_run end # create the instance method to access it def class_to_run self.class.class_to_run end def run(*args) options = args.pop # get the hash passed to enqueue self.class_to_run.new(args).send(options['trigger_method']) end end def self.included(klass) # create the unnamed class which inherits what we need later_class = Class.new(::ProcessLater::Later) # name the class we just created klass.const_set(:Later, later_class) # add the initializer klass.extend(Initializer) # assign the class_to_run variable to hold a reference later_class.class_to_run = klass end module Initializer def new(*args) instance = allocate instance.instance_variable_set(:@initializer_arguments, args) instance.send(:initialize, *args.flatten) instance end end end

When you solve your application's challenges, how to you build new tools? In what ways are the tools you build aiding future developers in there ability to overcome challenges without confusing errors or unknown dependencies?