Gem Version Maintainability Test Coverage CircleCI

Industrialist

Industrialist manufactures factories that build self-registered classes.

Background

At the heart of your typical Gang of Four factory method is a case statement:

class Sedan; end
class Coupe; end
class Cabriolet; end

class AutomobileFactory
  def self.build(automobile_type)
    automobile_klass(automobile_type)&.new
  end

  def self.automobile_klass(automobile_type)
    case automobile_type
    when :sedan
      Sedan
    when :coupe
      Coupe
    when :convertible
      Cabriolet
    end
  end
end

AutomobileFactory.build(:sedan)

Another way to do this is with a hash:

class Sedan; end
class Coupe; end
class Cabriolet; end

class AutomobileFactory
  AUTOMOBILE_KLASSES = {
    sedan: Sedan,
    coupe: Coupe,
    convertible: Cabriolet
  }.freeze

  def self.build(automobile_type)
    AUTOMOBILE_KLASSES[automobile_type]&.new
  end
end

AutomobileFactory.build(:coupe)

But, both of these approaches require you to maintain your factory by hand. In order to extend these factories, you must modify them, which violates the Open/Closed Principle.

The Ruby way to do this is with conventions and metaprogramming:

class AutomobileFactory
  def self.build(automobile_type, *args)
    Object.get_const("#{automobile_type.capitalize}").new(*args)
  end
end

But, factories of this type also have issues. If your keys are not easily mapped to a convention, you won't be able to use this type of factory. For example, the Cabriolet class above corresponds to the key :convertible.

You can find a deeper dive into the motivations behind Industrialst here.

Usage

Industrialist creates factories for you. Just extend the Manufacturable module in a base class. This will register a manufacturable type based on the class name. Children of the base class can register themselves with the factory by specifying their corresponding key. To build an instance specify the manufacturable type and key.

class Automobile
  extend Industrialist::Manufacturable
end

class Sedan < Automobile
  corresponds_to :sedan
end

class Coupe < Automobile
  corresponds_to :coupe
end

Industrialist.build(:automobile, :sedan)  # => #<Sedan:0x00007ff64d88ce58>

Manufacturable classes may also correspond to multiple keys:

class Cabriolet < Automobile
  corresponds_to :cabriolet
  corresponds_to :convertible
end

By default, Industrialist factories will return nil when built with an unregistered key. If you would instead prefer a default object, you can designate a manufacturable_default.

class Plane
  extend Industrialist::Manufacturable
end

class Biplane < Plane
  manufacturable_default
  corresponds_to :biplane
end

class FighterJet < Plane
  corresponds_to :fighter
end

Industrialist.build(:plane, :bomber)  # => #<Biplane:0x00007ffcd4165610>

Industrialist can accept any Ruby object as a key, which is handy when you need to define more complex keys. For example, you could use a hash:

class Train
  extend Industrialist::Manufacturable
end

class SteamEngine < Train
  corresponds_to engine: :steam
end

class Diesel < Train
  corresponds_to engine: :diesel
end

class Boxcar < Train
  corresponds_to cargo: :boxcar
end

class Carriage < Train
  corresponds_to passenger: :carriage
end

class Sleeper < Train
  corresponds_to passenger: :sleeper
end

def train_car(role, type)
  Industrialist.build(:train, role => type)
end

train_car(:engine, :diesel)  # => #<Diesel:0x00007ff64f846640>

For convenience, you can also define your own factory classes.

class AutomobileFactory
  extend Industrialist::Factory

  manufactures Automobile
end

AutomobileFactory.build(:sedan)  # => #<Sedan:0x00007ff64d88ce58>

Installation

Add this line to your application's Gemfile:

gem 'industrialist'

And then execute:

$ bundle

Or install it yourself as:

$ gem install industrialist

If you are using Industrialist with Rails, you'll need to

Industrialist.config do |config|
  config.manufacturable_paths << Rails.root.join('app', 'planes')
  config.manufacturable_paths << Rails.root.join('app', 'trains')
  config.manufacturable_paths << Rails.root.join('app', 'automobiles')
end

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/entelo/industrialist.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Industrialist project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.