HornsAndHooves-flat_map 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/.metrics +17 -0
- data/.rspec +4 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +20 -0
- data/HornsAndHooves-flat_map.gemspec +29 -0
- data/LICENSE +21 -0
- data/README.markdown +214 -0
- data/Rakefile +15 -0
- data/lib/flat_map.rb +14 -0
- data/lib/flat_map/errors.rb +57 -0
- data/lib/flat_map/mapping.rb +124 -0
- data/lib/flat_map/mapping/factory.rb +21 -0
- data/lib/flat_map/mapping/reader.rb +12 -0
- data/lib/flat_map/mapping/reader/basic.rb +28 -0
- data/lib/flat_map/mapping/reader/formatted.rb +45 -0
- data/lib/flat_map/mapping/reader/formatted/formats.rb +28 -0
- data/lib/flat_map/mapping/reader/method.rb +25 -0
- data/lib/flat_map/mapping/reader/proc.rb +15 -0
- data/lib/flat_map/mapping/writer.rb +11 -0
- data/lib/flat_map/mapping/writer/basic.rb +25 -0
- data/lib/flat_map/mapping/writer/method.rb +28 -0
- data/lib/flat_map/mapping/writer/proc.rb +18 -0
- data/lib/flat_map/model_mapper.rb +195 -0
- data/lib/flat_map/model_mapper/persistence.rb +108 -0
- data/lib/flat_map/model_mapper/skipping.rb +45 -0
- data/lib/flat_map/open_mapper.rb +113 -0
- data/lib/flat_map/open_mapper/attribute_methods.rb +55 -0
- data/lib/flat_map/open_mapper/factory.rb +244 -0
- data/lib/flat_map/open_mapper/mapping.rb +123 -0
- data/lib/flat_map/open_mapper/mounting.rb +168 -0
- data/lib/flat_map/open_mapper/persistence.rb +178 -0
- data/lib/flat_map/open_mapper/skipping.rb +66 -0
- data/lib/flat_map/open_mapper/traits.rb +95 -0
- data/lib/flat_map/version.rb +3 -0
- data/spec/flat_map/errors_spec.rb +23 -0
- data/spec/flat_map/mapper/attribute_methods_spec.rb +36 -0
- data/spec/flat_map/mapper/callbacks_spec.rb +76 -0
- data/spec/flat_map/mapper/factory_spec.rb +285 -0
- data/spec/flat_map/mapper/mapping_spec.rb +98 -0
- data/spec/flat_map/mapper/mounting_spec.rb +142 -0
- data/spec/flat_map/mapper/persistence_spec.rb +152 -0
- data/spec/flat_map/mapper/skipping_spec.rb +91 -0
- data/spec/flat_map/mapper/targeting_spec.rb +156 -0
- data/spec/flat_map/mapper/traits_spec.rb +172 -0
- data/spec/flat_map/mapper/validations_spec.rb +72 -0
- data/spec/flat_map/mapper_spec.rb +9 -0
- data/spec/flat_map/mapping/factory_spec.rb +12 -0
- data/spec/flat_map/mapping/reader/basic_spec.rb +15 -0
- data/spec/flat_map/mapping/reader/formatted_spec.rb +62 -0
- data/spec/flat_map/mapping/reader/method_spec.rb +13 -0
- data/spec/flat_map/mapping/reader/proc_spec.rb +13 -0
- data/spec/flat_map/mapping/writer/basic_spec.rb +15 -0
- data/spec/flat_map/mapping/writer/method_spec.rb +13 -0
- data/spec/flat_map/mapping/writer/proc_spec.rb +13 -0
- data/spec/flat_map/mapping_spec.rb +123 -0
- data/spec/flat_map/open_mapper_spec.rb +19 -0
- data/spec/spec_helper.rb +7 -0
- data/tmp/metric_fu/_data/20131218.yml +6902 -0
- data/tmp/metric_fu/_data/20131219.yml +6726 -0
- metadata +220 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# This module enhances and modifies original FlatMap::OpenMapper::Persistence
|
3
|
+
# functionality for ActiveRecord models as targets.
|
4
|
+
module ModelMapper::Persistence
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
# Writer of the target class name. Allows manual control over target
|
9
|
+
# class of the mapper, for example:
|
10
|
+
#
|
11
|
+
# class CustomerMapper
|
12
|
+
# self.target_class_name = 'Customer::Active'
|
13
|
+
# end
|
14
|
+
class_attribute :target_class_name
|
15
|
+
end
|
16
|
+
|
17
|
+
# ModelMethods class macros
|
18
|
+
module ClassMethods
|
19
|
+
# Find a record of the +target_class+ by +id+ and use it as a
|
20
|
+
# target for a new mapper, with a list of passed +traits+ applied
|
21
|
+
# to it.
|
22
|
+
#
|
23
|
+
# @param [#to_i] id of the record
|
24
|
+
# @param [*Symbol] traits
|
25
|
+
# @return [FlatMap::Mapper] mapper
|
26
|
+
def find(id, *traits, &block)
|
27
|
+
new(target_class.find(id), *traits, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Fetch a class for the target of the mapper.
|
31
|
+
#
|
32
|
+
# @return [Class] class
|
33
|
+
def target_class
|
34
|
+
(target_class_name || default_target_class_name).constantize
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return target class name based on name of the ancestor mapper
|
38
|
+
# that is closest to {FlatMap::Mapper}, which may be +self+.
|
39
|
+
#
|
40
|
+
# class VehicleMapper
|
41
|
+
# # some definitions
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# class CarMapper < VehicleMapper
|
45
|
+
# # some more definitions
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# CarMapper.target_class # => Vehicle
|
49
|
+
#
|
50
|
+
# @return [String]
|
51
|
+
def default_target_class_name
|
52
|
+
ancestor_classes = ancestors.select{ |ancestor| ancestor.is_a? Class }
|
53
|
+
base_mapper_index = ancestor_classes.index(::FlatMap::ModelMapper)
|
54
|
+
ancestor_classes[base_mapper_index - 1].name[/^([\w:]+)Mapper.*$/, 1]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Return a 'mapper' string as a model_name. Used by Rails FormBuilder.
|
59
|
+
#
|
60
|
+
# @return [String]
|
61
|
+
def model_name
|
62
|
+
'mapper'
|
63
|
+
end
|
64
|
+
|
65
|
+
# Delegate to the target's #to_key method.
|
66
|
+
# @return [String]
|
67
|
+
def to_key
|
68
|
+
target.to_key
|
69
|
+
end
|
70
|
+
|
71
|
+
# Write a passed set of +params+. Then try to save the model if +self+
|
72
|
+
# passes validation. Saving is performed in a transaction.
|
73
|
+
#
|
74
|
+
# @param [Hash] params
|
75
|
+
# @return [Boolean]
|
76
|
+
def apply(params)
|
77
|
+
write(params)
|
78
|
+
res = if valid?
|
79
|
+
ActiveRecord::Base.transaction do
|
80
|
+
save
|
81
|
+
end
|
82
|
+
end
|
83
|
+
!!res
|
84
|
+
end
|
85
|
+
|
86
|
+
# Save +target+
|
87
|
+
#
|
88
|
+
# @return [Boolean]
|
89
|
+
def save_target
|
90
|
+
return true if owned?
|
91
|
+
target.respond_to?(:save) ? target.save(:validate => false) : true
|
92
|
+
end
|
93
|
+
|
94
|
+
# Delegate persistence to target.
|
95
|
+
#
|
96
|
+
# @return [Boolean]
|
97
|
+
def persisted?
|
98
|
+
target.respond_to?(:persisted?) ? target.persisted? : false
|
99
|
+
end
|
100
|
+
|
101
|
+
# Delegate #id to target, if possible.
|
102
|
+
#
|
103
|
+
# @return [Fixnum, nil]
|
104
|
+
def id
|
105
|
+
target.id if target.respond_to?(:id)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# This helper module slightly enhances ofunctionality of the
|
3
|
+
# {FlatMap::OpenMapper::Skipping} module for most commonly
|
4
|
+
# used +ActiveRecord+ targets.
|
5
|
+
module ModelMapper::Skipping
|
6
|
+
# Extend original #skip! method for Rails-models-based targets
|
7
|
+
#
|
8
|
+
# Note that this will mark the target record as
|
9
|
+
# destroyed if it is a new record. Thus, this
|
10
|
+
# record will not be a subject of Rails associated
|
11
|
+
# validation procedures, and will not be saved as an
|
12
|
+
# associated record.
|
13
|
+
#
|
14
|
+
# @return [Object]
|
15
|
+
def skip!
|
16
|
+
super
|
17
|
+
if target.is_a?(ActiveRecord::Base)
|
18
|
+
if target.new_record?
|
19
|
+
# Using the instance variable directly as {ActiveRecord::Base#delete}
|
20
|
+
# will freeze the record.
|
21
|
+
target.instance_variable_set('@destroyed', true)
|
22
|
+
else
|
23
|
+
# Using reload instead of reset_changes! to reset associated nested
|
24
|
+
# model changes also
|
25
|
+
target.reload
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Extend original #use! method for Rails-models-based targets, as
|
31
|
+
# acoompanied to #skip! method.
|
32
|
+
#
|
33
|
+
# @return [Object]
|
34
|
+
def use!
|
35
|
+
super
|
36
|
+
if target.is_a?(ActiveRecord::Base)
|
37
|
+
if target.new_record?
|
38
|
+
target.instance_variable_set('@destroyed', false)
|
39
|
+
else
|
40
|
+
all_nested_mountings.each(&:use!)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# Base Mapper that can be used for mounting other mappers, handling business logic,
|
3
|
+
# etc. For the intentional usage of mappers, pleas see {ModelMapper}
|
4
|
+
class OpenMapper
|
5
|
+
# Raised when mapper is initialized with no target defined
|
6
|
+
class NoTargetError < ArgumentError
|
7
|
+
# Initializes exception with a name of mapper class.
|
8
|
+
#
|
9
|
+
# @param [Class] mapper_class class of mapper being initialized
|
10
|
+
def initialize(mapper_class)
|
11
|
+
super("Target object is required to initialize #{mapper_class.name}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
extend ActiveSupport::Autoload
|
16
|
+
|
17
|
+
autoload :Mapping
|
18
|
+
autoload :Mounting
|
19
|
+
autoload :Traits
|
20
|
+
autoload :Factory
|
21
|
+
autoload :AttributeMethods
|
22
|
+
autoload :Persistence
|
23
|
+
autoload :Skipping
|
24
|
+
|
25
|
+
include Mapping
|
26
|
+
include Mounting
|
27
|
+
include Traits
|
28
|
+
include AttributeMethods
|
29
|
+
include ActiveModel::Validations
|
30
|
+
include Persistence
|
31
|
+
include Skipping
|
32
|
+
|
33
|
+
attr_writer :host, :suffix
|
34
|
+
attr_reader :target, :traits
|
35
|
+
attr_accessor :owner, :name
|
36
|
+
|
37
|
+
# Callback to dup mappings and mountings on inheritance.
|
38
|
+
# The values are cloned from actual mappers (i.e. something
|
39
|
+
# like CustomerAccountMapper, since it is useless to clone
|
40
|
+
# empty values of FlatMap::Mapper).
|
41
|
+
#
|
42
|
+
# Note: those class attributes are defined in {Mapping}
|
43
|
+
# and {Mounting} modules.
|
44
|
+
def self.inherited(subclass)
|
45
|
+
subclass.mappings = mappings.dup
|
46
|
+
subclass.mountings = mountings.dup
|
47
|
+
end
|
48
|
+
|
49
|
+
# Initializes +mapper+ with +target+ and +traits+, which are
|
50
|
+
# used to fetch proper list of mounted mappers. Raises error
|
51
|
+
# if target is not specified.
|
52
|
+
#
|
53
|
+
# @param [Object] target Target of mapping
|
54
|
+
# @param [*Symbol] traits List of traits
|
55
|
+
# @raise [FlatMap::Mapper::NoTargetError]
|
56
|
+
def initialize(target, *traits)
|
57
|
+
raise NoTargetError.new(self.class) unless target
|
58
|
+
|
59
|
+
@target, @traits = target, traits
|
60
|
+
|
61
|
+
if block_given?
|
62
|
+
singleton_class.trait :extension, &Proc.new
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Return a simple string representation of +mapper+. Done so to
|
67
|
+
# avoid really long inspection of internal objects (target -
|
68
|
+
# usually AR model, mountings and mappings)
|
69
|
+
# @return [String]
|
70
|
+
def inspect
|
71
|
+
to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
# Return +true+ if +mapper+ is owned. This means that current
|
75
|
+
# mapper is actually a trait. Thus, it is a part of an owner
|
76
|
+
# mapper.
|
77
|
+
#
|
78
|
+
# @return [Boolean]
|
79
|
+
def owned?
|
80
|
+
owner.present?
|
81
|
+
end
|
82
|
+
|
83
|
+
# If mapper was mounted by another mapper, host is the one who
|
84
|
+
# mounted +self+.
|
85
|
+
#
|
86
|
+
# @return [FlatMap::Mapper]
|
87
|
+
def host
|
88
|
+
owned? ? owner.host : @host
|
89
|
+
end
|
90
|
+
|
91
|
+
# Return +true+ if mapper is hosted, i.e. it is mounted by another
|
92
|
+
# mapper.
|
93
|
+
#
|
94
|
+
# @return [Boolean]
|
95
|
+
def hosted?
|
96
|
+
host.present?
|
97
|
+
end
|
98
|
+
|
99
|
+
# +suffix+ reader. Delegated to owner for owned mappers.
|
100
|
+
#
|
101
|
+
# @return [String, nil]
|
102
|
+
def suffix
|
103
|
+
owned? ? owner.suffix : @suffix
|
104
|
+
end
|
105
|
+
|
106
|
+
# Return +true+ if +suffix+ is present.
|
107
|
+
#
|
108
|
+
# @return [Boolean]
|
109
|
+
def suffixed?
|
110
|
+
suffix.present?
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# This module allows mappers to return and assign values via method calls
|
3
|
+
# which names correspond to names of mappings defined within the mapper.
|
4
|
+
#
|
5
|
+
# This methods are defined within anonymous module that will extend
|
6
|
+
# mapper on first usage of this methods.
|
7
|
+
#
|
8
|
+
# NOTE: :to_ary method is called internally by Ruby 1.9.3 when we call
|
9
|
+
# something like [mapper].flatten. And we DO want default behavior
|
10
|
+
# for handling this missing method.
|
11
|
+
module OpenMapper::AttributeMethods
|
12
|
+
# Lazily define reader and writer methods for all mappings available
|
13
|
+
# to the mapper, and extend +self+ with it.
|
14
|
+
def method_missing(name, *args, &block)
|
15
|
+
if name == :to_ary ||
|
16
|
+
@attribute_methods_defined ||
|
17
|
+
self.class.protected_instance_methods.include?(name)
|
18
|
+
return super
|
19
|
+
end
|
20
|
+
|
21
|
+
mappings = all_mappings
|
22
|
+
valid_names = mappings.map do |mapping|
|
23
|
+
full_name = mapping.full_name
|
24
|
+
[full_name, "#{full_name}=".to_sym]
|
25
|
+
end
|
26
|
+
valid_names.flatten!
|
27
|
+
|
28
|
+
return super unless valid_names.include?(name)
|
29
|
+
|
30
|
+
extend attribute_methods(mappings)
|
31
|
+
@attribute_methods_defined = true
|
32
|
+
send(name, *args, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Define anonymous module with reader and writer methods for
|
36
|
+
# all the +mappings+ being passed.
|
37
|
+
#
|
38
|
+
# @param [Array<FlatMap::Mapping>] mappings list of mappings
|
39
|
+
# @return [Module] module with method definitions
|
40
|
+
def attribute_methods(mappings)
|
41
|
+
Module.new do
|
42
|
+
mappings.each do |mapping|
|
43
|
+
full_name = mapping.full_name
|
44
|
+
|
45
|
+
define_method(full_name){ |*args| mapping.read(*args) }
|
46
|
+
|
47
|
+
define_method("#{full_name}=") do |value|
|
48
|
+
mapping.write(value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
private :attribute_methods
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# Mapper factory objects are used to store mounting and trait definitions
|
3
|
+
# and to instantiate and setup corresponding mapper objects thereafter.
|
4
|
+
# Factory objects are stored by mapper classes in opposite to actual
|
5
|
+
# mounted mappers that are stored by mapper objects themselves.
|
6
|
+
class OpenMapper::Factory
|
7
|
+
# Initializes factory with an identifier (name of a mounted mapper,
|
8
|
+
# or the actual class for a trait) and a set of options. Those args
|
9
|
+
# are used to create actual mapper object for the host mapper.
|
10
|
+
#
|
11
|
+
# @param [Symbol, Class] identifier name of a mapper or mapper class
|
12
|
+
# itself
|
13
|
+
# @param [Hash] options
|
14
|
+
def initialize(identifier, options = {}, &block)
|
15
|
+
@identifier, @options, @extension = identifier, options, block
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return +true+ if factory defines a trait.
|
19
|
+
#
|
20
|
+
# @return [Boolean]
|
21
|
+
def traited?
|
22
|
+
@identifier.is_a?(Class)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return the name of the mapper being defined by the factory.
|
26
|
+
# Return +nil+ for the traited factory.
|
27
|
+
#
|
28
|
+
# @return [Symbol, nil]
|
29
|
+
def name
|
30
|
+
traited? ? nil : @identifier
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return the trait name if the factory defines a trait.
|
34
|
+
def trait_name
|
35
|
+
@options[:trait_name] if traited?
|
36
|
+
end
|
37
|
+
|
38
|
+
# Return the list of traits that should be applied for a mapper being
|
39
|
+
# mounted on a host mapper.
|
40
|
+
#
|
41
|
+
# @return [Array<Symbol>] list of traits
|
42
|
+
def traits
|
43
|
+
Array(@options[:traits]).compact
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return the anonymous trait class if the factory defines a trait.
|
47
|
+
# Fetch and return the class of a mapper defined by a symbol.
|
48
|
+
#
|
49
|
+
# @return [Class] ancestor of {FlatMap::OpenMapper}
|
50
|
+
def mapper_class
|
51
|
+
@mapper_class ||= begin
|
52
|
+
case
|
53
|
+
when traited? then @identifier
|
54
|
+
when @options[:open] then open_mapper_class
|
55
|
+
when @options[:mapper_class] then @options[:mapper_class]
|
56
|
+
else
|
57
|
+
class_name = @options[:mapper_class_name] || "#{name.to_s.camelize}Mapper"
|
58
|
+
class_name.constantize
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return descendant of +OpenMapper+ class to be used when mounting
|
64
|
+
# open mappers.
|
65
|
+
#
|
66
|
+
# @return [Class]
|
67
|
+
def open_mapper_class
|
68
|
+
klass = Class.new(OpenMapper)
|
69
|
+
klass_name = "#{name.to_s.camelize}Mapper"
|
70
|
+
klass.singleton_class.send(:define_method, :name){ klass_name }
|
71
|
+
klass
|
72
|
+
end
|
73
|
+
|
74
|
+
# Return +true+ if mapper to me mounted is +ModelMapper+
|
75
|
+
#
|
76
|
+
# @return [Boolean]
|
77
|
+
def model_mount?
|
78
|
+
mapper_class < ModelMapper
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return +true+ if mapper to me mounted is not +ModelMapper+, i.e. +OpenMapper+.
|
82
|
+
#
|
83
|
+
# @return [Boolean]
|
84
|
+
def open_mount?
|
85
|
+
!model_mount?
|
86
|
+
end
|
87
|
+
|
88
|
+
# Fetch the target for the mapper being created based on target of a host mapper.
|
89
|
+
#
|
90
|
+
# @param [FlatMap::OpenMapper] mapper Host mapper
|
91
|
+
# @return [Object] target for new mapper
|
92
|
+
def fetch_target_from(mapper)
|
93
|
+
owner_target = mapper.target
|
94
|
+
|
95
|
+
return owner_target if traited?
|
96
|
+
|
97
|
+
if open_mount?
|
98
|
+
explicit_target(mapper) || new_target
|
99
|
+
else
|
100
|
+
explicit_target(mapper) ||
|
101
|
+
target_from_association(owner_target) ||
|
102
|
+
target_from_name(owner_target) ||
|
103
|
+
new_target
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Return new instance of +target_class+ of the mapper.
|
108
|
+
#
|
109
|
+
# @return [Object]
|
110
|
+
def new_target
|
111
|
+
mapper_class.target_class.new
|
112
|
+
end
|
113
|
+
|
114
|
+
# Try to use explicit target definition passed in options to fetch a
|
115
|
+
# target. If this value is a +Proc+, will call it with owner target as
|
116
|
+
# argument.
|
117
|
+
#
|
118
|
+
# @param [FlatMap::OpenMapper] mapper
|
119
|
+
# @return [Object, nil] target for new mapper.
|
120
|
+
def explicit_target(mapper)
|
121
|
+
if @options.key?(:target)
|
122
|
+
target = @options[:target]
|
123
|
+
case target
|
124
|
+
when Proc then target.call(mapper.target)
|
125
|
+
when Symbol then mapper.send(target)
|
126
|
+
else target
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Try to fetch the target for a new mapper being mounted, based on
|
132
|
+
# correspondence of the mounting name and presence of the association
|
133
|
+
# with a similar name in the host mapper.
|
134
|
+
#
|
135
|
+
# For example:
|
136
|
+
# class Foo < ActiveRecord::Base
|
137
|
+
# has_one :baz
|
138
|
+
# has_many :bars
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# class FooMapper < FlatMap::Mapper
|
142
|
+
# # target of this mapper is the instance of Foo. Lets reference it as 'foo'
|
143
|
+
# mount :baz # This will look for BazMapper, and will try to fetch a target for
|
144
|
+
# # it based on :has_one association, i.e. foo.baz || foo.build_baz
|
145
|
+
#
|
146
|
+
# mount :bar # This will look for BarMapper, and will try to fetch a target for
|
147
|
+
# # it based on :has_many association, i.e. foo.bars.build
|
148
|
+
# end
|
149
|
+
def target_from_association(owner_target)
|
150
|
+
return unless owner_target.is_a?(ActiveRecord::Base)
|
151
|
+
|
152
|
+
reflection = reflection_from_target(owner_target)
|
153
|
+
return unless reflection.present?
|
154
|
+
|
155
|
+
reflection_macro = reflection.macro
|
156
|
+
case
|
157
|
+
when reflection_macro == :has_one && reflection.options[:is_current]
|
158
|
+
owner_target.send("effective_#{name}")
|
159
|
+
when reflection_macro == :has_one || reflection_macro == :belongs_to
|
160
|
+
owner_target.send(name) || owner_target.send("build_#{name}")
|
161
|
+
when reflection_macro == :has_many
|
162
|
+
owner_target.association(reflection.name).build
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Try to retreive an association reflection that has a name corresponding
|
167
|
+
# to the one of +self+
|
168
|
+
#
|
169
|
+
# @param [ActiveRecord::Base] target
|
170
|
+
# @return [ActiveRecord::Reflection::AssociationReflection, nil]
|
171
|
+
def reflection_from_target(target)
|
172
|
+
return unless name.present? && target.is_a?(ActiveRecord::Base)
|
173
|
+
target_class = target.class
|
174
|
+
reflection = target_class.reflect_on_association(name)
|
175
|
+
reflection || target_class.reflect_on_association(name.to_s.pluralize.to_sym)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Send the name of the mounting to the target of the host mapper, and use
|
179
|
+
# return value as a target for a mapper being created.
|
180
|
+
#
|
181
|
+
# @return [Object]
|
182
|
+
def target_from_name(target)
|
183
|
+
target.send(name)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Return order relative to target of the passed +mapper+ in which mapper to
|
187
|
+
# be created should be saved. In particular, targets of <tt>:belongs_to</tt>
|
188
|
+
# associations should be saved before target of +mapper+ is saved.
|
189
|
+
#
|
190
|
+
# @param [FlatMap::OpenMapper] mapper
|
191
|
+
# @return [Symbol]
|
192
|
+
def fetch_save_order(mapper)
|
193
|
+
return :after unless mapper.is_a?(ModelMapper)
|
194
|
+
|
195
|
+
reflection = reflection_from_target(mapper.target)
|
196
|
+
return unless reflection.present?
|
197
|
+
reflection.macro == :belongs_to ? :before : :after
|
198
|
+
end
|
199
|
+
|
200
|
+
# Create a new mapper object for mounting. If the factory is traited,
|
201
|
+
# the new mapper is a part of a host mapper, and is 'owned' by it.
|
202
|
+
# Otherwise, assign the name of the factory to it to be able to find it
|
203
|
+
# later on.
|
204
|
+
#
|
205
|
+
# @param [FlatMap::OpenMapper] mapper Host mapper
|
206
|
+
# @param [*Symbol] owner_traits List of traits to be applied to a newly created mapper
|
207
|
+
def create(mapper, *owner_traits)
|
208
|
+
save_order = @options[:save] || fetch_save_order(mapper) || :after
|
209
|
+
new_one = mapper_class.new(fetch_target_from(mapper), *(traits + owner_traits).uniq, &@extension)
|
210
|
+
if traited?
|
211
|
+
new_one.owner = mapper
|
212
|
+
else
|
213
|
+
new_one.host = mapper
|
214
|
+
new_one.name = @identifier
|
215
|
+
new_one.save_order = save_order
|
216
|
+
|
217
|
+
if (suffix = @options[:suffix] || mapper.suffix).present?
|
218
|
+
new_one.suffix = suffix
|
219
|
+
new_one.name = :"#{@identifier}_#{suffix}"
|
220
|
+
else
|
221
|
+
new_one.name = @identifier
|
222
|
+
end
|
223
|
+
end
|
224
|
+
new_one
|
225
|
+
end
|
226
|
+
|
227
|
+
# Return +true+ if the factory is required to be able to apply a trait
|
228
|
+
# for the host mapper.
|
229
|
+
# For example, it is required if its name is listed in +traits+.
|
230
|
+
# It is also required if it has nested traits with names listed in +traits+.
|
231
|
+
#
|
232
|
+
# @param [Array<Symbol>] traits list of traits
|
233
|
+
# @return [Boolean]
|
234
|
+
def required_for_any_trait?(traits)
|
235
|
+
return true unless traited?
|
236
|
+
|
237
|
+
traits.include?(trait_name) ||
|
238
|
+
mapper_class.mountings.any? do |factory|
|
239
|
+
factory.traited? &&
|
240
|
+
factory.required_for_any_trait?(traits)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|