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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -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/HornsAndHooves-flat_map.gemspec +29 -0
  10. data/LICENSE +21 -0
  11. data/README.markdown +214 -0
  12. data/Rakefile +15 -0
  13. data/lib/flat_map.rb +14 -0
  14. data/lib/flat_map/errors.rb +57 -0
  15. data/lib/flat_map/mapping.rb +124 -0
  16. data/lib/flat_map/mapping/factory.rb +21 -0
  17. data/lib/flat_map/mapping/reader.rb +12 -0
  18. data/lib/flat_map/mapping/reader/basic.rb +28 -0
  19. data/lib/flat_map/mapping/reader/formatted.rb +45 -0
  20. data/lib/flat_map/mapping/reader/formatted/formats.rb +28 -0
  21. data/lib/flat_map/mapping/reader/method.rb +25 -0
  22. data/lib/flat_map/mapping/reader/proc.rb +15 -0
  23. data/lib/flat_map/mapping/writer.rb +11 -0
  24. data/lib/flat_map/mapping/writer/basic.rb +25 -0
  25. data/lib/flat_map/mapping/writer/method.rb +28 -0
  26. data/lib/flat_map/mapping/writer/proc.rb +18 -0
  27. data/lib/flat_map/model_mapper.rb +195 -0
  28. data/lib/flat_map/model_mapper/persistence.rb +108 -0
  29. data/lib/flat_map/model_mapper/skipping.rb +45 -0
  30. data/lib/flat_map/open_mapper.rb +113 -0
  31. data/lib/flat_map/open_mapper/attribute_methods.rb +55 -0
  32. data/lib/flat_map/open_mapper/factory.rb +244 -0
  33. data/lib/flat_map/open_mapper/mapping.rb +123 -0
  34. data/lib/flat_map/open_mapper/mounting.rb +168 -0
  35. data/lib/flat_map/open_mapper/persistence.rb +178 -0
  36. data/lib/flat_map/open_mapper/skipping.rb +66 -0
  37. data/lib/flat_map/open_mapper/traits.rb +95 -0
  38. data/lib/flat_map/version.rb +3 -0
  39. data/spec/flat_map/errors_spec.rb +23 -0
  40. data/spec/flat_map/mapper/attribute_methods_spec.rb +36 -0
  41. data/spec/flat_map/mapper/callbacks_spec.rb +76 -0
  42. data/spec/flat_map/mapper/factory_spec.rb +285 -0
  43. data/spec/flat_map/mapper/mapping_spec.rb +98 -0
  44. data/spec/flat_map/mapper/mounting_spec.rb +142 -0
  45. data/spec/flat_map/mapper/persistence_spec.rb +152 -0
  46. data/spec/flat_map/mapper/skipping_spec.rb +91 -0
  47. data/spec/flat_map/mapper/targeting_spec.rb +156 -0
  48. data/spec/flat_map/mapper/traits_spec.rb +172 -0
  49. data/spec/flat_map/mapper/validations_spec.rb +72 -0
  50. data/spec/flat_map/mapper_spec.rb +9 -0
  51. data/spec/flat_map/mapping/factory_spec.rb +12 -0
  52. data/spec/flat_map/mapping/reader/basic_spec.rb +15 -0
  53. data/spec/flat_map/mapping/reader/formatted_spec.rb +62 -0
  54. data/spec/flat_map/mapping/reader/method_spec.rb +13 -0
  55. data/spec/flat_map/mapping/reader/proc_spec.rb +13 -0
  56. data/spec/flat_map/mapping/writer/basic_spec.rb +15 -0
  57. data/spec/flat_map/mapping/writer/method_spec.rb +13 -0
  58. data/spec/flat_map/mapping/writer/proc_spec.rb +13 -0
  59. data/spec/flat_map/mapping_spec.rb +123 -0
  60. data/spec/flat_map/open_mapper_spec.rb +19 -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 +220 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e476b0c2831807b9734932bcec75f3d798a09d08
