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.
- checksums.yaml +4 -4
- data/.gitignore +30 -10
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile.lock +37 -0
- data/LICENSE +22 -0
- data/README.md +32 -2
- data/Rakefile +14 -0
- data/TODO.md +36 -0
- data/attrocity.gemspec +5 -2
- data/lib/attrocity.rb +91 -2
- data/lib/attrocity/attributes/attribute.rb +10 -0
- data/lib/attrocity/attributes/attribute_methods_builder.rb +47 -0
- data/lib/attrocity/attributes/attribute_set.rb +24 -0
- data/lib/attrocity/attributes/attribute_template.rb +28 -0
- data/lib/attrocity/attributes/attribute_template_set.rb +16 -0
- data/lib/attrocity/attributes/attributes_hash.rb +9 -0
- data/lib/attrocity/attributes/model_attribute.rb +14 -0
- data/lib/attrocity/attributes/model_attribute_set.rb +6 -0
- data/lib/attrocity/builders/model_builder.rb +60 -0
- data/lib/attrocity/builders/object_extension_builder.rb +18 -0
- data/lib/attrocity/coercer_registry.rb +38 -0
- data/lib/attrocity/coercers/boolean.rb +12 -0
- data/lib/attrocity/coercers/integer.rb +13 -0
- data/lib/attrocity/coercers/string.rb +14 -0
- data/lib/attrocity/mappers/key_mapper.rb +14 -0
- data/lib/attrocity/model.rb +10 -0
- data/lib/attrocity/value_extractor.rb +23 -0
- data/lib/attrocity/version.rb +1 -1
- data/notes.md +253 -0
- data/spec/attrocity/attributes/attribute_methods_builder_spec.rb +57 -0
- data/spec/attrocity/attributes/attribute_set_spec.rb +24 -0
- data/spec/attrocity/attributes/attribute_spec.rb +4 -0
- data/spec/attrocity/attributes/attribute_template_set_spec.rb +6 -0
- data/spec/attrocity/attributes/attribute_template_spec.rb +25 -0
- data/spec/attrocity/attributes/model_attribute_spec.rb +26 -0
- data/spec/attrocity/attrocity_spec.rb +83 -0
- data/spec/attrocity/coercer_registry_spec.rb +14 -0
- data/spec/attrocity/coercers/boolean_spec.rb +32 -0
- data/spec/attrocity/coercers/coercer_with_args_spec.rb +9 -0
- data/spec/attrocity/coercers/integer_spec.rb +25 -0
- data/spec/attrocity/coercers/string_spec.rb +13 -0
- data/spec/attrocity/default_value_spec.rb +22 -0
- data/spec/attrocity/mappers/key_mapper_spec.rb +22 -0
- data/spec/attrocity/mapping_spec.rb +47 -0
- data/spec/attrocity/model_spec.rb +34 -0
- data/spec/attrocity/object_extension_spec.rb +53 -0
- data/spec/attrocity/value_extractor_spec.rb +23 -0
- data/spec/spec_helper.rb +93 -0
- data/spec/support/examples.rb +79 -0
- metadata +99 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ecd334914b12e956f87212e8a6ea99334e85176
|
4
|
+
data.tar.gz: 8f46fc6967abe69da2caf3bac80e7bb9595a45e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: faf2df44f1fe13921a46eecdab94347bf021c5ed220d8716106e7011b591633c475692a54fa572436efe7f766e6c991e58dda7633c1dfb39af67eb1a92bcc6cc
|
7
|
+
data.tar.gz: e36f0c71909ffea9358b005df5509704f596c59c9f657453c6bee7c02d443e84bb5b8bf55ed34f4874b0ca92f12199e9abe89350545c68c3d38f26012dfc6def
|
data/.gitignore
CHANGED
@@ -1,14 +1,34 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
/_yardoc/
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
5
4
|
/coverage/
|
6
|
-
/
|
5
|
+
/InstalledFiles
|
7
6
|
/pkg/
|
8
7
|
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
9
10
|
/tmp/
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
data/.travis.yml
ADDED
data/Gemfile.lock
ADDED
@@ -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
|
-
|
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
|
data/attrocity.gemspec
CHANGED
@@ -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.
|
22
|
-
|
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
|
data/lib/attrocity.rb
CHANGED
@@ -1,5 +1,94 @@
|
|
1
|
-
|
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
|
-
|
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,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
|
+
|