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,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NjNmMzU5MGI0NjIwZGQyZmY4YzBlM2RhYzFmYTMzNGJmM2ZiMzIzMA==
5
+ data.tar.gz: !binary |-
6
+ NjAxZjRjYThiNWMxN2QwN2NkMWNkODFhZDg0NjEzZDQ0MGE5YWM5ZQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ N2M2NmQzZmFkMjZjMjg3NThmZjhjN2I0YmI2NGMyMWQxZTcyNTYwYTFmNzJj
10
+ NmM0Yzc1NDg4YWM1MmM1ZGE3ZTA3MTUzZTNmNjgwZGZkZDYxN2ViMzliNDVl
11
+ NWQ4Yjg3ZDI5NTg2MmZiOTQ2ZjQ5ODk1YTUwODQyN2JmNzIxNDQ=
12
+ data.tar.gz: !binary |-
13
+ ZTg1ZmI2NzY1NjA3ZGYyYzQ1NTAwYzFlMDUxODY2Y2M5YmNlMWI5YTNkNWYx
14
+ MTI5NGQwNDA5YmQwOTNjOTJiYmRhZDdjYWY0YTkwMzU4MzQxM2FlYjU5Nzgy
15
+ YzlkYmEzNmNlM2ExNTA5M2E5Y2E1OGFlNDViYmU1MGVmYzg5M2U=
@@ -0,0 +1,31 @@
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
+ # jeweler generated
16
+ pkg
17
+
18
+ *.gem
19
+ Gemfile.lock
20
+
21
+ # For MacOS:
22
+ .DS_Store
23
+
24
+ # exclude everything in tmp
25
+ tmp/*
26
+ # except the metric_fu directory
27
+ !tmp/metric_fu/
28
+ # but exclude everything *in* the metric_fu directory
29
+ tmp/metric_fu/*
30
+ # except for the _data directory to track metrical outputs
31
+ !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 nested
3
+ --order rand
4
+ --profile
@@ -0,0 +1 @@
1
+ flat_map
@@ -0,0 +1 @@
1
+ ruby-1.9.3-p448
@@ -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
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 TMXCredit, authors Artem Kuzko, Zachary Belzer, Sergey Potapov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,211 @@
1
+ # FlatMap
2
+
3
+ [![Build Status](https://secure.travis-ci.org/TMXCredit/flat_map.png)](http://travis-ci.org/TMXCredit/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
+ * `:target` Allows to manually specify target for the new mapper. May be oject or lambda
90
+ with arity of one that accepts host mapper target as argument. Comes in handy
91
+ when target cannot be obviously detected or requires additional setup:
92
+ `mount :title, :target => lambda{ |customer| customer.title_customers.build.build_title }`
93
+ * `:traits` Specifies list of traits to be used by mounted mapper
94
+ * `:suffix` Specifies the suffix that will be appended to all mappings and mountings of mapper,
95
+ as well as mapper name itself.
96
+
97
+ ### Traits
98
+
99
+ Traits allow mappers to encapsulate named sets of additional definitions, and use them optionally
100
+ on mapper initialization. Everything that can be defined within the mapper may be defined within
101
+ the trait. In fact, from the implementation perspective traits are mappers themselves that are
102
+ mounted on the host mapper.
103
+
104
+ ```ruby
105
+ class CustomerAccountMapper < FlatMap::Mapper
106
+ map :brand, :format => :enum
107
+ trait :with_email do
108
+ map :source, :format => :enum
109
+ mount :email_address
110
+ trait :with_email_phones_residence do
111
+ mount :customer, :traits => [:with_phone_numbers, :with_residence]
112
+ end
113
+ end
114
+ end
115
+ CustomerAccountMapper.find(1).read # => {:brand => 'TLP'}
116
+ CustomerAccountMapper.find(1, :with_email).read # => {:brand => 'TLP', :source => nil, :email_address => 'j.smith@gmail.com'}
117
+ CustomerAccountMapper.find(1, :with_email_phone_residence).read # => :brand, :source, :email_address, phone numbers,
118
+ #:residence attributes - all will be available for reading and writing in plain hash
119
+ ```
120
+
121
+ ### Extensions
122
+
123
+ When mounting a mapper, one can pass an optional block. This block is used as an extension for a mounted
124
+ mapper and acts as an anonymous trait. For example:
125
+
126
+ ```ruby
127
+ class CustomerAccountMapper < FlatMap::Mapper
128
+ mount :customer do
129
+ map :dob => :date_of_birth, :format => :i18n_l
130
+ validates_presence_of :dob
131
+
132
+ mount :unique_identifier
133
+
134
+ validates_acceptance_of :mandatory_agreement, :message => "You must check this box to continue"
135
+ end
136
+ end
137
+ ```
138
+
139
+ ### Validation
140
+
141
+ `FlatMap::Mapper` includes `ActiveModel::Validations` module, allowing each model to
142
+ perform its own validation routines before trying to save its target (which is usually AR model). Mapper
143
+ validation is very handy when mappers are used with Rails forms, since there no need to lookup for a
144
+ deeply nested errors hash of the AR models to extract error messages. Mapper validations will attach
145
+ messages to mapping names.
146
+ Mapper validations become even more useful when used within traits, providing way of very flexible validation sets.
147
+
148
+ ### Callbacks
149
+
150
+ Since mappers include `ActiveModel::Validation`, they already support ActiveSupport's callbacks.
151
+ Additionally, `:save` callbacks have been defined (i.e. there have been define_callbacks `:save`
152
+ call for `FlatMap::Mapper`). This allows you to control flow of mapper saving:
153
+
154
+ ```ruby
155
+ set_callback :save, :before, :set_model_validation
156
+ def set_model_validation
157
+ target.use_validation :some_themis_validation
158
+ end
159
+ ```
160
+
161
+ ### Skipping
162
+
163
+ In some cases, it is required to omit mapper processing after it has been created within mounting chain. If
164
+ `skip!` method is called on mapper, it will return `true` for `valid?` and `save`
165
+ method calls without performing any other operations. For example:
166
+
167
+ ```ruby
168
+ class CustomerMapper < FlatMap::Mapper
169
+ # some definitions
170
+
171
+ trait :product_selection do
172
+ attr_reader :selected_product_id
173
+
174
+ mount :product
175
+
176
+ set_callback :validate, :before, :ignore_new_product
177
+
178
+ def ignore_new_product
179
+ mounting(:product).skip! if product_selected?
180
+ end
181
+
182
+ # some more definitions
183
+ end
184
+ end
185
+ ```
186
+
187
+ ### Attribute Methods
188
+
189
+ All mappers have the ability to read and write values via method calls:
190
+
191
+ ```ruby
192
+ mapper.read[:first_name] # => John
193
+ mapper.first_name # => 'John'
194
+ mapper.last_name = 'Smith'
195
+ ```
196
+
197
+ ## Run tests
198
+
199
+ ```sh
200
+ rake spec
201
+ ```
202
+
203
+ ## Credits
204
+
205
+ * [Artem Kuzko](https://github.com/akuzko)
206
+ * [Zachary Belzer](https://github.com/zbelzer)
207
+ * [Potapov Sergey](https://github.com/greyblake)
208
+
209
+ ## Copyright
210
+
211
+ 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,30 @@
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 = "flat_map"
7
+ s.version = FlatMap::VERSION
8
+ s.authors = ["TMX Credit", "Artem Kuzko", "Zachary Belzer", "Sergey Potapov"]
9
+ s.email = ["rubygems@tmxcredit.com", "a.kuzko@gmail.com", "zbelzer@gmail.com", "blake131313@gmail.com"]
10
+ s.homepage = "https://github.com/TMXCredit/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.rubyforge_project = "flat_map"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+
23
+ # specify any dependencies here; for example:
24
+ s.add_dependency(%q<activesupport>, ["~> 3.2"])
25
+ s.add_dependency(%q<activerecord>, ["~> 3.2"])
26
+ s.add_dependency(%q<yard>, [">= 0"])
27
+
28
+ s.add_development_dependency "rspec"
29
+ s.add_development_dependency "rake"
30
+ end
@@ -0,0 +1,9 @@
1
+ require 'active_support/core_ext'
2
+ require 'active_record'
3
+
4
+ require "flat_map/version"
5
+ require 'flat_map/mapping'
6
+ require 'flat_map/errors'
7
+ require 'flat_map/base_mapper'
8
+ require 'flat_map/mapper'
9
+ require 'flat_map/empty_mapper'
@@ -0,0 +1,95 @@
1
+ module FlatMap
2
+ # +BaseMapper+ is an abstract class that hosts overwhelming majority
3
+ # of common functionality of {EmptyMapper EmptyMappers} and {Mapper Mappers}.
4
+ #
5
+ # For more detailed information on what mappers are, refer to {Mapper} documentation.
6
+ class BaseMapper
7
+ extend ActiveSupport::Autoload
8
+
9
+ autoload :Mapping
10
+ autoload :Mounting
11
+ autoload :Traits
12
+ autoload :Factory
13
+ autoload :AttributeMethods
14
+ autoload :Persistence
15
+ autoload :Skipping
16
+
17
+ include Mapping
18
+ include Mounting
19
+ include Traits
20
+ include AttributeMethods
21
+ include ActiveModel::Validations
22
+ include Persistence
23
+ include Skipping
24
+
25
+ attr_reader :traits
26
+ attr_writer :host, :suffix
27
+ attr_accessor :owner, :name
28
+
29
+ # Callback to dup mappings and mountings on inheritance.
30
+ # The values are cloned from actual mappers (i.e. something
31
+ # like CustomerAccountMapper, since it is useless to clone
32
+ # empty values of FlatMap::Mapper).
33
+ #
34
+ # Note: those class attributes are defined in {Mapping}
35
+ # and {Mounting} modules.
36
+ def self.inherited(subclass)
37
+ subclass.mappings = mappings.dup
38
+ subclass.mountings = mountings.dup
39
+ end
40
+
41
+ # Raise exception on trying to initialize an instance.
42
+ #
43
+ # @raise [RuntimeError]
44
+ def initialize
45
+ raise 'BaseMapper is abstract class and cannot be initialized'
46
+ end
47
+
48
+ # Return a simple string representation of +mapper+. Done so to
49
+ # avoid really long inspection of internal objects (target -
50
+ # usually AR model, mountings and mappings)
51
+ # @return [String]
52
+ def inspect
53
+ to_s
54
+ end
55
+
56
+ # Return +true+ if +mapper+ is owned. This means that current
57
+ # mapper is actually a trait. Thus, it is a part of an owner
58
+ # mapper.
59
+ #
60
+ # @return [Boolean]
61
+ def owned?
62
+ owner.present?
63
+ end
64
+
65
+ # If mapper was mounted by another mapper, host is the one who
66
+ # mounted +self+.
67
+ #
68
+ # @return [FlatMap::Mapper]
69
+ def host
70
+ owned? ? owner.host : @host
71
+ end
72
+
73
+ # Return +true+ if mapper is hosted, i.e. it is mounted by another
74
+ # mapper.
75
+ #
76
+ # @return [Boolean]
77
+ def hosted?
78
+ host.present?
79
+ end
80
+
81
+ # +suffix+ reader. Delegated to owner for owned mappers.
82
+ #
83
+ # @return [String, nil]
84
+ def suffix
85
+ owned? ? owner.suffix : @suffix
86
+ end
87
+
88
+ # Return +true+ if +suffix+ is present.
89
+ #
90
+ # @return [Boolean]
91
+ def suffixed?
92
+ suffix.present?
93
+ end
94
+ end
95
+ end