Factory Methods in Ruby

What is a Ruby factory method?

A factory class is a clean way to have a single factory method that produces various kind of objects.
It takes a parameter, a parameter that tells the method which kind of object to create. For example to generate an Employee or a Boss, depending on the symbol that is passed in:

class Person
def initialize(attributes)
end
end

class Boss
def initialize(attributes)
end
end

class Employee
def initialize(attributes)
end
end

class PersonFactory
TYPES = {
employee: Employee,
boss: Boss
}

def self.for(type, attributes)
(TYPES[type] || Person).new(attributes)
end
end

and then:

employee = PersonFactory.for(:employee, name: 'Danny')
boss = PersonFactory.for(:boss, name: 'Danny')
person = PersonFactory.for(:foo, name: 'Danny')

I also wrote a more detailed blog post about that topic: The Factory Pattern

Factory methods in Ruby

If I make a factory method that is not called1 new or initialize, I guess that doesn't really answer the question "how do I make a ... constructor ...", but I think that's how I would do it...

class Vehicle
def Vehicle.factory vt
{ :Bike => Bike, :Car => Car }[vt].new
end
end

class Bike < Vehicle
end

class Car < Vehicle
end

c = Vehicle.factory :Car
c.class.factory :Bike

1. Calling the method factory works really well in this instructional example but IRL you may want to consider @AlexChaffee's advice in the comments.

Ruby - Method Factory


How should I implement Factory Method on this example?

You shouldn't.

In class-based programming, the factory method pattern is a creational pattern which uses factory methods to deal with the problem of creating objects without specifying the exact class of object that will be created.

When you'll have specialized subclasses of Name, then maybe.

How should my thought process be in order to use such design patterns?

Your first thought should be "does this pattern even make sense here? Do I understand the pattern well enough to be trying to apply it here?". If answers are "yes" and "yes", then you just apply it.

Update:

What you most likely meant to use here is Factory pattern.

In class-based programming, a factory is an abstraction of a constructor of a class

class NameFactory
def self.create(str)
# break str here
Name.new(first_name, last_name)
end
end

# then

@user.name = NameFactory.create(params[:user_name])

In this concrete example, in real life, I wouldn't use any factory stuff. It's just unwarranted complication. But if I have a significantly more complicated class (say, Transaction), then I employ some kind of a creational pattern, yes. Most often it's a Builder.

Ruby factory method to call private setter

This is possible only with a hacky approach:

class Foo
def self.from_bar(bar)
new(bar.foo_id).tap { |f| f.send(:bar=, bar) }
# or, better:
new(bar.foo_id).tap { |f| f.instance_variable_set(:@bar, bar) }
end

private
attr_accessor :bar
end

Honestly, making attr_accessor private makes a little sense, though.

Ruby Factory Method - what is wrong?

Change elseif to elsif (without the second e).

Then make sure to initialize Mage and Warrior classes as you'll get a NameError if you don't.

Rails - Force model to be created via factory method

Well, it's possible to make the constructor private:

private_class_method :new

And of course, you can try making the ActiveRecord query methods (.find, .where etc.) private as well. But to me that sounds like a good way to end up with erratic behaviour. If you were to go this route, make sure your app is thoroughly tested first.


Another route would be for Cart not to extend ActiveRecord::Base (which I'm assuming it does), and instead include only the parts you need, like ActiveRecord::Persistence. If you are willing to dive in deep, check out the parts that are included in the source for ActiveRecord::Base.


Edit: Still one option would be to make Cart itself private within a module that only exposes CartFactory. There's no built-in syntax for a "private class", but it's possible to achieve since Ruby classes are just regular objects. Again, no idea how well ActiveRecord would deal with that.


But lastly there is of course the question of whether you want to do this at all. In general, Ruby is not very good at protecting you from yourself. :) As expressed in the latter linked answer, documentation and trust go a long way.

Creating multiple different factories using the same create method

No, it is not possbile to create factory_1 and factory_2 from the same command.

Here, 'factory1' is the Model or class whose objects are created.

FactoryBot.create(:factory1)

If you are doing it for making spec DRY, then rather than doing as above, you can do as below :

[
[:factory_1, traits_1_factory_1],
[:factory_2, traits_1_factory2]
].each do |factory|
FactoryBot.create(*factory)
end

Store multiple factories in array and iterate it.

Ruby design pattern: How to make an extensible factory class?

You don't need a LogFileReaderFactory; just teach your LogFileReader class how to instantiate its subclasses:

class LogFileReader
def self.create type
case type
when :git
GitLogFileReader.new
when :bzr
BzrLogFileReader.new
else
raise "Bad log file type: #{type}"
end
end
end

class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
end

class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
end

As you can see, the superclass can act as its own factory. Now, how about automatic registration? Well, why don't we just keep a hash of our registered subclasses, and register each one when we define them:

class LogFileReader
@@subclasses = { }
def self.create type
c = @@subclasses[type]
if c
c.new
else
raise "Bad log file type: #{type}"
end
end
def self.register_reader name
@@subclasses[name] = self
end
end

class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
register_reader :git
end

class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
register_reader :bzr
end

LogFileReader.create(:git).display
LogFileReader.create(:bzr).display

class SvnLogFileReader < LogFileReader
def display
puts "Subersion reader, at your service."
end
register_reader :svn
end

LogFileReader.create(:svn).display

And there you have it. Just split that up into a few files, and require them appropriately.

You should read Peter Norvig's Design Patterns in Dynamic Languages if you're interested in this sort of thing. He demonstrates how many design patterns are actually working around restrictions or inadequacies in your programming language; and with a sufficiently powerful and flexible language, you don't really need a design pattern, you just implement what you want to do. He uses Dylan and Common Lisp for examples, but many of his points are relevant to Ruby as well.

You might also want to take a look at Why's Poignant Guide to Ruby, particularly chapters 5 and 6, though only if you can deal with surrealist technical writing.

edit: Riffing of off Jörg's answer now; I do like reducing repetition, and so not repeating the name of the version control system in both the class and the registration. Adding the following to my second example will allow you to write much simpler class definitions while still being pretty simple and easy to understand.

def log_file_reader name, superclass=LogFileReader, &block
Class.new(superclass, &block).register_reader(name)
end

log_file_reader :git do
def display
puts "I'm a git log file reader!"
end
end

log_file_reader :bzr do
def display
puts "A bzr log file reader..."
end
end

Of course, in production code, you may want to actually name those classes, by generating a constant definition based on the name passed in, for better error messages.

def log_file_reader name, superclass=LogFileReader, &block
c = Class.new(superclass, &block)
c.register_reader(name)
Object.const_set("#{name.to_s.capitalize}LogFileReader", c)
end


Related Topics



Leave a reply



Submit