flat_map 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +31 -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/LICENSE +20 -0
- data/README.markdown +211 -0
- data/Rakefile +15 -0
- data/flat_map.gemspec +30 -0
- data/lib/flat_map.rb +9 -0
- data/lib/flat_map/base_mapper.rb +95 -0
- data/lib/flat_map/base_mapper/attribute_methods.rb +54 -0
- data/lib/flat_map/base_mapper/factory.rb +238 -0
- data/lib/flat_map/base_mapper/mapping.rb +123 -0
- data/lib/flat_map/base_mapper/mounting.rb +168 -0
- data/lib/flat_map/base_mapper/persistence.rb +145 -0
- data/lib/flat_map/base_mapper/skipping.rb +62 -0
- data/lib/flat_map/base_mapper/traits.rb +94 -0
- data/lib/flat_map/empty_mapper.rb +29 -0
- data/lib/flat_map/errors.rb +57 -0
- data/lib/flat_map/mapper.rb +213 -0
- data/lib/flat_map/mapper/skipping.rb +45 -0
- data/lib/flat_map/mapper/targeting.rb +130 -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/version.rb +3 -0
- data/spec/flat_map/empty_mapper_spec.rb +36 -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 +258 -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/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/spec_helper.rb +7 -0
- data/tmp/metric_fu/_data/20131218.yml +6902 -0
- data/tmp/metric_fu/_data/20131219.yml +6726 -0
- metadata +184 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# This helper module slightly enhances functionality of the
|
3
|
+
# {BaseMapper::Skipping} module for most commonly used +ActiveRecord+ targets.
|
4
|
+
# This is done to improve modularity of the {FlatMap} mappers.
|
5
|
+
module Mapper::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,130 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# This module provides some integration between mapper and its target,
|
3
|
+
# which is usually an ActiveRecord model, as well as some integration
|
4
|
+
# between mapper and Rails forms.
|
5
|
+
module Mapper::Targeting
|
6
|
+
# Raised when mapper is initialized with no target defined
|
7
|
+
class NoTargetError < ArgumentError
|
8
|
+
# Initializes exception with a name of mapper class.
|
9
|
+
#
|
10
|
+
# @param [Class] mapper_class class of mapper being initialized
|
11
|
+
def initialize(mapper_class)
|
12
|
+
super("Target object is required to initialize #{mapper_class.name}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
extend ActiveSupport::Concern
|
17
|
+
|
18
|
+
included do
|
19
|
+
define_callbacks :save
|
20
|
+
|
21
|
+
# Writer of the target class name. Allows manual control over target
|
22
|
+
# class of the mapper, for example:
|
23
|
+
#
|
24
|
+
# class CustomerMapper
|
25
|
+
# self.target_class_name = 'Customer::Active'
|
26
|
+
# end
|
27
|
+
class_attribute :target_class_name
|
28
|
+
end
|
29
|
+
|
30
|
+
# ModelMethods class macros
|
31
|
+
module ClassMethods
|
32
|
+
# Create a new mapper object wrapped around new instance of its
|
33
|
+
# +target_class+, with a list of passed +traits+ applied to it.
|
34
|
+
#
|
35
|
+
# @param [*Symbol] traits
|
36
|
+
# @return [FlatMap::Mapper] mapper
|
37
|
+
def build(*traits, &block)
|
38
|
+
new(target_class.new, *traits, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Find a record of the +target_class+ by +id+ and use it as a
|
42
|
+
# target for a new mapper, with a list of passed +traits+ applied
|
43
|
+
# to it.
|
44
|
+
#
|
45
|
+
# @param [#to_i] id of the record
|
46
|
+
# @param [*Symbol] traits
|
47
|
+
# @return [FlatMap::Mapper] mapper
|
48
|
+
def find(id, *traits, &block)
|
49
|
+
new(target_class.find(id), *traits, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Fetch a class for the target of the mapper.
|
53
|
+
#
|
54
|
+
# @return [Class] class
|
55
|
+
def target_class
|
56
|
+
(target_class_name || default_target_class_name).constantize
|
57
|
+
end
|
58
|
+
|
59
|
+
# Return target class name based on name of the ancestor mapper
|
60
|
+
# that is closest to {FlatMap::Mapper}, which may be +self+.
|
61
|
+
#
|
62
|
+
# class VehicleMapper
|
63
|
+
# # some definitions
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# class CarMapper < VehicleMapper
|
67
|
+
# # some more definitions
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# CarMapper.target_class # => Vehicle
|
71
|
+
#
|
72
|
+
# @return [String]
|
73
|
+
def default_target_class_name
|
74
|
+
ancestor_classes = ancestors.select{ |ancestor| ancestor.is_a? Class }
|
75
|
+
base_mapper_index = ancestor_classes.index(::FlatMap::Mapper)
|
76
|
+
ancestor_classes[base_mapper_index - 1].name[/^([\w:]+)Mapper.*$/, 1]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return a 'mapper' string as a model_name. Used by Rails FormBuilder.
|
81
|
+
#
|
82
|
+
# @return [String]
|
83
|
+
def model_name
|
84
|
+
'mapper'
|
85
|
+
end
|
86
|
+
|
87
|
+
# Delegate to the target's #to_key method.
|
88
|
+
# @return [String]
|
89
|
+
def to_key
|
90
|
+
target.to_key
|
91
|
+
end
|
92
|
+
|
93
|
+
# Write a passed set of +params+. Then try to save the model if +self+
|
94
|
+
# passes validation. Saving is performed in a transaction.
|
95
|
+
#
|
96
|
+
# @param [Hash] params
|
97
|
+
# @return [Boolean]
|
98
|
+
def apply(params)
|
99
|
+
write(params)
|
100
|
+
res = if valid?
|
101
|
+
ActiveRecord::Base.transaction do
|
102
|
+
save
|
103
|
+
end
|
104
|
+
end
|
105
|
+
!!res
|
106
|
+
end
|
107
|
+
|
108
|
+
# Save +target+
|
109
|
+
#
|
110
|
+
# @return [Boolean]
|
111
|
+
def save_target
|
112
|
+
return true if owned?
|
113
|
+
target.respond_to?(:save) ? target.save(:validate => false) : true
|
114
|
+
end
|
115
|
+
|
116
|
+
# Delegate persistence to target.
|
117
|
+
#
|
118
|
+
# @return [Boolean]
|
119
|
+
def persisted?
|
120
|
+
target.respond_to?(:persisted?) ? target.persisted? : false
|
121
|
+
end
|
122
|
+
|
123
|
+
# Delegate #id to target, if possible.
|
124
|
+
#
|
125
|
+
# @return [Fixnum, nil]
|
126
|
+
def id
|
127
|
+
target.id if target.respond_to?(:id)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# Encapsulates mapping concept used by mappers. Each mapping belongs to
|
3
|
+
# a particular mapper and has its own reader and writer objects.
|
4
|
+
class Mapping
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
autoload :Reader
|
8
|
+
autoload :Writer
|
9
|
+
autoload :Factory
|
10
|
+
|
11
|
+
attr_reader :mapper, :name, :full_name, :target_attribute
|
12
|
+
attr_reader :reader, :writer
|
13
|
+
attr_reader :multiparam
|
14
|
+
|
15
|
+
delegate :target, :to => :mapper
|
16
|
+
delegate :write, :to => :writer, :allow_nil => true
|
17
|
+
delegate :read, :to => :reader, :allow_nil => true
|
18
|
+
|
19
|
+
# Initialize a mapping, passing to it a +mapper+, which is
|
20
|
+
# a gateway to actual +target+, +name+, which is an external
|
21
|
+
# identifier, +target_attribute+, which is used to access
|
22
|
+
# actual information of the +target+, and +options+.
|
23
|
+
#
|
24
|
+
# @param [FlatMap::Mapper] mapper
|
25
|
+
# @param [Symbol] name
|
26
|
+
# @param [Symbol] target_attribute
|
27
|
+
# @param [Hash] options
|
28
|
+
# @option [Symbol, Proc] :reader specifies how value will
|
29
|
+
# be read from the +target+
|
30
|
+
# @option [Symbol] :format specifies additional processing
|
31
|
+
# of the value on reading
|
32
|
+
# @option [Symbol, Proc] :writer specifies how value will
|
33
|
+
# be written to the +target+
|
34
|
+
# @option [Class] :multiparam specifies multiparam Class,
|
35
|
+
# object of which will be instantiated on writing
|
36
|
+
# multiparam attribute passed from the Rails form
|
37
|
+
def initialize(mapper, name, target_attribute, options = {})
|
38
|
+
@mapper = mapper
|
39
|
+
@name = name
|
40
|
+
@target_attribute = target_attribute
|
41
|
+
|
42
|
+
@full_name = mapper.suffixed? ? :"#{@name}_#{mapper.suffix}" : name
|
43
|
+
|
44
|
+
@multiparam = options[:multiparam]
|
45
|
+
|
46
|
+
fetch_reader(options)
|
47
|
+
fetch_writer(options)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return +true+ if the mapping was created with the <tt>:multiparam</tt>
|
51
|
+
# option set.
|
52
|
+
#
|
53
|
+
# @return [Boolean]
|
54
|
+
def multiparam?
|
55
|
+
!!@multiparam
|
56
|
+
end
|
57
|
+
|
58
|
+
# Lookup the passed hash of params for the key that corresponds
|
59
|
+
# to the +full_name+ of +self+, and write it if it is present.
|
60
|
+
#
|
61
|
+
# @param [Hash] params
|
62
|
+
# @return [Object] value assigned
|
63
|
+
def write_from_params(params)
|
64
|
+
write(params[full_name]) if params.key?(full_name) && writer.present?
|
65
|
+
end
|
66
|
+
|
67
|
+
# Return a hash of a single key => value pair, where key
|
68
|
+
# corresponds to +full_name+ and +value+ to value read from
|
69
|
+
# +target+. If +reader+ is not set, return an empty hash.
|
70
|
+
#
|
71
|
+
# @return [Hash]
|
72
|
+
def read_as_params
|
73
|
+
reader ? {full_name => read} : {}
|
74
|
+
end
|
75
|
+
|
76
|
+
# Instantiate a +reader+ object based on the <tt>:reader</tt>
|
77
|
+
# and <tt>:format</tt> values of +options+.
|
78
|
+
#
|
79
|
+
# @param [Hash] options
|
80
|
+
# @return [FlatMap::Mapping::Reader::Basic]
|
81
|
+
def fetch_reader(options)
|
82
|
+
options_reader = options[:reader]
|
83
|
+
|
84
|
+
@reader =
|
85
|
+
case options_reader
|
86
|
+
when Symbol, String
|
87
|
+
Reader::Method.new(self, options_reader)
|
88
|
+
when Proc
|
89
|
+
Reader::Proc.new(self, options_reader)
|
90
|
+
when false
|
91
|
+
nil
|
92
|
+
else
|
93
|
+
if options.key?(:format) then
|
94
|
+
Reader::Formatted.new(self, options[:format])
|
95
|
+
else
|
96
|
+
Reader::Basic.new(self)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
private :fetch_reader
|
101
|
+
|
102
|
+
# Instantiate a +writer+ object based on the <tt>:writer</tt>
|
103
|
+
# value of +options+.
|
104
|
+
#
|
105
|
+
# @param [Hash] options
|
106
|
+
# @return [FlatMap::Mapping::Writer::Basic]
|
107
|
+
def fetch_writer(options)
|
108
|
+
options_writer = options[:writer]
|
109
|
+
|
110
|
+
@writer =
|
111
|
+
case options_writer
|
112
|
+
when Symbol, String
|
113
|
+
Writer::Method.new(self, options_writer)
|
114
|
+
when Proc
|
115
|
+
Writer::Proc.new(self, options_writer)
|
116
|
+
when false
|
117
|
+
nil
|
118
|
+
else
|
119
|
+
Writer::Basic.new(self)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
private :fetch_writer
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# Factory objects store mapping definitions within mapper class and are
|
3
|
+
# used eventually to generate mapping objects for a particular mapper.
|
4
|
+
class Mapping::Factory
|
5
|
+
# Simply store all arguments necessary to create a new mapping for
|
6
|
+
# a specific mapper.
|
7
|
+
#
|
8
|
+
# @param [*Object] args
|
9
|
+
def initialize(*args)
|
10
|
+
@args = args
|
11
|
+
end
|
12
|
+
|
13
|
+
# Return a new mapping, initialized by +mapper+ and <tt>@args</tt>.
|
14
|
+
#
|
15
|
+
# @param [FlatMap::Mapper] mapper
|
16
|
+
# @return [FlatMap::Mapping]
|
17
|
+
def create(mapper)
|
18
|
+
Mapping.new(mapper, *@args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# Reader module hosts various readers that are used by
|
3
|
+
# mappings for reading and returning values.
|
4
|
+
module Mapping::Reader
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
autoload :Basic
|
8
|
+
autoload :Method
|
9
|
+
autoload :Proc
|
10
|
+
autoload :Formatted
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module FlatMap
|
2
|
+
module Mapping::Reader
|
3
|
+
# Basic reader simply sends a mapped attribute to the target
|
4
|
+
# and returns the result value.
|
5
|
+
class Basic
|
6
|
+
attr_reader :mapping
|
7
|
+
|
8
|
+
delegate :target, :target_attribute, :to => :mapping
|
9
|
+
|
10
|
+
# Initialize the reader with a mapping.
|
11
|
+
#
|
12
|
+
# @param [FlatMap::Mapping] mapping
|
13
|
+
def initialize(mapping)
|
14
|
+
@mapping = mapping
|
15
|
+
end
|
16
|
+
|
17
|
+
# Send the attribute method to the target and return its value.
|
18
|
+
# As a base class for readers, it allows to pass additional
|
19
|
+
# arguments when reading value (for example, used by :enum
|
20
|
+
# format of {Formatted} reader)
|
21
|
+
#
|
22
|
+
# @return [Object] value returned by reading
|
23
|
+
def read(*)
|
24
|
+
target.send(target_attribute)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module FlatMap
|
2
|
+
module Mapping::Reader
|
3
|
+
# Formatted reader reads the value the same as Basic reader does, but
|
4
|
+
# additionally performs value postprocessing. All processing methods
|
5
|
+
# are defined within {Formatted::Formats} module. The method is chosen
|
6
|
+
# based on the :format option when the mapping is defined.
|
7
|
+
class Formatted < Basic
|
8
|
+
extend ActiveSupport::Autoload
|
9
|
+
autoload :Formats
|
10
|
+
|
11
|
+
include Formats
|
12
|
+
|
13
|
+
# Initialize the reader with a +mapping+ and a +format+.
|
14
|
+
#
|
15
|
+
# @param [FlatMap::Mapping] mapping
|
16
|
+
# @param [Symbol] format
|
17
|
+
def initialize(mapping, format)
|
18
|
+
@mapping, @format = mapping, format
|
19
|
+
end
|
20
|
+
|
21
|
+
# Read the value just like the {Basic} reader does, but
|
22
|
+
# additionally send the returned value to its format method.
|
23
|
+
#
|
24
|
+
# Additional arguments will be passed to formatting function
|
25
|
+
# of the mapping's format.
|
26
|
+
#
|
27
|
+
# @return [Object] formatted value
|
28
|
+
def read(*args)
|
29
|
+
format_value(super, *args)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Send the +value+ to the format method, defined in the {Format}
|
33
|
+
# module and specified upon reader initialization.
|
34
|
+
#
|
35
|
+
# Additional optional arguments are passed as well.
|
36
|
+
#
|
37
|
+
# @param [Object] value
|
38
|
+
# @return [Object] formatted value
|
39
|
+
def format_value(value, *args)
|
40
|
+
send(@format, value, *args)
|
41
|
+
end
|
42
|
+
private :format_value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module FlatMap
|
2
|
+
module Mapping::Reader
|
3
|
+
# Hosts various formats that can be applied to values read by mappings
|
4
|
+
# for post-processing.
|
5
|
+
module Formatted::Formats
|
6
|
+
if defined? I18n
|
7
|
+
# Pass +value+ to <tt>I18n::l</tt> method.
|
8
|
+
def i18n_l(value)
|
9
|
+
I18n::l(value) if value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
if defined? PowerEnum
|
14
|
+
# Return the specified +property+ of a +value+ which
|
15
|
+
# is supposed to be an +enum+ record. By default,
|
16
|
+
# uses <tt>:name</tt>. However, <tt>:description</tt>
|
17
|
+
# might be also useful for UI purposes.
|
18
|
+
#
|
19
|
+
# @param [Object] value
|
20
|
+
# @param [Symbol] property
|
21
|
+
# @return [Object]
|
22
|
+
def enum(value, property = :name)
|
23
|
+
value.send(property) if value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module FlatMap
|
2
|
+
module Mapping::Reader
|
3
|
+
# Method mapper calls a method, defined by the mapper, sending
|
4
|
+
# the mapping object to it as an argument.
|
5
|
+
class Method < Basic
|
6
|
+
delegate :mapper, :to => :mapping
|
7
|
+
|
8
|
+
# Initialize the reader with a +mapping+ and a +method+.
|
9
|
+
#
|
10
|
+
# @param [FlatMap::Mapping] mapping
|
11
|
+
# @param [Symbol] method name
|
12
|
+
def initialize(mapping, method)
|
13
|
+
@mapping, @method = mapping, method
|
14
|
+
end
|
15
|
+
|
16
|
+
# Send the <tt>@method</tt> to the mapping's mapper, passing
|
17
|
+
# the mapping itself to it.
|
18
|
+
#
|
19
|
+
# @return [Object] value returned by reader
|
20
|
+
def read
|
21
|
+
mapper.send(@method, mapping)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|