HornsAndHooves-flat_map 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|