attrocity 0.0.1 → 0.1.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 (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
+