attrocity 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +30 -10
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile.lock +37 -0
  6. data/LICENSE +22 -0
  7. data/README.md +32 -2
  8. data/Rakefile +14 -0
  9. data/TODO.md +36 -0
  10. data/attrocity.gemspec +5 -2
  11. data/lib/attrocity.rb +91 -2
  12. data/lib/attrocity/attributes/attribute.rb +10 -0
  13. data/lib/attrocity/attributes/attribute_methods_builder.rb +47 -0
  14. data/lib/attrocity/attributes/attribute_set.rb +24 -0
  15. data/lib/attrocity/attributes/attribute_template.rb +28 -0
  16. data/lib/attrocity/attributes/attribute_template_set.rb +16 -0
  17. data/lib/attrocity/attributes/attributes_hash.rb +9 -0
  18. data/lib/attrocity/attributes/model_attribute.rb +14 -0
  19. data/lib/attrocity/attributes/model_attribute_set.rb +6 -0
  20. data/lib/attrocity/builders/model_builder.rb +60 -0
  21. data/lib/attrocity/builders/object_extension_builder.rb +18 -0
  22. data/lib/attrocity/coercer_registry.rb +38 -0
  23. data/lib/attrocity/coercers/boolean.rb +12 -0
  24. data/lib/attrocity/coercers/integer.rb +13 -0
  25. data/lib/attrocity/coercers/string.rb +14 -0
  26. data/lib/attrocity/mappers/key_mapper.rb +14 -0
  27. data/lib/attrocity/model.rb +10 -0
  28. data/lib/attrocity/value_extractor.rb +23 -0
  29. data/lib/attrocity/version.rb +1 -1
  30. data/notes.md +253 -0
  31. data/spec/attrocity/attributes/attribute_methods_builder_spec.rb +57 -0
  32. data/spec/attrocity/attributes/attribute_set_spec.rb +24 -0
  33. data/spec/attrocity/attributes/attribute_spec.rb +4 -0
  34. data/spec/attrocity/attributes/attribute_template_set_spec.rb +6 -0
  35. data/spec/attrocity/attributes/attribute_template_spec.rb +25 -0
  36. data/spec/attrocity/attributes/model_attribute_spec.rb +26 -0
  37. data/spec/attrocity/attrocity_spec.rb +83 -0
  38. data/spec/attrocity/coercer_registry_spec.rb +14 -0
  39. data/spec/attrocity/coercers/boolean_spec.rb +32 -0
  40. data/spec/attrocity/coercers/coercer_with_args_spec.rb +9 -0
  41. data/spec/attrocity/coercers/integer_spec.rb +25 -0
  42. data/spec/attrocity/coercers/string_spec.rb +13 -0
  43. data/spec/attrocity/default_value_spec.rb +22 -0
  44. data/spec/attrocity/mappers/key_mapper_spec.rb +22 -0
  45. data/spec/attrocity/mapping_spec.rb +47 -0
  46. data/spec/attrocity/model_spec.rb +34 -0
  47. data/spec/attrocity/object_extension_spec.rb +53 -0
  48. data/spec/attrocity/value_extractor_spec.rb +23 -0
  49. data/spec/spec_helper.rb +93 -0
  50. data/spec/support/examples.rb +79 -0
  51. metadata +99 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5c66ceb662de478873245283e4016d0039d6ad35
4
- data.tar.gz: f1d8aeef00103d3c1c6864fc46f654c6223ad16e
3
+ metadata.gz: 0ecd334914b12e956f87212e8a6ea99334e85176
4
+ data.tar.gz: 8f46fc6967abe69da2caf3bac80e7bb9595a45e2
5
5
  SHA512:
