Factory

class
Superclass
Object
Included Modules
Enumerable
Extended With
Loggability

The fluent fixture monadic factory class.

Constants

CREATE_METHODS

The methods to look for to save new instances when create is called

DEFAULT_GENERATOR_LIMIT

The default limit for generators

Attributes

constructor_args[R]

The Array of arguments to pass to the constructor when creating a new fixtured object.

constructor_block[R]

The block to pass to the constructor when creating a new fixtured object.

decorators[R]

The decorators that will be applied to the fixtured object when it's created.

fixture_module[R]

The fixture module that contains the decorator declarations

Public Class Methods

anchor
new( fixture_module, *args, &block )

Create a new FluentFactory that will act as a monadic factory for the specified fixture_module, and use the given args in the construction of new objects.

# File lib/fluent_fixtures/factory.rb, line 26
def initialize( fixture_module, *args, &block )
        @fixture_module = fixture_module
        @constructor_args = args
        @constructor_block = block
        @decorators = []
end

Public Instance Methods

anchor
create( args={}, &block )

Return a saved instance of the fixtured object.

# File lib/fluent_fixtures/factory.rb, line 104
def create( args={}, &block )
        obj = self.with_transaction do
                obj = self.instance( args, &block )
                obj = self.try_to_save( obj )
                obj
        end

        return obj
end
anchor
decorated_with( &block )

Return a copy of the factory that will apply the specified block as a decorator.

# File lib/fluent_fixtures/factory.rb, line 116
def decorated_with( &block )
        return self.mutate( nil, &block )
end
anchor
each( &block )

Iterate over DEFAULT_GENERATOR_LIMIT instances of the fixtured object, yielding each new instance if a block is provided. If no block is provided, returns an Enumerator.

# File lib/fluent_fixtures/factory.rb, line 124
def each( &block )
        return self.generator unless block
        return self.generator.each( &block )
end
anchor
generator( create: false, limit: DEFAULT_GENERATOR_LIMIT, &block )

Return an infinite generator for unsaved instances of the fixtured object.

# File lib/fluent_fixtures/factory.rb, line 131
def generator( create: false, limit: DEFAULT_GENERATOR_LIMIT, &block )
        return Enumerator.new( limit || Float::INFINITY ) do |yielder|
                count = 0
                constructor = create ? :create : :instance
                loop do
                        break if limit && count >= limit

                        obj = if block
                                        self.send( constructor, &block.curry(2)[count] )
                                else
                                        self.send( constructor )
                                end

                        yielder.yield( obj )

                        count += 1
                end
        end
end
anchor
initialize_copy( original )

Copy constructor – make a distinct copy of the clone's decorators.

# File lib/fluent_fixtures/factory.rb, line 35
def initialize_copy( original )
        @decorators = @decorators.dup
end
anchor
inspect()

Return a human-readable representation of the object suitable for debugging.

# File lib/fluent_fixtures/factory.rb, line 153
def inspect
        decorator_description = self.decorators.map( &:first ).join( ' + ' )

        return "#<%p:%0#16x for %p%s>" % [
                self.class,
                self.__id__ * 2,
                self.fixture_module,
                decorator_description.empty? ? '' : ' + ' + decorator_description
        ]
end
anchor
instance( args={}, &block )

Create an instance, apply declared decorators in order, and return the resulting object.

# File lib/fluent_fixtures/factory.rb, line 68
def instance( args={}, &block )
        instance = self.fixture_module.
                fixtured_instance( *self.constructor_args, &self.constructor_block )

        self.decorators.each do |decorator_name, decorator_args, block|
                # :TODO: Reify other fixtures in `decorator_args` here?
                if !decorator_name
                        self.apply_inline_decorator( instance, block )
                elsif self.fixture_module.decorators.key?( decorator_name )
                        instance = self.apply_named_decorator( instance, decorator_args, decorator_name )
                else
                        self.apply_method_decorator( instance, decorator_args, decorator_name, block )
                end
        end

        args.each_pair do |attrname, value|
                # :TODO: Reify the `value` if it responds to #create?
                instance.public_send( "#{attrname}=", value )
        end

        # If the factory was called with a block, use it as a final decorator before
        # returning it.
        if block
                self.log.debug "Applying inline decorator %p" % [ block ]
                if block.arity.zero?
                        instance.instance_exec( &block )
                else
                        block.call( instance )
                end
        end

        return instance
end
anchor
mutate( name, *args, &block )

