flat_map 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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