6
- metadata.gz: abd90edfdbb9e98f828e9a50312f1cba09054aabed06dc9eedaac6a59aeafa09a93d3413645de42bc2505b9f53260a69966bee7b4bc271f9f6ccef7cbe4a3f98
7
- data.tar.gz: f75a102ad93042b8963dd1b541ba06553a646b9f58d187c67c8b284da06cf0981f76caec73c69670abc07008c90dd53e2a081949d517e089dbc512e30159775e
6
+ metadata.gz: faf2df44f1fe13921a46eecdab94347bf021c5ed220d8716106e7011b591633c475692a54fa572436efe7f766e6c991e58dda7633c1dfb39af67eb1a92bcc6cc
7
+ data.tar.gz: e36f0c71909ffea9358b005df5509704f596c59c9f657453c6bee7c02d443e84bb5b8bf55ed34f4874b0ca92f12199e9abe89350545c68c3d38f26012dfc6def
data/.gitignore CHANGED
@@ -1,14 +1,34 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
1
+ *.gem
2
+ *.rbc
3
+ /.config
5
4
  /coverage/
6
- /doc/
5
+ /InstalledFiles
7
6
  /pkg/
8
7
  /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
9
10
  /tmp/
10
- *.bundle
11
- *.so
12
- *.o
13
- *.a
14
- mkmf.log
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.4
4
+ - 2.1.8
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ attrocity (0.0.1)
5
+ hashie (~> 3.4)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.2.5)
11
+ hashie (3.4.3)
12
+ rake (10.4.2)
13
+ rspec (3.4.0)
14
+ rspec-core (~> 3.4.0)
15
+ rspec-expectations (~> 3.4.0)
16
+ rspec-mocks (~> 3.4.0)
17
+ rspec-core (3.4.1)
18
+ rspec-support (~> 3.4.0)
19
+ rspec-expectations (3.4.0)
20
+ diff-lcs (>= 1.2.0, < 2.0)
21
+ rspec-support (~> 3.4.0)
22
+ rspec-mocks (3.4.0)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.4.0)
25
+ rspec-support (3.4.1)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ attrocity!
32
+ bundler (~> 1.11)
33
+ rake (~> 10.4)
34
+ rspec (~> 3.4)
35
+
36
+ BUNDLED WITH
37
+ 1.11.2
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 RentPath
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, 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,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md CHANGED
@@ -1,6 +1,12 @@
1
- # Attrocity
1
+ Attrocity
2
+ =========
2
3
 