Return a new clone of the receiver with an additional decorator composed of the specified name, args, and block.

# File lib/fluent_fixtures/factory.rb, line 59
def mutate( name, *args, &block )
        new_instance = self.dup
        new_instance.decorators << [ name, args, block ]
        return new_instance
end

Protected Instance Methods

anchor
apply_inline_decorator( instance, block )

Apply a decorator block added to the factory via decorated_with to the instance.

# File lib/fluent_fixtures/factory.rb, line 170
def apply_inline_decorator( instance, block )
        self.log.debug "Applying anonymous inline decorator %p" % [ block ]
        if block.arity.nonzero?
                block.call( instance )
        else
                instance.instance_eval( &block )
        end
end
anchor
apply_method_decorator( instance, args, name, block )

Apply a decorator declared by calling the proxy method with the specified name, args, and block to the given instance.

# File lib/fluent_fixtures/factory.rb, line 200
def apply_method_decorator( instance, args, name, block )
        self.log.debug "Mutating instance %p with regular method %p( %p, %p )" %
                [ instance, name, args, block ]
        if block
                instance.public_send( name, *args )
        else
                instance.public_send( name, *args, &block )
        end
end
anchor
apply_named_decorator( instance, args, decorator_name )

Apply a decorator declared in the fixture module by the given decorator_name to the specified instance.

# File lib/fluent_fixtures/factory.rb, line 182
def apply_named_decorator( instance, args, decorator_name )
        decorator_block = self.fixture_module.decorators[ decorator_name ] or
                raise "non-existent fixture `%s`" % [ decorator_name ]
        decorator_options = self.fixture_module.decorator_options[ decorator_name ] || {}
        self.log.debug "Applying decorator %p (%p - %p) to a %p with args: %p" %
                [ decorator_name, decorator_block, decorator_options, instance.class, args ]

        self.apply_prelude( instance, decorator_options[:prelude] ) if decorator_options[:prelude]

        instance = self.try_to_save( instance ) if decorator_options[:presave]
        instance.instance_exec( *args, &decorator_block )

        return instance
end
anchor
apply_prelude( instance, prelude, args=[] )

Apply a decorator prelude to the current instance.

# File lib/fluent_fixtures/factory.rb, line 212
def apply_prelude( instance, prelude, args=[] )
        case prelude
        when Symbol
                self.log.debug "Applying single prelude decorator: %p" % [ prelude ]
                self.apply_named_decorator( instance, args, prelude )
        when Array
                self.log.debug "Applying multiple prelude decorators: %p" % [ prelude ]
                prelude.each do |sublude|
                        self.apply_prelude( instance, sublude, args )
                end
        when Hash
                self.log.debug "Applying one or more prelude decorators with args: %p" % [ prelude ]
                prelude.each do |sublude, args|
                        self.apply_prelude( instance, sublude, args )
                end
        else
                raise ArgumentError, "unhandled prelude type %p" % [ prelude.class ]
        end
end
anchor
method_missing( sym, *args, &block )

Proxy method – look up the decorator with the same name as the method being called, and if one is found, returned a new instance of the factory with the additional decorator.

# File lib/fluent_fixtures/factory.rb, line 273
def method_missing( sym, *args, &block )
        return self.mutate( sym, *args, &block )
end
anchor
try_to_save( object )

Try various methods for saving the given object, logging a warning if it doesn't respond to any of them.

# File lib/fluent_fixtures/factory.rb, line 249
def try_to_save( object )
        object = self.fixture_module.call_before_saving( object ) if
                self.fixture_module.respond_to?( :call_before_saving )

        save_method = CREATE_METHODS.find do |methodname|
                object.respond_to?( methodname )
        end

        if save_method
                object.public_send( save_method )
        else
                self.log.warn "create: don't know how to save %p" % [ object ]
        end

        object = self.fixture_module.call_after_saving( object ) if
                self.fixture_module.respond_to?( :call_after_saving )

        return object
end
anchor
with_transaction( ) { || ... }

Look for common transaction mechanisms on the fixtured class, and wrap one of them around the block if one exists. If no transaction mechanism can be found, just yield to the block.

# File lib/fluent_fixtures/factory.rb, line 236
def with_transaction( &block )
        fixtured_class = self.fixture_module.fixtured_class
        if fixtured_class.respond_to?( :db )
                self.log.debug "Using db.transaction for creation."
                return fixtured_class.db.transaction( &block )
        else
                yield
        end
end