Okay,

We have a factory that is responsible for creating certain class instances to run background jobs. This class was originally 650 lines long, doing nothing more then for every background entity defining the function my_background_class_command.

for……every……single….class inside our CommandEntity namespace….

Refactoring

Rails has this dogma called Convention over Configuration. I wanted to implement something similar because this factory was nothing more than creating a method for every entity we support.

  • Convention over Configuration
  • Filenames need to match the class they define
  • The entities all reside in their own namespace/folder

Using those three rules, I created a single class method for our factory that checks the entire folder structure, determines whether the class is a correct type and defines the required methods on our factory:

module CommandEntity
  class Factory
    include Singleton

    # Constants
    PATH = 'path/to_my/command_entity/'
    DIRECTORY = Rails.root.join(PATH)
    EXCLUDED = [
      DIRECTORY.join('my_module.rb'),
      DIRECTORY.join('my_second_module.rb'),
      DIRECTORY.join('some_error_class.rb'),
      DIRECTORY.join('factory.rb'),
      DIRECTORY.join('magical_helper.rb'),
      DIRECTORY.join('magical_second_helper.rb'),
      DIRECTORY.join('database_magic.rb'),
      DIRECTORY.join('serializers/json.rb')
    ].freeze

    class << self
      def command_factory
        Dir[DIRECTORY.join('**/*.rb')].entries.each do |file_name|
          next if EXCLUDED.include?(Pathname.new(file_name))

          chunks = file_name.gsub(Rails.root.to_s, '').gsub('.rb', '').gsub(PATH, '')[1..-1]
          method_name = chunks.gsub(::File::SEPARATOR, '_')
          structure = chunks.split(::File::SEPARATOR).reject(&:blank?)

          define_method("#{method_name}_command") do |options = {}|
            "::CommandEntity::#{structure.map(&:camelcase).join('::')}".constantize.new(options)
          end
        end
      end
    end

    command_factory
  end
end

So what exactly are we doing here right now:

  1. We loop over every entry inside the folder that holds our entities
  2. If it’s something to ignore, we move on to the next entity
  3. The filename has everything we need, so throw out the useless parts
  4. Build the method name
  5. Tell the method to dynamically create the class and instantiate it

The result: Reduced a class that was 650 lines long to a mere 50 while retaining all functionality.

The kicker: We don’t even need the class cause all we do is some_class.new(options) for which we really don’t even need a factory in the first place…..

Advertisements