Class: Gamera::Builder

Inherits:
Object
  • Object
show all
Extended by:
Forwardable, Dsl
Defined in:
lib/gamera/builder.rb

Overview

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>)

Defined Under Namespace

Modules: Dsl

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Dsl

default_for

Class Method Details

.create_with(spec, &block) ⇒ Object

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>)

109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# 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

.with_options(*option_names) ⇒ Object

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")

182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# 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

Instance Method Details

#buildObject

Note:

Don't call this method directly, use #result instead.

Executes the builder


232
233
234
# File 'lib/gamera/builder.rb', line 232

def build
  fail NotImplementedError
end

#refine_with(alterations) ⇒ Object

Returns a clone of this object but with options listed in alterations updated to match.


240
241
242
# File 'lib/gamera/builder.rb', line 240

def refine_with(alterations)
  self.class.new options.merge(alterations)
end

#resultObject

The object built by this builder


225
226
227
# File 'lib/gamera/builder.rb', line 225

def result
  @result ||= build
end