contrackt 0.0.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 +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +26 -0
- data/LICENSE.txt +13 -0
- data/README.md +124 -0
- data/contrackt.gemspec +13 -0
- data/lib/contrackt.rb +3 -0
- data/lib/contrackt/base.rb +72 -0
- data/lib/contrackt/props.rb +68 -0
- data/lib/contrackt/version.rb +3 -0
- data/spec/contrackt_spec.rb +213 -0
- metadata +53 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5b24a1a4206a9550aced407dd6a29c7496485d1e
|
4
|
+
data.tar.gz: f346524d7e88b2cd1987dee455dde888e42e3ce3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7329932b9a65f010c19096a241c80077f2ce97b32b49df431791b10a490a30a22fab70a1c299df195ef54067f71c2b4c561a2477503c24f4521838c96bf624a2
|
7
|
+
data.tar.gz: a45b3e9c369ebeb5f3b66c4b801688c8507e9871cfa4e03863272be8dc9874099b8959c024666d40cf13a8821187fda6f927d91293b0e6d57b43152f65d75099
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.2.5)
|
5
|
+
rspec (3.5.0)
|
6
|
+
rspec-core (~> 3.5.0)
|
7
|
+
rspec-expectations (~> 3.5.0)
|
8
|
+
rspec-mocks (~> 3.5.0)
|
9
|
+
rspec-core (3.5.3)
|
10
|
+
rspec-support (~> 3.5.0)
|
11
|
+
rspec-expectations (3.5.0)
|
12
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
13
|
+
rspec-support (~> 3.5.0)
|
14
|
+
rspec-mocks (3.5.0)
|
15
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
16
|
+
rspec-support (~> 3.5.0)
|
17
|
+
rspec-support (3.5.0)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
rspec
|
24
|
+
|
25
|
+
BUNDLED WITH
|
26
|
+
1.12.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2016 Credit Karma
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
Contrackt is a tool to make handling request and response objects more object-oriented.
|
2
|
+
|
3
|
+
In general, Ruby handles these request and response objects mostly using hashes. One would expect to see something along the lines of
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
{
|
7
|
+
body: {
|
8
|
+
"foo" => {
|
9
|
+
"bar" => "baz"
|
10
|
+
}
|
11
|
+
}
|
12
|
+
}
|
13
|
+
```
|
14
|
+
|
15
|
+
So your code might involve accessing variables like `response[:body]['foo']['bar']` (always keeping track of which key is a string and which is a symbol) instead of the more object-oriented `response.body.foo.bar`. Contracts are an attempt to represent these JSON hashes more clearly based on the objects they represent.
|
16
|
+
|
17
|
+
`Contrackt` allows the user to define self-documenting "contracts" that they expect to receive or send based on whatever API tool (raml, swagger, blueprint) they use to specify an API. An example usage is
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
class MyContract < Contrackt::Base
|
21
|
+
required 'foo' => Foo
|
22
|
+
end
|
23
|
+
|
24
|
+
class Foo < Contrackt::Base
|
25
|
+
required 'bar'
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
You can then use those classes to work with the resulting JSON, e.g.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# json_response = { body: { "foo" => { "bar" => "baz" } } }
|
33
|
+
contract = MyContract.new(json_response[:body])
|
34
|
+
contract.foo.bar #=> 'baz'
|
35
|
+
contract.to_hash #=> { "foo" => { "bar" => "baz" } }
|
36
|
+
```
|
37
|
+
|
38
|
+
You can also specify arrays of type:
|
39
|
+
```
|
40
|
+
# json = { albums: [{ name: "21", artist: "Adele" }, { name: "IV", artist: "Led Zeppelin" }] }
|
41
|
+
|
42
|
+
class Album < Contrackt::Base
|
43
|
+
required :name
|
44
|
+
required :artist
|
45
|
+
end
|
46
|
+
|
47
|
+
class AlbumPayload < Contrackt::Base
|
48
|
+
required albums: Album[]
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
The RAML spec provides for the concept of a "discriminator," i.e. a field that indicates the object's type. You can specify a discriminator as follows:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# json = { pets:
|
56
|
+
# [
|
57
|
+
# { type: "Cat", name: "Garfield", owner: "Jon", color: "striped" },
|
58
|
+
# { type: "Dog", name: "Marmaduke", owner: "Phil", breed: "great dane" },
|
59
|
+
# ...
|
60
|
+
# ]
|
61
|
+
# }
|
62
|
+
|
63
|
+
class Pet < Contrackt::Base
|
64
|
+
discriminator :type
|
65
|
+
required :name
|
66
|
+
required :owner
|
67
|
+
end
|
68
|
+
|
69
|
+
class PetsPayload < Contrackt::Base
|
70
|
+
required pets: Pet[]
|
71
|
+
end
|
72
|
+
|
73
|
+
class Cat < Pet
|
74
|
+
required :color
|
75
|
+
end
|
76
|
+
|
77
|
+
class Dog < Pet
|
78
|
+
required :breed
|
79
|
+
end
|
80
|
+
|
81
|
+
```
|
82
|
+
|
83
|
+
You can also use discriminator along with a map if the discriminator doesn't provide the class name:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
# json = { pets:
|
87
|
+
# [
|
88
|
+
# { type: "cat", name: "Garfield", owner: "Jon", color: "striped" },
|
89
|
+
# { type: "dog", name: "Marmaduke", owner: "Phil", breed: "great dane" },
|
90
|
+
# ...
|
91
|
+
# ]
|
92
|
+
# }
|
93
|
+
|
94
|
+
class Pet < Contrackt::Base
|
95
|
+
discriminator :type, 'cat' => Cat, 'dog' => Dog
|
96
|
+
required :name
|
97
|
+
required :owner
|
98
|
+
end
|
99
|
+
|
100
|
+
class PetsPayload < Contrackt::Base
|
101
|
+
required pets: Pet[]
|
102
|
+
end
|
103
|
+
|
104
|
+
class Cat < Pet
|
105
|
+
required :color
|
106
|
+
end
|
107
|
+
|
108
|
+
class Dog < Pet
|
109
|
+
required :breed
|
110
|
+
end
|
111
|
+
|
112
|
+
```
|
113
|
+
|
114
|
+
If you have complicated code, you can write a custom parser, e.g.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
# json = { foo: 'bar' }
|
118
|
+
|
119
|
+
class Thing < Contrackt::Base
|
120
|
+
required(:foo).with_custom_parser { |json| "#{json} (came from key foo)" }
|
121
|
+
end
|
122
|
+
|
123
|
+
Thing.new(json).foo #=> "bar (came from key foo)"
|
124
|
+
```
|
data/contrackt.gemspec
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'contrackt'
|
3
|
+
s.version = '0.0.0'
|
4
|
+
s.date = '2016-10-20'
|
5
|
+
s.summary = "Contrackt: simple Ruby contracts"
|
6
|
+
s.description = "Self-documenting contracts for Ruby"
|
7
|
+
s.authors = ["David Reiman"]
|
8
|
+
s.email = 'david.reiman@creditkarma.com'
|
9
|
+
s.files = `git ls-files`.split($\)
|
10
|
+
s.require_path = 'lib'
|
11
|
+
s.homepage = 'https://github.com/creditkarma/contrackt'
|
12
|
+
s.license = 'Apache 2.0'
|
13
|
+
end
|
data/lib/contrackt.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module Contrackt
|
2
|
+
class Base
|
3
|
+
def self.required(key)
|
4
|
+
define_prop(Props::Required.new(key))
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.optional(key)
|
8
|
+
define_prop(Props::Optional.new(key))
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.discriminator(discriminator, mapping = nil)
|
12
|
+
@discriminator = discriminator
|
13
|
+
@mapping = mapping
|
14
|
+
define_prop(Props::Required.new(discriminator))
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.define_prop(prop)
|
18
|
+
symbol_key = prop.key.to_sym
|
19
|
+
attr_reader symbol_key
|
20
|
+
props[symbol_key] = prop
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.props
|
24
|
+
@props ||= superclass == Contrackt::Base ? {} : superclass.clone_props
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.clone_props
|
28
|
+
props.reduce({}) { |props, kvp| props.merge(kvp[0] => kvp[1].clone) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.[]
|
32
|
+
Collection.new(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.new(json)
|
36
|
+
klass = determine_contract_class(json)
|
37
|
+
klass == self ? super(json) : klass.new(json)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.determine_contract_class(json)
|
41
|
+
if @discriminator
|
42
|
+
class_key = json[@discriminator]
|
43
|
+
(@mapping && @mapping[class_key]) || Object.const_get(class_key)
|
44
|
+
else
|
45
|
+
self
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(json)
|
50
|
+
self.class.props.each do |symbol_key, prop|
|
51
|
+
instance_variable_set "@#{prop.key}", prop.parse(json)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_hash
|
56
|
+
self.class.props.reduce({}) do |hash, array|
|
57
|
+
symbol_key, prop = array
|
58
|
+
hash.merge(prop.hashify(send(symbol_key)))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Collection
|
64
|
+
def initialize(klass)
|
65
|
+
@klass = klass
|
66
|
+
end
|
67
|
+
|
68
|
+
def new(values)
|
69
|
+
values.map {|value| @klass.new(value)}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Contrackt
|
2
|
+
module Props
|
3
|
+
class Prop
|
4
|
+
attr_reader :key
|
5
|
+
def initialize(key)
|
6
|
+
unpack(key)
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse(json)
|
10
|
+
target = json[key]
|
11
|
+
if @custom_parser
|
12
|
+
@custom_parser[target]
|
13
|
+
elsif @klass
|
14
|
+
@klass.new(target)
|
15
|
+
else
|
16
|
+
target
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def hashify(value)
|
21
|
+
{ key => hashify_value(value) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_custom_parser(&block)
|
25
|
+
@custom_parser = block
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def unpack(key)
|
30
|
+
case key
|
31
|
+
when String
|
32
|
+
@key = key
|
33
|
+
when Symbol
|
34
|
+
@key = key
|
35
|
+
when Hash
|
36
|
+
raise ArgumentError, "Prop can only take one key-value pair" unless key.length == 1
|
37
|
+
@key = key.keys[0]
|
38
|
+
@klass = key.values[0]
|
39
|
+
else
|
40
|
+
raise ArgumentError, "Prop can only take a string or a hash"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def hashify_value(value)
|
45
|
+
if (value.is_a? Array)
|
46
|
+
value.map {|value| hashify_value(value)}
|
47
|
+
elsif value.respond_to?(:to_hash)
|
48
|
+
value.to_hash
|
49
|
+
else
|
50
|
+
value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Required < Prop
|
56
|
+
end
|
57
|
+
|
58
|
+
class Optional < Prop
|
59
|
+
def parse(json)
|
60
|
+
json[key].nil? ? nil : super(json)
|
61
|
+
end
|
62
|
+
|
63
|
+
def hashify(value)
|
64
|
+
value.nil? ? {} : super(value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require_relative '../lib/contrackt'
|
2
|
+
|
3
|
+
describe Contrackt::Base do
|
4
|
+
before(:all) do
|
5
|
+
class MyContract < Contrackt::Base
|
6
|
+
required 'foo'
|
7
|
+
end
|
8
|
+
|
9
|
+
class Foo < Contrackt::Base
|
10
|
+
required 'bar'
|
11
|
+
end
|
12
|
+
|
13
|
+
class ComposedContract < Contrackt::Base
|
14
|
+
required 'foo' => Foo
|
15
|
+
end
|
16
|
+
|
17
|
+
class ComposedWithArray < Contrackt::Base
|
18
|
+
required 'foos' => Foo[]
|
19
|
+
end
|
20
|
+
|
21
|
+
class ComposedWithOptional < Contrackt::Base
|
22
|
+
required 'foo' => Foo
|
23
|
+
optional 'additionalStuff' => Foo
|
24
|
+
end
|
25
|
+
|
26
|
+
class WithCustomParser < Contrackt::Base
|
27
|
+
required('foo').with_custom_parser { |foo| Foo.new(foo.merge('bar' => foo['bar'] + " (this was the bar)")) }
|
28
|
+
end
|
29
|
+
|
30
|
+
@simple_payload_hash = { "foo" => "bar" }
|
31
|
+
@complex_payload_hash = { "foo" => { "bar" => "baz" } }
|
32
|
+
@array_payload_hash = { "foos" => [{ "bar" => "baz" }, { "bar" => "qux" }] }
|
33
|
+
@complex_with_optional_hash = @complex_payload_hash.merge({ "additionalStuff" => { "bar" => "and more"}})
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can be inherited" do
|
37
|
+
expect(true).to be true # if we get here, the inheritance(s) above didn't throw an error
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when inherited" do
|
41
|
+
it "should be able to be constructed with a json payload" do
|
42
|
+
expect { MyContract.new(@simple_payload_hash) }.not_to raise_exception
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should be able to reproduce the original json from which it was constructed" do
|
46
|
+
contract = MyContract.new(@simple_payload_hash)
|
47
|
+
expect(contract.to_hash).to eq @simple_payload_hash
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should provide a getter for the key that is defined in inheritor::required" do
|
51
|
+
contract = MyContract.new(@simple_payload_hash)
|
52
|
+
expect(contract.foo).to eq @simple_payload_hash['foo']
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "with subcontract" do
|
57
|
+
it "should allow a hash like 'foo' => Foo in the required call" do
|
58
|
+
expect { ComposedContract.new(@complex_payload_hash) }.not_to raise_exception
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should properly convert its subcontract values into contract objects" do
|
62
|
+
contract = ComposedContract.new(@complex_payload_hash)
|
63
|
+
expect(contract.foo).to be_a Foo
|
64
|
+
expect(contract.foo.bar).to eq 'baz'
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should be able to reconstruct the hash from which it was constructed" do
|
68
|
+
expect(ComposedContract.new(@complex_payload_hash).to_hash).to eq @complex_payload_hash
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "with array subcontract" do
|
73
|
+
it "should allow a hash like 'foo' => Foo[] in the required call" do
|
74
|
+
expect { ComposedWithArray.new(@array_payload_hash) }.not_to raise_exception
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should properly map its subcontract values into contract objects" do
|
78
|
+
contract = ComposedWithArray.new(@array_payload_hash)
|
79
|
+
expect(contract.foos.all? { |foo| foo.is_a? Foo }).to be true
|
80
|
+
expect(contract.foos.map(&:bar)).to eq ['baz', 'qux']
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should be able to reconstruct the hash from which it was constructed" do
|
84
|
+
expect(ComposedWithArray.new(@array_payload_hash).to_hash).to eq @array_payload_hash
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "with optional props" do
|
89
|
+
context "missing in the payload" do
|
90
|
+
it "should not throw an exception if the properties are missing" do
|
91
|
+
expect { ComposedWithOptional.new(@complex_payload_hash) }.not_to raise_exception
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should still allow those optional properties to be accessed, just with nil value" do
|
95
|
+
expect(ComposedWithOptional.new(@complex_payload_hash).additionalStuff).to be nil
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should ignore those optional properties when reconstructing the hash" do
|
99
|
+
expect(ComposedWithOptional.new(@complex_payload_hash).to_hash).to eq @complex_payload_hash
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "present in the payload" do
|
104
|
+
it "should parse optional properties properly when they are present" do
|
105
|
+
expect(ComposedWithOptional.new(@complex_with_optional_hash).additionalStuff).to be_a Foo
|
106
|
+
expect(ComposedWithOptional.new(@complex_with_optional_hash).additionalStuff.bar).to eq "and more"
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should ignore those optional properties when reconstructing the hash" do
|
110
|
+
expect(ComposedWithOptional.new(@complex_payload_hash).to_hash).to eq @complex_payload_hash
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "with custom parser" do
|
116
|
+
it "uses the custom parser to build new objects" do
|
117
|
+
expect(WithCustomParser.new(@complex_payload_hash).foo.bar).to eq "baz (this was the bar)"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "discriminator" do
|
122
|
+
before(:all) do
|
123
|
+
class Card < Contrackt::Base
|
124
|
+
required :owner
|
125
|
+
discriminator :type
|
126
|
+
end
|
127
|
+
|
128
|
+
class BusinessCard < Card
|
129
|
+
required :phone
|
130
|
+
end
|
131
|
+
|
132
|
+
class CreditCard < Card
|
133
|
+
required :account
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context "not a collection" do
|
138
|
+
before(:all) do
|
139
|
+
@discriminator_hash = { card:
|
140
|
+
{ type: "BusinessCard", owner: "Jane Doe", phone: "(555) 555-5555" }
|
141
|
+
}
|
142
|
+
|
143
|
+
class CardPayload < Contrackt::Base
|
144
|
+
required card: Card
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
it "properly applies the discriminator" do
|
149
|
+
contract = CardPayload.new(@discriminator_hash)
|
150
|
+
expect(contract.card.class).to be BusinessCard
|
151
|
+
expect(contract.card.owner).to eq "Jane Doe"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context "collection" do
|
156
|
+
before(:all) do
|
157
|
+
@discriminator_hash = {
|
158
|
+
cards: [
|
159
|
+
{ type: "BusinessCard", owner: "Jane Doe", phone: "(555) 555-5555" },
|
160
|
+
{ type: "CreditCard", owner: "Jane Doe", account: "1234-5678-9000-0000" }
|
161
|
+
]
|
162
|
+
}
|
163
|
+
|
164
|
+
class CardsPayload < Contrackt::Base
|
165
|
+
required cards: Card[]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
it "properly applies the discriminator" do
|
170
|
+
contract = CardsPayload.new(@discriminator_hash)
|
171
|
+
expect(contract.cards.first.class).to be BusinessCard
|
172
|
+
expect(contract.cards.first.owner).to eq "Jane Doe"
|
173
|
+
expect(contract.cards.first.phone).to eq "(555) 555-5555"
|
174
|
+
expect(contract.cards.last.class).to be CreditCard
|
175
|
+
expect(contract.cards.last.owner).to eq "Jane Doe"
|
176
|
+
expect(contract.cards.last.account).to eq "1234-5678-9000-0000"
|
177
|
+
end
|
178
|
+
|
179
|
+
it "reproduces the original hash via to_hash" do
|
180
|
+
expect(CardsPayload.new(@discriminator_hash).to_hash).to eq @discriminator_hash
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
context "custom discriminator handling" do
|
185
|
+
before(:all) do
|
186
|
+
class CustomCard < Contrackt::Base
|
187
|
+
required :owner
|
188
|
+
discriminator :type, 'business' => BusinessCard, 'credit' => CreditCard
|
189
|
+
end
|
190
|
+
|
191
|
+
class CustomCardsPayload < Contrackt::Base
|
192
|
+
required cards: CustomCard[]
|
193
|
+
end
|
194
|
+
|
195
|
+
@discriminator_hash = {
|
196
|
+
cards: [
|
197
|
+
{ type: "business", owner: "Jane Doe", phone: "(555) 555-5555" },
|
198
|
+
{ type: "credit", owner: "Jane Doe", account: "1234-5678-9000-0000" }
|
199
|
+
]
|
200
|
+
}
|
201
|
+
end
|
202
|
+
it "properly applies the discriminator" do
|
203
|
+
contract = CustomCardsPayload.new(@discriminator_hash)
|
204
|
+
expect(contract.cards.first.class).to be BusinessCard
|
205
|
+
expect(contract.cards.first.owner).to eq "Jane Doe"
|
206
|
+
expect(contract.cards.first.phone).to eq "(555) 555-5555"
|
207
|
+
expect(contract.cards.last.class).to be CreditCard
|
208
|
+
expect(contract.cards.last.owner).to eq "Jane Doe"
|
209
|
+
expect(contract.cards.last.account).to eq "1234-5678-9000-0000"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: contrackt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Reiman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-20 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Self-documenting contracts for Ruby
|
14
|
+
email: david.reiman@creditkarma.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- Gemfile
|
20
|
+
- Gemfile.lock
|
21
|
+
- LICENSE.txt
|
22
|
+
- README.md
|
23
|
+
- contrackt.gemspec
|
24
|
+
- lib/contrackt.rb
|
25
|
+
- lib/contrackt/base.rb
|
26
|
+
- lib/contrackt/props.rb
|
27
|
+
- lib/contrackt/version.rb
|
28
|
+
- spec/contrackt_spec.rb
|
29
|
+
homepage: https://github.com/creditkarma/contrackt
|
30
|
+
licenses:
|
31
|
+
- Apache 2.0
|
32
|
+
metadata: {}
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 2.4.6
|
50
|
+
signing_key:
|
51
|
+
specification_version: 4
|
52
|
+
summary: 'Contrackt: simple Ruby contracts'
|
53
|
+
test_files: []
|