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
Does Ruby Have the Java Equivalent of Synchronize Keyword
Undef - Why Would You Want to Undefine a Method in Ruby
Git Deployment + Configuration Files + Heroku
Getting Webpage Content with Ruby -- I'm Having Troubles
Force Strings to Utf-8 from Any Encoding
How to Spawn a Child Process in Ruby
What Is the Modern Way to Structure a Ruby Gem
Iconv Deprecation Warning with Ruby 1.9.3
How Does Rails Csrf Protection Work
Rails: Plus Sign in Get-Request Replaced by Space
How to Deal with Not Knowing What Exceptions Can Be Raised by a Library Method in Ruby
How Would I Save Multiple Records at Once in Rails
Why Is 'Return a or B' a Void Value Expression Error in Ruby
How to Avoid Putting the Magic Encoding Comment on Top of Every Utf-8 File in Ruby 1.9
Cannot Load Such File -- Readline (Loaderror) When Running Heroku Create --Stack Cedar
Why Does Accessing a Ssl Site with Mechanize on Windows Fail, But on MAC Work