Class: FlatMap::OpenMapper::Factory

Inherits:
Object
  • Object
show all
Defined in:
lib/flat_map/open_mapper/factory.rb

Overview

Mapper factory objects are used to store mounting and trait definitions and to instantiate and setup corresponding mapper objects thereafter. Factory objects are stored by mapper classes in opposite to actual mounted mappers that are stored by mapper objects themselves.

Instance Method Summary collapse

Constructor Details

#initialize(identifier, options = {}, &block) ⇒ Factory

Initializes factory with an identifier (name of a mounted mapper, or the actual class for a trait) and a set of options. Those args are used to create actual mapper object for the host mapper.

Parameters:

  • identifier (Symbol, Class)

    name of a mapper or mapper class itself

  • options (Hash) (defaults to: {})


14
15
16
# File 'lib/flat_map/open_mapper/factory.rb', line 14

def initialize(identifier, options = {}, &block)
  @identifier, @options, @extension = identifier, options, block
end

Instance Method Details

#create(mapper, *owner_traits) ⇒ Object

Create a new mapper object for mounting. If the factory is traited, the new mapper is a part of a host mapper, and is ‘owned’ by it. Otherwise, assign the name of the factory to it to be able to find it later on.

Parameters:

  • mapper (FlatMap::OpenMapper)

    Host mapper

  • owner_traits (*Symbol)

    List of traits to be applied to a newly created mapper



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/flat_map/open_mapper/factory.rb', line 206

def create(mapper, *owner_traits)
  save_order = @options[:save] || fetch_save_order(mapper) || :after
  new_one = mapper_class.new(fetch_target_from(mapper), *(traits + owner_traits).uniq, &@extension)
  if traited?
    new_one.owner = mapper
  else
    new_one.host = mapper
    new_one.name = @identifier
    new_one.save_order = save_order

    if (suffix = @options[:suffix] || mapper.suffix).present?
      new_one.suffix = suffix
      new_one.name   = :"#{@identifier}_#{suffix}"
    else
      new_one.name = @identifier
    end
  end
  new_one
end

#explicit_target(mapper) ⇒ Object?

Try to use explicit target definition passed in options to fetch a target. If this value is a Proc, will call it with owner target as argument.

Parameters:

Returns:

  • (Object, nil)

    target for new mapper.



119
120
121
122
123
124
125
126
127
128
# File 'lib/flat_map/open_mapper/factory.rb', line 119

def explicit_target(mapper)
  if @options.key?(:target)
    target = @options[:target]
    case target
    when Proc   then target.call(mapper.target)
    when Symbol then mapper.send(target)
    else target
    end
  end
end

#fetch_save_order(mapper) ⇒ Symbol

Return order relative to target of the passed mapper in which mapper to be created should be saved. In particular, targets of :belongs_to associations should be saved before target of mapper is saved.

Parameters:

Returns:

  • (Symbol)


191
192
193
194
195
196
197
# File 'lib/flat_map/open_mapper/factory.rb', line 191

def fetch_save_order(mapper)
  return :after unless mapper.is_a?(ModelMapper)

  reflection = reflection_from_target(mapper.target)
  return unless reflection.present?
  reflection.macro == :belongs_to ? :before : :after
end

#fetch_target_from(mapper) ⇒ Object

Fetch the target for the mapper being created based on target of a host mapper.

Parameters:

Returns:

  • (Object)

    target for new mapper



91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/flat_map/open_mapper/factory.rb', line 91

def fetch_target_from(mapper)
  owner_target = mapper.target

  return owner_target if traited?

  if open_mount?
    explicit_target(mapper) || new_target
  else
    explicit_target(mapper) ||
      target_from_association(owner_target) ||
      target_from_name(owner_target) ||
      new_target
  end
end

#mapper_classClass

Return the anonymous trait class if the factory defines a trait. Fetch and return the class of a mapper defined by a symbol.

Returns:



50
51
52
53
54
55
56
57
58
59
60
# File 'lib/flat_map/open_mapper/factory.rb', line 50

def mapper_class
  @mapper_class ||= begin
    case
    when traited?        then @identifier
    when @options[:open] then open_mapper_class
    else
      class_name = @options[:mapper_class_name] || "#{name.to_s.camelize}Mapper"
      class_name.constantize
    end
  end
end

#model_mount?Boolean

Return true if mapper to me mounted is ModelMapper

Returns:

  • (Boolean)


76
77
78
# File 'lib/flat_map/open_mapper/factory.rb', line 76

def model_mount?
  mapper_class < ModelMapper
end

#nameSymbol?

Return the name of the mapper being defined by the factory. Return nil for the traited factory.

Returns:

  • (Symbol, nil)


