class Gamera::Builder
Builders
provide a generic, standard interface for creating/setting data in an app.
For easy creation of a builder use the +with_options()+ class builder method. For example:
class UserBuilder < Builder.with_options(:name, :bday, :nickname) def build User.create!(name: name, bday: bday, nickname: nickname) end end
The created builder will automatically have methods to set each of the options. In the example above the +UserBuilder#with_nickname+ method will return a new UserBuilder
that has the specified nickname.
UserBuilder.new .with_nickname("shorty") .result #=> User(name: nil, bday: nil, nickname: "shorty")
Sometimes the way you refer to values inside the builder may be different than how clients do. In that case, builders can define setter methods that use terminology that matches the client's point of view.
class UserBuilder < Builder.with_options(:name, :bday) def born_on(a_date) refine_with bday: a_date end end
Default values can be specified using the default_for
class method.
class UserBuilder < Builder.with_options(:name, :bday) default_for :name, "Jane" default_for :bday { Time.now } end
You can handle type conversions using coercion methods.
class UserBuilder < Builder.with_options(:name, :bday) def name_coercion(new_name) new_name ? new_name.to_s : "Bob" end end
To use this builder:
UserBuilder.new .born_on(25.years.ago) .result #=> User(name: "Bob", bday: #<Date: Sat, 09 Dec 1989>)
or
UserBuilder.new .with_bday(25.years.ago) .result #=> User(name: "Bob", bday: #<Date: Sat, 09 Dec 1989>)
or
UserBuilder.new(bday: 25.years.ago) .result #=> User(name: "Bob", bday: #<Date: Sat, 09 Dec 1989>)
Public Class Methods
One way to create builders.
b = Builder.create_with(name: "Bob", bday: 25.years.ago) do User.create!(name: name, bday: bday) end b.build #=> User(name: "Bob", bday: #<Date: Sat, 09 Dec 1989>)
# File lib/gamera/builder.rb, line 109 def self.create_with(spec, &block) struct = Struct.new(*spec.keys) do def initialize(options = {}) super options.each { |opt, value| self[opt] = value } end def with(options, &block) new_struct = dup options.each { |opt, value| new_struct[opt] = value } if block_given? new_struct.class.class_eval do define_method :build, &block end end new_struct end members.each do |opt| define_method "with_#{opt}" do |value, &inner_block| with(opt => value, &inner_block) end end define_method :build, &block end struct.new spec end
# File lib/gamera/builder.rb, line 111 def initialize(options = {}) super options.each { |opt, value| self[opt] = value } end
Another way to create builders.
For easy creation of a builder use the +with_options()+ class builder method. For example:
class UserBuilder < Builder.with_options(:name, :bday, :nickname) def build User.create!(name: name, bday: bday, nickname: nickname) end end
The created builder will automatically have methods to set each of the options. In the example above the +UserBuilder#with_nickname+ method will return a new UserBuilder
that has the specified nickname.
UserBuilder.new .with_nickname("shorty") .result #=> User(name: nil, bday: nil, nickname: "shorty")
# File lib/gamera/builder.rb, line 182 def self.with_options(*option_names) init_arg_list = option_names.map { |o| "#{o}: nil" }.join(', ') args_to_ivars = option_names.map { |o| "@#{o} = #{o}_coercion(#{o})" }.join('; ') Class.new(self) do module_eval <<-METH def initialize(#{init_arg_list}) # def initialize(tags: nil, name: nil) #{args_to_ivars} # @tags = tags_coercion(tags); @name = name_coercion(name) super() # super() end # end METH # +with_...+ methods option_names.each do |name| define_method(:"with_#{name}") do |new_val, *extra| val = if extra.any? # called with multiple params, eg (p1,p2,p3,...), so # package those as an array and pass them in [new_val] + extra else # called with single param new_val end refine_with(name => val) end end protected attr_reader(*option_names) define_method(:options) do Hash[option_names.map { |o| [o, send(o)] }] end option_names.each do |o_name| define_method(:"#{o_name}_coercion") { |new_val| new_val } end end end
Public Instance Methods
Executes the builder
@note Don't call this method directly, use #result
instead.
# File lib/gamera/builder.rb, line 232 def build raise NotImplementedError end
Returns a clone of this object but with options listed in alterations
updated to match.
# File lib/gamera/builder.rb, line 240 def refine_with(alterations) self.class.new options.merge(alterations) end
The object built by this builder
# File lib/gamera/builder.rb, line 225 def result @result ||= build end
# File lib/gamera/builder.rb, line 116 def with(options, &block) new_struct = dup options.each { |opt, value| new_struct[opt] = value } if block_given? new_struct.class.class_eval do define_method :build, &block end end new_struct end