4
+ data.tar.gz: 88d957bf1d9467c8abcf2f1e75e655c28630a51c
5
+ SHA512:
6
+ metadata.gz: 6d721973e3a24a1f9e42599f7daaa3ce4c8ca34b7de8cbdcd8cec93249a72ef4adbe29025ddf481a52de4622309dd04aa2d1cd8f89028cb426001cfa5f449b9f
7
+ data.tar.gz: f8b5c8efcdff94145f7a329abc3f124797754bcc50baf3ecd828d70cd9bb37973f8a247e4fb4d99b7fcc1b4ea14a61da4d808b30c923d40b032ad5641bee688a
@@ -0,0 +1,34 @@
1
+ # SimpleCov generated
2
+ coverage
3
+ coverage.data
4
+
5
+ # rdoc generated
6
+ rdoc
7
+
8
+ # yard generated
9
+ doc
10
+ .yardoc
11
+
12
+ # bundler
13
+ .bundle
14
+
15
+ # IDE
16
+ .idea
17
+
18
+ # jeweler generated
19
+ pkg
20
+
21
+ *.gem
22
+ Gemfile.lock
23
+
24
+ # For MacOS:
25
+ .DS_Store
26
+
27
+ # exclude everything in tmp
28
+ tmp/*
29
+ # except the metric_fu directory
30
+ !tmp/metric_fu/
31
+ # but exclude everything *in* the metric_fu directory
32
+ tmp/metric_fu/*
33
+ # except for the _data directory to track metrical outputs
34
+ !tmp/metric_fu/_data/
@@ -0,0 +1,17 @@
1
+ MetricFu::Configuration.run do |config|
2
+ [ :rcov,
3
+ :rails_best_practices
4
+ ].each do |metric|
5
+ config.configure_metric(metric) do |m|
6
+ m.enabled = false
7
+ end
8
+ end
9
+
10
+ config.configure_metric(:cane) do |cane|
11
+ cane.line_length = 100
12
+ end
13
+
14
+ config.configure_metric(:flay) do |flay|
15
+ flay.minimum_score = 10
16
+ end
17
+ end
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format documentation
3
+ --order rand
4
+ --profile
@@ -0,0 +1 @@
1
+ flat_map
@@ -0,0 +1 @@
1
+ 2.1.2
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ script: "bundle exec rake spec"
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.0
7
+ notifications:
8
+ email:
9
+ - a.kuzko@gmail.com
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in flat_map.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'redcarpet'
8
+ gem 'yard'
9
+ gem 'pry'
10
+ end
11
+
12
+ group :development, :test do
13
+ # code metrics:
14
+ gem "metric_fu"
15
+ end
16
+
17
+ group :test do
18
+ gem 'simplecov' , :require => false
19
+ gem 'simplecov-rcov-text', :require => false
20
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "flat_map/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "HornsAndHooves-flat_map"
7
+ s.version = FlatMap::VERSION
8
+ s.authors = ["Artem Kuzko", "Zachary Belzer", "Sergey Potapov"]
9
+ s.email = ["a.kuzko@gmail.com", "zbelzer@gmail.com", "blake131313@gmail.com"]
10
+ s.homepage = "https://github.com/HornsAndHooves/flat_map"
11
+ s.licenses = ["LICENSE"]
12
+ s.summary = %q{Deep object graph to a plain properties mapper}
13
+ s.description = %q{This library allows to map accessors and properties of deeply
14
+ nested object graph to a plain mapper object with flexible behavior}
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_dependency(%q<activesupport>, ["~> 4.0.5"])
23
+ s.add_dependency(%q<activerecord>, ["~> 4.0.5"])
24
+ s.add_dependency(%q<yard>, [">= 0"])
25
+
26
+ s.add_development_dependency "rspec", "~> 3.0"
27
+ s.add_development_dependency "rspec-its"
28
+ s.add_development_dependency "rake"
29
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 HornsAndHooves
4
+ Copyright (c) 2013 TMXCredit, authors Artem Kuzko, Zachary Belzer, Sergey Potapov
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
+ the Software, and to permit persons to whom the Software is furnished to do so,
11
+ subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,214 @@
1
+ # FlatMap
2
+
3
+ [![Build Status](https://secure.travis-ci.org/HornsAndHooves/flat_map.png)](http://travis-ci.org/HornsAndHooves/flat_map)
4
+
5
+ FlatMap is a flexible tool for mapping a complex, deeply nested object graph
6
+ into a mapper object with all mapped attributes accessible in a plain way.
7
+
8
+
9
+ ## Usage
10
+
11
+ ### Mapper
12
+
13
+ FlatMap mappers are designed to provide complex set of data, distributed over
14
+ associated AR models, in the simple form of a plain hash. They accept a plain
15
+ hash of the same format and distribute its values over deeply nested AR models.
16
+ To achieve this goal, Mapper uses three major concepts: Mappings, Mountings and
17
+ Traits.
18
+
19
+ ### Mappings
20
+
21
+ Mappings are defined view `Mapper.map` method. They represent a simple one-to-one
22
+ relation between target attribute and a mapper, extended by additional features
23
+ for convenience. The best way to show how they work is by example:
24
+
25
+ ```ruby
26
+ class CustomerMapper < FlatMap::Mapper
27
+ # When there is no need to rename attributes, they can be passed as array:
28
+ map :first_name, :last_name
29
+ # When hash is used, it will map field name to attribute name:
30
+ map :dob => :date_of_birth
31
+ # Also, additional options can be used:
32
+ map :name_suffix, :format => :enum
33
+ map :password, :reader => false, :writer => :assign_password
34
+ # Or you can combine all definitions together if they all are common:
35
+ map :first_name, :last_name,
36
+ :dob => :date_of_birth,
37
+ :suffix => :name_suffix,
38
+ :reader => :my_custom_reader
39
+ end
40
+ ```
41
+ When mappings are defined, one can read and write values using them:
42
+
43
+ ```ruby
44
+ mapper = CustomerMapper.find(1)
45
+ mapper.read # => {:first_name => 'John', :last_name => 'Smith', :dob => '02/01/1970'}
46
+ mapper.write(params) # will assign same-looking hash of arguments
47
+ ```
48
+
49
+ Following options may be used when defining mappings:
50
+
51
+ * `:format` Allows to additionally process output value on reading it. All formats are
52
+ defined within `FlatMap::Mapping::Reader::Formatted::Formats` and
53
+ specify the actual output of the mapping
54
+ * `:reader` Allows you to manually control reader value of a mapping, or a group of
55
+ mappings listed on definition. When String or Symbol is used, will call
56
+ a method, defined by mapper class, and pass mapping object to it. When
57
+ lambda is used, mapper's target (the model) will be passed to it.
58
+ * `:writer` Just like with the :reader option, allows to control how value is assigned
59
+ (written). Works the same way as :reader does, but additionally value is
60
+ sent to both mapper method and lambda.
61
+ * `:multiparam` If used, multiparam attributes will be extracted from params, when
62
+ those are passed for writing. Class should be passed as a value for
63
+ this option. Object of this class will be initialized with the arguments
64
+ extracted from params hash.
65
+
66
+ ### Mountings
67
+
68
+ Mappers may be mounted on top of each other. This ability allows host mapper to gain all the
69
+ mappings of the mounted mapper, thus providing more information for external usage (both reading
70
+ and writing). Usually, target for mounted mapper may be obtained from association of target of
71
+ the host mapper itself, but may be defined manually.
72
+
73
+ ```ruby
74
+ class CustomerMapper < FlatMap::Mapper
75
+ map :first_name, :last_name
76
+ end
77
+ class CustomerAccountMapper < FlatMap::Mapper
78
+ map :source, :brand, :format => :enum
79
+ mount :customer
80
+ end
81
+ mapper = CustomerAccountMapper.find(1)
82
+ mapper.read # => {:first_name => 'John', :last_name => 'Smith', :source => nil, :brand => 'FTW'}
83
+ mapper.write(params) # Will assign params for both CustomerAccount and Customer records
84
+ ```
85
+
86
+ The following options may be used when mounting a mapper:
87
+
88
+ * `:mapper_class` Specifies mapper class if it cannot be determined from mounting itself.
89
+ * `:mapper_class_name` Alternate string form of class name instead of mapper_class.
90
+ * `:target` Allows to manually specify target for the new mapper. May be oject or lambda
91
+ with arity of one that accepts host mapper target as argument. Comes in handy
92
+ when target cannot be obviously detected or requires additional setup:
93
+ `mount :title, :target => lambda{ |customer| customer.title_customers.build.build_title }`
94
+ * `:traits` Specifies list of traits to be used by mounted mapper
95
+ * `:suffix` Specifies the suffix that will be appended to all mappings and mountings of mapper,
96
+ as well as mapper name itself.
97
+
98
+ ### Traits
99
+
100
+ Traits allow mappers to encapsulate named sets of additional definitions, and use them optionally
101
+ on mapper initialization. Everything that can be defined within the mapper may be defined within
102
+ the trait. In fact, from the implementation perspective traits are mappers themselves that are
103
+ mounted on the host mapper.
104
+
105
+ ```ruby
106
+ class CustomerAccountMapper < FlatMap::Mapper
107
+ map :brand, :format => :enum
108
+ trait :with_email do
109
+ map :source, :format => :enum
110
+ mount :email_address
111
+ trait :with_email_phones_residence do
112
+ mount :customer, :traits => [:with_phone_numbers, :with_residence]
113
+ end
114
+ end
115
+ end
116
+ CustomerAccountMapper.find(1).read # => {:brand => 'TLP'}
117
+ CustomerAccountMapper.find(1, :with_email).read # => {:brand => 'TLP', :source => nil, :email_address => 'j.smith@gmail.com'}
118
+ CustomerAccountMapper.find(1, :with_email_phone_residence).read # => :brand, :source, :email_address, phone numbers,
119
+ #:residence attributes - all will be available for reading and writing in plain hash
120
+ ```
121
+
122
+ ### Extensions
123
+
124
+ When mounting a mapper, one can pass an optional block. This block is used as an extension for a mounted
125
+ mapper and acts as an anonymous trait. For example:
126
+
127
+ ```ruby
128
+ class CustomerAccountMapper < FlatMap::Mapper
129
+ mount :customer do
130
+ map :dob => :date_of_birth, :format => :i18n_l
131
+ validates_presence_of :dob
132
+
133
+ mount :unique_identifier
134
+
135
+ validates_acceptance_of :mandatory_agreement, :message => "You must check this box to continue"
136
+ end
137
+ end
138
+ ```
139
+
140
+ ### Validation
141
+
142
+ `FlatMap::Mapper` includes `ActiveModel::Validations` module, allowing each model to
143
+ perform its own validation routines before trying to save its target (which is usually AR model). Mapper
144
+ validation is very handy when mappers are used with Rails forms, since there no need to lookup for a
145
+ deeply nested errors hash of the AR models to extract error messages. Mapper validations will attach
146
+ messages to mapping names.
147
+ Mapper validations become even more useful when used within traits, providing way of very flexible validation sets.
148
+
149
+ ### Callbacks
150
+
151
+ Since mappers include `ActiveModel::Validation`, they already support ActiveSupport's callbacks.
152
+ Additionally, `:save` callbacks have been defined (i.e. there have been define_callbacks `:save`
153
+ call for `FlatMap::Mapper`). This allows you to control flow of mapper saving:
154
+
155
+ ```ruby
156
+ set_callback :save, :before, :set_model_validation
157
+ def set_model_validation
158
+ target.use_validation :some_themis_validation
159
+ end
160
+ ```
161
+
162
+ ### Skipping
163
+
164
+ In some cases, it is required to omit mapper processing after it has been created within mounting chain. If
165
+ `skip!` method is called on mapper, it will return `true` for `valid?` and `save`
166
+ method calls without performing any other operations. For example:
167
+
168
+ ```ruby
169
+ class CustomerMapper < FlatMap::Mapper
170
+ # some definitions
171
+
172
+ trait :product_selection do
173
+ attr_reader :selected_product_id
174
+
175
+ mount :product
176
+
177
+ set_callback :validate, :before, :ignore_new_product
178
+
179
+ def ignore_new_product
180
+ mounting(:product).skip! if product_selected?
181
+ end
182
+
183
+ # some more definitions
184
+ end
185
+ end
186
+ ```
187
+
188
+ ### Attribute Methods
189
+
190
+ All mappers have the ability to read and write values via method calls:
191
+
192
+ ```ruby
193
+ mapper.read[:first_name] # => John
194
+ mapper.first_name # => 'John'
195
+ mapper.last_name = 'Smith'
196
+ ```
197
+
198
+ ## Run tests
199
+
200
+ ```sh
201
+ rake spec
202
+ ```
203
+
204
+ ## Credits
205
+
206
+ * [Artem Kuzko](https://github.com/akuzko)
207
+ * [Zachary Belzer](https://github.com/zbelzer)
208
+ * [Potapov Sergey](https://github.com/greyblake)
209
+
210
+ ## Copyright
211
+
212
+ Copyright (c) 2014 HornsAndHooves.
213
+
214
+ Copyright (c) 2013 TMX Credit.
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ task :default => :spec
5
+ RSpec::Core::RakeTask.new
6
+
7
+ require 'rdoc/task'
8
+ Rake::RDocTask.new do |rdoc|
9
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
10
+
11
+ rdoc.rdoc_dir = 'rdoc'
12
+ rdoc.title = "flat_map #{version}"
13
+ rdoc.rdoc_files.include('README*')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
@@ -0,0 +1,14 @@
1
+ require 'ostruct'
2
+ require 'active_support'
3
+ require 'active_record'
4
+
5
+ require "flat_map/version"
6
+ require 'flat_map/mapping'
7
+ require 'flat_map/errors'
8
+ require 'flat_map/open_mapper'
9
+ require 'flat_map/model_mapper'
10
+
11
+ module FlatMap
12
+ # for backwards compatability
13
+ Mapper = ModelMapper
14
+ end
@@ -0,0 +1,57 @@
1
+ module FlatMap
2
+ # Inherited from ActiveModel::Errors to slightly ease work when writing
3
+ # attributes in a way that can possibly result in an exception. If we'd
4
+ # want to add errors on that point and see them in the resulting object,
5
+ # we have to preserve them before owner's <tt>run_validations!</tt> method
6
+ # call, since it will clear all the errors.
7
+ #
8
+ # After validation complete, preserved errors are added to the list of
9
+ # the original ones.
10
+ #
11
+ # Usecase scenario:
12
+ #
13
+ # class MyMapper < FlatMap::Mapper
14
+ # def custom_attr=(value)
15
+ # raise MyException, 'cannot be foo' if value == 'foo'
16
+ # rescue MyException => e
17
+ # errors.preserve :custom_attr, e.message
18
+ # end
19
+ # end
20
+ #
21
+ # mapper = MyMapper.new(MyObject.new)
22
+ # mapper.apply(:custom_attr => 'foo') # => false
23
+ # mapper.errors[:custom_attr] # => ['cannot be foo']
24
+ class Errors < ActiveModel::Errors
25
+ # Add <tt>@preserved_errors</tt> to object.
26
+ def initialize(*)
27
+ @preserved_errors = {}
28
+ super
29
+ end
30
+
31
+ # Postpone error. It will be added to <tt>@messages</tt> later,
32
+ # on <tt>empty?</tt> method call.
33
+ #
34
+ # @param [String, Symbol] key
35
+ # @param [String] message
36
+ def preserve(key, message)
37
+ @preserved_errors[key] = message
38
+ end
39
+
40
+ # Overloaded to add <tt>@preserved_errors</tt> to the list of
41
+ # original <tt>@messages</tt>. <tt>@preserved_errors</tt> are
42
+ # cleared after this method call.
43
+ def empty?
44
+ unless @preserved_errors.empty?
45
+ @preserved_errors.each{ |key, value| add(key, value) }
46
+ @preserved_errors.clear
47
+ end
48
+ super
49
+ end
50
+
51
+ # Overridden to add suffixing support for mappings of mappers with name suffix
52
+ def add(attr, *args)
53
+ attr = :"#{attr}_#{@base.suffix}" if attr != :base && @base.suffixed?
54
+ super
55
+ end
56
+ end
57
+ end