HornsAndHooves-flat_map 0.2.0

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 +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