Class: FlatMap::BaseMapper::Factory

Inherits:
Object
  • Object
show all
Defined in:
lib/flat_map/base_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/base_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::BaseMapper)

    Host mapper

  • owner_traits (*Symbol)

    List of traits to be applied to a newly created mapper



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
# File 'lib/flat_map/base_mapper/factory.rb', line 193

def create(mapper, *owner_traits)
  save_order = @options[:save] || fetch_save_order(mapper) || :after
  all_traits = (traits + owner_traits).uniq

  new_one =
    if targeted_mount? then
      mapper_class.new(fetch_target_from(mapper), *all_traits, &@extension)
    else
      mapper_class.new(*all_traits, &@extension)
    end

  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(owner_target) ⇒ 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:

  • owner_target (Object)

Returns:

  • (Object, nil)

    target for new mapper.



105
106
107
108
109
110
111
112
113
114
# File 'lib/flat_map/base_mapper/factory.rb', line 105

def explicit_target(owner_target)
  if @options.key?(:target)
    target = @options[:target]
    if target.is_a? Proc
      target.call(owner_target)
    else
      target
    end
  end
end

#explicit_target!(mapper) ⇒ Object

When creating mappers mounted on top of EmptyMapper, target cannot be implicitly fetched from it and should be specified explicitly.

Parameters:

Returns:

  • (Object)

    target for new mapper



85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/flat_map/base_mapper/factory.rb', line 85

def explicit_target!(mapper)
  target = @options[:target]

  if target.present?
    case target
    when Proc   then target.call
    when Symbol then mapper.send(target)
    else target
    end
  else
    raise Mapper::Targeting::NoTargetError.new(mapper_class)
  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)


178
179
180
181
182
183
184
# File 'lib/flat_map/base_mapper/factory.rb', line 178

def fetch_save_order(mapper)
  return :after if mapper.is_a?(EmptyMapper)

  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



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/flat_map/base_mapper/factory.rb', line 68

def fetch_target_from(mapper)
  return explicit_target!(mapper) if mapper.is_a?(EmptyMapper) && targeted_mount?

  owner_target = mapper.target

  return owner_target if traited?

  explicit_target(owner_target)         ||
  target_from_association(owner_target) ||
  target_from_name(owner_target)
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
# File 'lib/flat_map/base_mapper/factory.rb', line 50

def mapper_class
  return @identifier if traited?

  class_name = @options[:mapper_class_name] || "#{name.to_s.camelize}Mapper"
  class_name.constantize
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/base_mapper/factory.rb', line 29

def name
  traited? ? nil : @identifier
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)


157
158
159
160
161
162
# File 'lib/flat_map/base_mapper/factory.rb', line 157

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)


228
229
230
231
232
233
234
235
236
# File 'lib/flat_map/base_mapper/factory.rb', line 228

def required_for_any_trait?(traits)
  return true unless traited?

  traits.include?(trait_name) ||
    mapper_class.mountings.any?{ |factory|
      factory.traited? &&
      factory.required_for_any_trait?(traits)
    }
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


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/flat_map/base_mapper/factory.rb', line 134

def target_from_association(owner_target)
  return unless owner_target.kind_of?(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
  else # nil
  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)


168
169
170
# File 'lib/flat_map/base_mapper/factory.rb', line 168

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

#targeted_mount?Boolean

Return true if factory should create targeted mapper.

Returns:

  • (Boolean)


60
61
62
# File 'lib/flat_map/base_mapper/factory.rb', line 60

def targeted_mount?
  mapper_class < Mapper
end

#trait_nameObject

Return the trait name if the factory defines a trait.



34
35
36
# File 'lib/flat_map/base_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/base_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/base_mapper/factory.rb', line 42

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