3
4
  You should totally be using [Virtus](https://github.com/solnic/virtus) instead.
5
+ This gem is a work in progress.
6
+
7
+ The original code for this gem was written as an employee of
8
+ [RentPath](http://rentpath.com). Since I left RentPath in April 2015, the gem
9
+ has not been touched, so I'm taking over.
4
10
 
5
11
  ## Installation
6
12
 
@@ -20,7 +26,31 @@ Or install it yourself as:
20
26
 
21
27
  ## Usage
22
28
 
23
- Don't
29
+ Don't. Not yet.
30
+
31
+ ## Objects
32
+
33
+ ### Mappers
34
+
35
+ A mapper is responsible for extracting values from source data. A mapper object
36
+ responds to `call` and accepts as arguments to call: the instantiated model
37
+ object and the source data hash.
38
+
39
+ The default, built-in mapper is KeyMapper. When no mapper is explicitly
40
+ declared, KeyMapper is used. It simply fetches from the source data hash, using
41
+ the attribute name as hash key and falling back to nil as default.
42
+
43
+ ### Coercers
44
+
45
+ Coercers translate mapped data into the correct type. For example, the string
46
+ '1' is coerced to the integer 1 if the built-in integer coercer is used.
47
+
48
+ The built-in coercers are: string, integer, and boolean.
49
+
50
+ Custom coercers are the primary way of extracting data when the built-in
51
+ coercers are not sufficient for the use case.
52
+
53
+ [ ... more stuff on custom coercers here ... ]
24
54
 
25
55
  ## Contributing
26
56
 
data/Rakefile CHANGED
@@ -1,2 +1,16 @@
1
1
  require "bundler/gem_tasks"
2
2
 
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ task :default => [:spec]
7
+
8
+ desc 'Run specs'
9
+ RSpec::Core::RakeTask.new(:spec) do |r|
10
+ r.verbose = false
11
+ end
12
+ rescue LoadError
13
+ puts 'RSpec not available.'
14
+ end
15
+
16
+
data/TODO.md ADDED
@@ -0,0 +1,36 @@
1
+ ---
2
+ title: TODO
3
+ ---
4
+
5
+ - [ ] Make a decision about and implement mapped attribute rules
6
+
7
+ Given the attribute declaration:
8
+ ```ruby
9
+ class User
10
+ include Attrocity.model
11
+
12
+ attribute :first_name, coercer: :string, from: :firstname
13
+ end
14
+ ```
15
+
16
+ And the following argument to initialize:
17
+ ```ruby
18
+ User.new({ first_name: 'Homer' })
19
+ ```
20
+
21
+ Should the attribute get initialized with user.first_name of Homer? I think it probably should.
22
+
23
+ It gets a little muddier when there's a default involved, but I like the following prioritization rules:
24
+
25
+ 1. Mapped attribute
26
+ 2. Default value
27
+ 3. Unmapped attribute
28
+ 4. Coercer default return
29
+
30
+ I think this only applies to the default KeyMapper. It's really about mapping rules. The coercers handle defaults.
31
+
32
+ --------------------------------------------------------------------------------
33
+
34
+ - [ ] Compare to Hashie
35
+
36
+ - [ ] Go through notes.md and edit, update
@@ -18,6 +18,9 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.7"
22
- spec.add_development_dependency "rake", "~> 10.0"
21
+ spec.add_dependency 'hashie', '~> 3.4'
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.11"
24
+ spec.add_development_dependency "rake", "~> 10.4"
25
+ spec.add_development_dependency 'rspec', '~> 3.4'
23
26
  end
@@ -1,5 +1,94 @@
1
- require "attrocity/version"
1
+ module Attrocity
2
+ CoercionError = Class.new(StandardError)
3
+ end
4
+
5
+ require 'attrocity/version'
6
+ require 'attrocity/attributes/attribute_template'
7
+ require 'attrocity/attributes/attribute_methods_builder'
8
+ require 'attrocity/attributes/attribute_template_set'
9
+ require 'attrocity/attributes/attributes_hash'
10
+ require 'attrocity/attributes/model_attribute'
11
+ require 'attrocity/attributes/model_attribute_set'
12
+ require 'attrocity/model'
13
+ require 'attrocity/value_extractor'
14
+ require 'attrocity/attributes/attribute'
15
+ require 'attrocity/attributes/attribute_set'
16
+ require 'attrocity/builders/object_extension_builder'
17
+ require 'attrocity/builders/model_builder'
18
+ require 'attrocity/coercer_registry'
19
+ require 'attrocity/coercers/boolean'
20
+ require 'attrocity/coercers/integer'
21
+ require 'attrocity/coercers/string'
22
+ require 'attrocity/mappers/key_mapper'
2
23
 
3
24
  module Attrocity
4
- # Your code goes here...
25
+
26
+ def self.model
27
+ ModelBuilder.new
28
+ end
29
+
30
+ def self.object_extension
31
+ ObjectExtensionBuilder.new
32
+ end
33
+
34
+ def self.default_mapper
35
+ KeyMapper
36
+ end
37
+
38
+ module ModuleMethods
39
+ def attribute(name, coercer:, default: nil, from: name)
40
+ coercer = CoercerRegistry.instance_for(*coercer_args(coercer))
41
+ attribute_set << AttributeTemplate.new(name, coercer, mapper(from, default))
42
+ end
43
+
44
+ def coercer_args(args)
45
+ if args.is_a?(Symbol)
46
+ [args, {}]
47
+ elsif args.is_a?(Hash)
48
+ name = args.delete(:name)
49
+ [name, args]
50
+ end
51
+ end
52
+
53
+ def model_attribute(name, model:)
54
+ ModelAttribute.new(name, model).tap do |model_attr|
55
+ model_attribute_set << model_attr
56
+ end
57
+ end
58
+
59
+ def mapper(mapping, default)
60
+ if mapping.respond_to?(:call)
61
+ mapping
62
+ else
63
+ Attrocity.default_mapper.new(mapping, default)
64
+ end
65
+ end
66
+
67
+ def attribute_set
68
+ @attribute_set ||= AttributeTemplateSet.new
69
+ end
70
+
71
+ def model_attribute_set
72
+ @model_attribute_set ||= ModelAttributeSet.new
73
+ end
74
+
75
+ def model_from_mapped_data(data)
76
+ attrs = attribute_set.attributes
77
+ self.new(
78
+ Hash.new.tap do |h|
79
+ data.each do |k,v|
80
+ # TODO: Use more efficient enumerable method
81
+ key = attrs.collect { |attr| attr.mapper_key_for(k) }.compact.first
82
+ h[key] = v if key
83
+ end
84
+ end
85
+ ).model
86
+ end
87
+ end
88
+ end
89
+
90
+ Attrocity::CoercerRegistry.register do
91
+ add :boolean, Attrocity::Coercers::Boolean
92
+ add :integer, Attrocity::Coercers::Integer
93
+ add :string, Attrocity::Coercers::String
5
94
  end
@@ -0,0 +1,10 @@
1
+ module Attrocity
2
+ class Attribute
3
+ attr_reader :name
4
+ attr_accessor :value
5
+
6
+ def initialize(name, value)
7
+ @name, @value = name, value
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,47 @@
1
+ module Attrocity
2
+ class AttributeMethodsBuilder
3
+
4
+ attr_reader :object, :attributes
5
+
6
+ def initialize(object, attributes)
7
+ @object, @attributes = object, attributes
8
+ end
9
+
10
+ def self.for_attribute(obj, attribute)
11
+ self.new(obj, Array(attribute))
12
+ end
13
+
14
+ def self.for_attribute_set(obj, attribute_set)
15
+ self.new(obj, attribute_set.attributes)
16
+ end
17
+
18
+ def build
19
+ attributes.each do |attr|
20
+ define_methods(attr)
21
+ end
22
+ end
23
+
24
+ def define_methods(attribute)
25
+ define_reader(attribute)
26
+ define_writer(attribute)
27
+ define_predicate(attribute)
28
+ end
29
+
30
+ def define_reader(attribute)
31
+ object.define_singleton_method(attribute.name) { attribute.value }
32
+ end
33
+
34
+ def define_writer(attribute)
35
+ object.define_singleton_method("#{attribute.name}=") do |value|
36
+ attribute.value = value
37
+ end
38
+ end
39
+
40
+ def define_predicate(attribute)
41
+ object.define_singleton_method("#{attribute.name}?") do
42
+ # TODO: Would true & attribute.value work better here?
43
+ !!(attribute.value)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ module Attrocity
2
+ class AttributeSet
3
+
4
+ attr_reader :attributes
5
+
6
+ def initialize
7
+ @attributes = Array.new
8
+ end
9
+
10
+ def add(attrs)
11
+ Array(attrs).each { |attr| attributes << attr }
12
+ end
13
+ alias_method :<<, :add
14
+
15
+ def to_h
16
+ Hash.new.tap do |h|
17
+ attributes.each do |attr|
18
+ h[attr.name] = attr.value
19
+ end
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ module Attrocity
2
+ class AttributeTemplate
3
+
4
+ attr_reader :name, :coercer, :mapper
5
+
6
+ def initialize(name, coercer, mapping)
7
+ @name = name
8
+ @coercer = coercer
9
+ @mapper = mapping
10
+ end
11
+
12
+ def to_attribute(data)
13
+ val = ValueExtractor.new(data, mapper: mapper, coercer: coercer).value
14
+ Attribute.new(name, val)
15
+ end
16
+
17
+ def mapper_key_for(key)
18
+ key = key.to_s
19
+ if name.to_s == key || mapper.key.to_s == key
20
+ mapper.key
21
+ else
22
+ nil
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+