29
30
31
# File 'lib/flat_map/open_mapper/factory.rb', line 29

def name
  traited? ? nil : @identifier
end

#new_targetObject

Return new instance of target_class of the mapper.

Returns:

  • (Object)


109
110
111
# File 'lib/flat_map/open_mapper/factory.rb', line 109

def new_target
  mapper_class.target_class.new
end

#open_mapper_classClass

Return descendant of OpenMapper class to be used when mounting open mappers.

Returns:

  • (Class)


66
67
68
69
70
71
# File 'lib/flat_map/open_mapper/factory.rb', line 66

def open_mapper_class
  klass = Class.new(OpenMapper)
  klass_name = "#{name.to_s.camelize}Mapper"
  klass.singleton_class.send(:define_method, :name){ klass_name }
  klass
end

#open_mount?Boolean

Return true if mapper to me mounted is not ModelMapper, i.e. OpenMapper.

Returns:

  • (Boolean)


83
84
85
# File 'lib/flat_map/open_mapper/factory.rb', line 83

def open_mount?
  !model_mount?
end

#reflection_from_target(target) ⇒ ActiveRecord::Reflection::AssociationReflection?

Try to retreive an association reflection that has a name corresponding to the one of self

Parameters:

  • target (ActiveRecord::Base)

Returns:

  • (ActiveRecord::Reflection::AssociationReflection, nil)


170
171
172
173
174
175
# File 'lib/flat_map/open_mapper/factory.rb', line 170

def reflection_from_target(target)
  return unless name.present? && target.is_a?(ActiveRecord::Base)
  target_class = target.class
  reflection  = target_class.reflect_on_association(name)
  reflection || target_class.reflect_on_association(name.to_s.pluralize.to_sym)
end

#required_for_any_trait?(traits) ⇒ Boolean

Return true if the factory is required to be able to apply a trait for the host mapper. For example, it is required if its name is listed in traits. It is also required if it has nested traits with names listed in traits.

Parameters:

  • traits (Array<Symbol>)

    list of traits

Returns:

  • (Boolean)


233
234
235
236
237
238
239
240
241
# File 'lib/flat_map/open_mapper/factory.rb', line 233

def required_for_any_trait?(traits)
  return true unless traited?

  traits.include?(trait_name) ||
    mapper_class.mountings.any? do |factory|
      factory.traited? &&
        factory.required_for_any_trait?(traits)
    end
end

#target_from_association(owner_target) ⇒ Object

Try to fetch the target for a new mapper being mounted, based on correspondence of the mounting name and presence of the association with a similar name in the host mapper.

For example:

class Foo < ActiveRecord::Base
  has_one :baz
  has_many :bars
end

class FooMapper < FlatMap::Mapper
  # target of this mapper is the instance of Foo. Lets reference it as 'foo'
  mount :baz # This will look for BazMapper, and will try to fetch a target for
             # it based on :has_one association, i.e. foo.baz || foo.build_baz

  mount :bar # This will look for BarMapper, and will try to fetch a target for
             # it based on :has_many association, i.e. foo.bars.build
end


148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/flat_map/open_mapper/factory.rb', line 148

def target_from_association(owner_target)
  return unless owner_target.is_a?(ActiveRecord::Base)

  reflection = reflection_from_target(owner_target)
  return unless reflection.present?

  reflection_macro = reflection.macro
  case
  when reflection_macro == :has_one && reflection.options[:is_current]
    owner_target.send("effective_#{name}")
  when reflection_macro == :has_one || reflection_macro == :belongs_to
    owner_target.send(name) || owner_target.send("build_#{name}")
  when reflection_macro == :has_many
    owner_target.association(reflection.name).build
  end
end

#target_from_name(target) ⇒ Object

Send the name of the mounting to the target of the host mapper, and use return value as a target for a mapper being created.

Returns:

  • (Object)


181
182
183
# File 'lib/flat_map/open_mapper/factory.rb', line 181

def target_from_name(target)
  target.send(name)
end

#trait_nameObject

Return the trait name if the factory defines a trait.



34
35
36
# File 'lib/flat_map/open_mapper/factory.rb', line 34

def trait_name
  @options[:trait_name] if traited?
end

#traited?Boolean

Return true if factory defines a trait.

Returns:

  • (Boolean)


21
22
23
# File 'lib/flat_map/open_mapper/factory.rb', line 21

def traited?
  @identifier.is_a?(Class)
end

#traitsArray<Symbol>

Return the list of traits that should be applied for a mapper being mounted on a host mapper.

Returns:

  • (Array<Symbol>)

    list of traits



42
43
44
# File 'lib/flat_map/open_mapper/factory.rb', line 42

def traits
  Array(@options[:traits]).compact
end