flat_map 0.0.3

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.
Files changed (64) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +31 -0
  3. data/.metrics +17 -0
  4. data/.rspec +4 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +9 -0
  8. data/Gemfile +20 -0
  9. data/LICENSE +20 -0
  10. data/README.markdown +211 -0
  11. data/Rakefile +15 -0
  12. data/flat_map.gemspec +30 -0
  13. data/lib/flat_map.rb +9 -0
  14. data/lib/flat_map/base_mapper.rb +95 -0
  15. data/lib/flat_map/base_mapper/attribute_methods.rb +54 -0
  16. data/lib/flat_map/base_mapper/factory.rb +238 -0
  17. data/lib/flat_map/base_mapper/mapping.rb +123 -0
  18. data/lib/flat_map/base_mapper/mounting.rb +168 -0
  19. data/lib/flat_map/base_mapper/persistence.rb +145 -0
  20. data/lib/flat_map/base_mapper/skipping.rb +62 -0
  21. data/lib/flat_map/base_mapper/traits.rb +94 -0
  22. data/lib/flat_map/empty_mapper.rb +29 -0
  23. data/lib/flat_map/errors.rb +57 -0
  24. data/lib/flat_map/mapper.rb +213 -0
  25. data/lib/flat_map/mapper/skipping.rb +45 -0
  26. data/lib/flat_map/mapper/targeting.rb +130 -0
  27. data/lib/flat_map/mapping.rb +124 -0
  28. data/lib/flat_map/mapping/factory.rb +21 -0
  29. data/lib/flat_map/mapping/reader.rb +12 -0
  30. data/lib/flat_map/mapping/reader/basic.rb +28 -0
  31. data/lib/flat_map/mapping/reader/formatted.rb +45 -0
  32. data/lib/flat_map/mapping/reader/formatted/formats.rb +28 -0
  33. data/lib/flat_map/mapping/reader/method.rb +25 -0
  34. data/lib/flat_map/mapping/reader/proc.rb +15 -0
  35. data/lib/flat_map/mapping/writer.rb +11 -0
  36. data/lib/flat_map/mapping/writer/basic.rb +25 -0
  37. data/lib/flat_map/mapping/writer/method.rb +28 -0
  38. data/lib/flat_map/mapping/writer/proc.rb +18 -0
  39. data/lib/flat_map/version.rb +3 -0
  40. data/spec/flat_map/empty_mapper_spec.rb +36 -0
  41. data/spec/flat_map/errors_spec.rb +23 -0
  42. data/spec/flat_map/mapper/attribute_methods_spec.rb +36 -0
  43. data/spec/flat_map/mapper/callbacks_spec.rb +76 -0
  44. data/spec/flat_map/mapper/factory_spec.rb +258 -0
  45. data/spec/flat_map/mapper/mapping_spec.rb +98 -0
  46. data/spec/flat_map/mapper/mounting_spec.rb +142 -0
  47. data/spec/flat_map/mapper/skipping_spec.rb +91 -0
  48. data/spec/flat_map/mapper/targeting_spec.rb +156 -0
  49. data/spec/flat_map/mapper/traits_spec.rb +172 -0
  50. data/spec/flat_map/mapper/validations_spec.rb +72 -0
  51. data/spec/flat_map/mapper_spec.rb +9 -0
  52. data/spec/flat_map/mapping/factory_spec.rb +12 -0
  53. data/spec/flat_map/mapping/reader/basic_spec.rb +15 -0
  54. data/spec/flat_map/mapping/reader/formatted_spec.rb +62 -0
  55. data/spec/flat_map/mapping/reader/method_spec.rb +13 -0
  56. data/spec/flat_map/mapping/reader/proc_spec.rb +13 -0
  57. data/spec/flat_map/mapping/writer/basic_spec.rb +15 -0
  58. data/spec/flat_map/mapping/writer/method_spec.rb +13 -0
  59. data/spec/flat_map/mapping/writer/proc_spec.rb +13 -0
  60. data/spec/flat_map/mapping_spec.rb +123 -0
  61. data/spec/spec_helper.rb +7 -0
  62. data/tmp/metric_fu/_data/20131218.yml +6902 -0
  63. data/tmp/metric_fu/_data/20131219.yml +6726 -0
  64. 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