objection 1.3.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/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +102 -0
- data/Rakefile +12 -0
- data/lib/objection/version.rb +3 -0
- data/lib/objection.rb +165 -0
- data/objection.gemspec +26 -0
- data/spec/objection/objection_spec.rb +290 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/demo_basic.rb +2 -0
- data/spec/support/demo_nested.rb +16 -0
- data/spec/support/demo_optionals.rb +7 -0
- data/spec/support/demo_requires.rb +7 -0
- data/spec/support/demo_typed.rb +9 -0
- metadata +137 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a390d9c4658fb60f221e09685282b000ff1d3657
|
4
|
+
data.tar.gz: 64dc47f4902018a6e69380aca9a90047b84292eb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2958a1ac53269c34e2d5b7f1ada89821a3bccbfec413926689bf98c505df6c9f0f14de881f8ca628100b46376318f2a549bc77a878613bc7ab377cf51abe3278
|
7
|
+
data.tar.gz: b312dd26bc3c3f873d3e6d04fb9a0e61475eb2dd418eba7f8516b66dcf747f1a522f3b32a2b3350dd9f53c16089f16c91e1ce5058366654c88c377e76a2523d4
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Ewout Quax
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Objection
|
2
|
+
|
3
|
+
Build a contract to your interfaces, with predefined and required fields
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'objection'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install objection
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Build your own class, and let it inherit from Objection::Base.
|
22
|
+
Set required fields with the requires-command, and optional fields with the optional-command
|
23
|
+
```ruby
|
24
|
+
class MyContract < Objection::Base
|
25
|
+
requires :required_1, :required_2
|
26
|
+
optionals :optional_1, :optional_2
|
27
|
+
end
|
28
|
+
|
29
|
+
contract = MyContract.new(required_1: 'value', required_2: 'other-value', optional_1: 'more info')
|
30
|
+
contract.optional_2 = 'other info'
|
31
|
+
|
32
|
+
contract.required_1 => 'value'
|
33
|
+
contract.optional_2 => 'other info'
|
34
|
+
```
|
35
|
+
|
36
|
+
The gem will protect you from using unknown fields, and from suppling blank values for the required fields
|
37
|
+
|
38
|
+
## Fields with type
|
39
|
+
|
40
|
+
Declare via:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
class MyContract < Objection::Base
|
44
|
+
requires :required_1, :required_2
|
45
|
+
optionals :optional_1, :optional_2
|
46
|
+
|
47
|
+
input_types(
|
48
|
+
required_1: Fixnum,
|
49
|
+
required_2: String,
|
50
|
+
optional_1: Float
|
51
|
+
)
|
52
|
+
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
## Nested structures
|
57
|
+
|
58
|
+
Consider the following classes:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
class DemoNestedCar < Objection::Base
|
62
|
+
requires :car_model
|
63
|
+
optionals :licence_plate
|
64
|
+
input_types(
|
65
|
+
car_model: String
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
class DemoNestedBooking < Objection::Base
|
70
|
+
requires :booking_id, :booking_date
|
71
|
+
optionals :car
|
72
|
+
input_types(
|
73
|
+
booking_date: Date,
|
74
|
+
car: DemoNestedCar,
|
75
|
+
)
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
When initializing the class DemoNestedBooking, and suppliing a hash for field 'car', the gem will cast the type.
|
80
|
+
The class 'DemoNestedCar' will be instantiated, with the hash of 'car' as input.
|
81
|
+
After instantiating, the following will be true:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
obj.car.is_a?(DemoNestedCar)
|
85
|
+
obj.car.car_model == "<value given via ['car']['car_model']>"
|
86
|
+
```
|
87
|
+
|
88
|
+
This also works for arrays. When an array is given where an object is suspected, then each item within the array will be
|
89
|
+
convered into the declared object.
|
90
|
+
|
91
|
+
## To hash
|
92
|
+
|
93
|
+
For better connection with other services, objection can convert its values to an hash, with the `to_hash` function.
|
94
|
+
This operation with recursivly with nested objects and arrays of objects.
|
95
|
+
|
96
|
+
## Contributing
|
97
|
+
|
98
|
+
1. Fork it ( https://github.com/[my-github-username]/objection/fork )
|
99
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
100
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
101
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
102
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/lib/objection.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
require "objection/version"
|
2
|
+
|
3
|
+
module Objection
|
4
|
+
class Base
|
5
|
+
class ObjectionException < Exception; end
|
6
|
+
class RequiredFieldMissing < ObjectionException; end
|
7
|
+
class RequiredFieldEmpty < ObjectionException; end
|
8
|
+
class RequiredFieldMadeEmpty < ObjectionException; end
|
9
|
+
class UnknownFieldGiven < ObjectionException; end
|
10
|
+
|
11
|
+
def self.requires(*args)
|
12
|
+
@required_fields = args
|
13
|
+
end
|
14
|
+
def self.optionals(*args)
|
15
|
+
@optional_fields = args
|
16
|
+
end
|
17
|
+
def self.input_types(*args)
|
18
|
+
@input_types = args[0]
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(*args)
|
23
|
+
@values = normalize_input(*args)
|
24
|
+
|
25
|
+
check_values!
|
26
|
+
define_accessors
|
27
|
+
apply_structures!
|
28
|
+
check_types!
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_hash
|
32
|
+
hash = {}
|
33
|
+
@values.each_pair do |key, value|
|
34
|
+
if value.is_a?(Array)
|
35
|
+
hash[key] = value.inject([]) do |items, item|
|
36
|
+
items << item.to_hash
|
37
|
+
end
|
38
|
+
elsif value.respond_to?(:to_hash)
|
39
|
+
hash[key] = value.to_hash
|
40
|
+
else
|
41
|
+
hash[key] = value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
hash
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def define_accessors
|
49
|
+
known_fields.each{|field| define_getter(field)}
|
50
|
+
optional_fields.each{|field| define_optional_setter(field)}
|
51
|
+
required_fields.each{|field| define_required_setter(field)}
|
52
|
+
end
|
53
|
+
|
54
|
+
def check_values!
|
55
|
+
raise UnknownFieldGiven, unknown_fields.join(', ') if unknown_fields_present?
|
56
|
+
raise RequiredFieldMissing, missing_required_fields.join(', ') if missing_required_fields?
|
57
|
+
raise RequiredFieldEmpty, blank_required_fields.join(', ') if blank_required_fields?
|
58
|
+
end
|
59
|
+
|
60
|
+
def apply_structures!
|
61
|
+
input_types.each do |field, type|
|
62
|
+
value = self.send(field)
|
63
|
+
if !value.is_a?(type) && value.is_a?(Hash)
|
64
|
+
@values[field] = type.new(value)
|
65
|
+
end
|
66
|
+
if !value.is_a?(type) && value.is_a?(Array)
|
67
|
+
values = []
|
68
|
+
value.each do |item|
|
69
|
+
values << type.new(item)
|
70
|
+
end
|
71
|
+
@values[field] = values
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def check_types!
|
77
|
+
input_types.each do |field, type|
|
78
|
+
value = self.send(field)
|
79
|
+
unless value.nil?
|
80
|
+
if !(value.is_a?(type) || value.is_a?(Array))
|
81
|
+
raise TypeError, "#{field} has the wrong type; #{type} was expected, but got #{value.class}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def define_getter(fieldname)
|
88
|
+
self.class.send(:define_method, "#{fieldname}") do
|
89
|
+
@values[fieldname]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def define_optional_setter(fieldname)
|
94
|
+
self.class.send(:define_method, "#{fieldname}=") do |arg|
|
95
|
+
@values[fieldname] = arg
|
96
|
+
check_types!
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def define_required_setter(fieldname)
|
101
|
+
self.class.send(:define_method, "#{fieldname}=") do |arg|
|
102
|
+
raise RequiredFieldMadeEmpty, fieldname if arg.nil? || arg == ''
|
103
|
+
@values[fieldname] = arg
|
104
|
+
check_types!
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def normalize_input(*args)
|
109
|
+
if args.any? && args[0].is_a?(Hash)
|
110
|
+
args[0].inject({}) do |out, (key, value)|
|
111
|
+
out.merge(key.to_sym => value)
|
112
|
+
end
|
113
|
+
else
|
114
|
+
{}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def known_fields
|
119
|
+
required_fields + optional_fields
|
120
|
+
end
|
121
|
+
|
122
|
+
def unknown_fields_present?
|
123
|
+
unknown_fields.any?
|
124
|
+
end
|
125
|
+
|
126
|
+
def unknown_fields
|
127
|
+
present_fields - known_fields
|
128
|
+
end
|
129
|
+
|
130
|
+
def blank_required_fields?
|
131
|
+
blank_required_fields.any?
|
132
|
+
end
|
133
|
+
|
134
|
+
def blank_required_fields
|
135
|
+
required_fields.select do |field|
|
136
|
+
value = @values[field]
|
137
|
+
value == '' || value.nil?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def missing_required_fields?
|
142
|
+
missing_required_fields.any?
|
143
|
+
end
|
144
|
+
|
145
|
+
def missing_required_fields
|
146
|
+
required_fields - present_fields
|
147
|
+
end
|
148
|
+
|
149
|
+
def present_fields
|
150
|
+
@values.keys
|
151
|
+
end
|
152
|
+
|
153
|
+
def required_fields
|
154
|
+
self.class.instance_variable_get('@required_fields') || []
|
155
|
+
end
|
156
|
+
|
157
|
+
def optional_fields
|
158
|
+
self.class.instance_variable_get('@optional_fields') || []
|
159
|
+
end
|
160
|
+
|
161
|
+
def input_types
|
162
|
+
self.class.instance_variable_get('@input_types') || {}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/objection.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'objection/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "objection"
|
8
|
+
spec.version = Objection::VERSION
|
9
|
+
spec.authors = ["Ewout Quax"]
|
10
|
+
spec.email = ["ewout.quax@quicknet.nl"]
|
11
|
+
spec.summary = %q{Declare a predefined contract for use with services}
|
12
|
+
spec.description = %q{A predefined contract, with required and optional fields, for use with services.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "pry"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "simplecov"
|
26
|
+
end
|
@@ -0,0 +1,290 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../support/demo_basic'
|
3
|
+
require_relative '../support/demo_requires'
|
4
|
+
require_relative '../support/demo_optionals'
|
5
|
+
require_relative '../support/demo_typed'
|
6
|
+
require_relative '../support/demo_nested'
|
7
|
+
|
8
|
+
describe Objection do
|
9
|
+
before do
|
10
|
+
Objection::Base.instance_variable_set('@required_fields', nil)
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'base' do
|
14
|
+
it 'can instantiate the class' do
|
15
|
+
expect(Objection::Base.new).to be_kind_of(Objection::Base)
|
16
|
+
end
|
17
|
+
it 'can find the requires-function' do
|
18
|
+
expect(Objection::Base.respond_to?(:requires)).to eq(true)
|
19
|
+
end
|
20
|
+
it 'can find the optionals-function' do
|
21
|
+
expect(Objection::Base.respond_to?(:optionals)).to eq(true)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'required_fields' do
|
26
|
+
let(:object) { build_object_for_required_fields }
|
27
|
+
|
28
|
+
it 'can be defined and fetched' do
|
29
|
+
obj = DemoRequires.new(required_1: 'dummy', required_2: 'dummy')
|
30
|
+
obj_more = DemoRequiresMore.new(required_3: 'dummy', required_4: 'dummy')
|
31
|
+
|
32
|
+
expect(obj.send(:required_fields)).to eq([:required_1, :required_2])
|
33
|
+
expect(obj_more.send(:required_fields)).to eq([:required_3, :required_4])
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'builds a list of missing required fields' do
|
37
|
+
expect(object).to receive(:present_fields).and_return([:field_1])
|
38
|
+
result = object.send(:missing_required_fields)
|
39
|
+
expect(result).to eq([:field_2])
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'can be changed after initialization' do
|
43
|
+
object.field_1 = 'new value'
|
44
|
+
expect(object.field_1).to eq('new value')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'raises an error, when a required field is blanked' do
|
48
|
+
expect{object.field_1 = ''}.to raise_error(Objection::Base::RequiredFieldMadeEmpty, 'field_1')
|
49
|
+
expect{object.field_2 = nil}.to raise_error(Objection::Base::RequiredFieldMadeEmpty, 'field_2')
|
50
|
+
end
|
51
|
+
|
52
|
+
def build_object_for_required_fields
|
53
|
+
obj = Objection::Base.new
|
54
|
+
obj.class.instance_variable_set('@required_fields', [:field_1, :field_2])
|
55
|
+
obj.send(:define_accessors)
|
56
|
+
obj
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'optional_fields' do
|
61
|
+
it 'can be defined and fetched' do
|
62
|
+
obj = DemoOptionals.new
|
63
|
+
obj_more = DemoOptionalsMore.new
|
64
|
+
|
65
|
+
expect(obj).to be_kind_of(DemoOptionals)
|
66
|
+
expect(obj).to be_kind_of(Objection::Base)
|
67
|
+
expect(obj.send(:optional_fields)).to eq([:optional_1, :optional_2])
|
68
|
+
expect(obj_more.send(:optional_fields)).to eq([:optional_3, :optional_4])
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'can be changed after initialization' do
|
72
|
+
obj = DemoOptionals.new
|
73
|
+
obj.optional_1 = 'value_1'
|
74
|
+
expect(obj.optional_1).to eq('value_1')
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'raises an error, if an unknown field is given' do
|
78
|
+
obj = DemoOptionals.new
|
79
|
+
expect{obj.unknown_field = 'mwhoehaha'}.to raise_error(NoMethodError)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'typed_input' do
|
84
|
+
let(:obj) { build_object_for_known_fields }
|
85
|
+
|
86
|
+
it 'can be initialized' do
|
87
|
+
expect(obj).to be_kind_of(DemoTyped)
|
88
|
+
end
|
89
|
+
it 'raises a type error, when a required field is populated incorrectly' do
|
90
|
+
expect{DemoTyped.new(required_1: 1.1, required_2: 'input')}.to raise_error(TypeError, 'required_1 has the wrong type; Fixnum was expected, but got Float')
|
91
|
+
end
|
92
|
+
it 'raises a type error, when an optional field is populated incorrectly' do
|
93
|
+
expect{DemoTyped.new(required_1: 1, required_2: 'input', optional_1: 'incorrect')}.to raise_error(TypeError, 'optional_1 has the wrong type; Float was expected, but got String')
|
94
|
+
end
|
95
|
+
it 'can change the type of a field without type' do
|
96
|
+
obj.optional_2 = 'String'
|
97
|
+
obj.optional_2 = 1.1
|
98
|
+
expect(obj.optional_2).to eq(1.1)
|
99
|
+
end
|
100
|
+
it 'raises a type error, when a required field is populated incorrectly' do
|
101
|
+
expect{obj.required_1 = 'incorrect'}.to raise_error(TypeError, 'required_1 has the wrong type; Fixnum was expected, but got String')
|
102
|
+
end
|
103
|
+
it 'raises a type error, when an optional field is populated incorrectly' do
|
104
|
+
expect{obj.optional_1 = 'incorrect'}.to raise_error(TypeError, 'optional_1 has the wrong type; Float was expected, but got String')
|
105
|
+
end
|
106
|
+
it 'raises nothing, when an optional field is nilled' do
|
107
|
+
obj.optional_1 = nil
|
108
|
+
expect(obj.optional_1).to be_nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_object_for_known_fields
|
112
|
+
DemoTyped.new(required_1: 1, required_2: 'input', optional_1: 1.1)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'nested_input' do
|
117
|
+
it 'initializes the object, when the optional structures is not used' do
|
118
|
+
obj = DemoNestedBooking.new(booking_id: 1, booking_date: Date.today)
|
119
|
+
expect(obj).to be_kind_of(DemoNestedBooking)
|
120
|
+
expect(obj.booking_date).to eq(Date.today)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'converts the hash to the given object' do
|
124
|
+
obj = DemoNestedBooking.new(booking_id: 1, booking_date: Date.today, car: {car_model: 'Opel'})
|
125
|
+
expect(obj).to be_kind_of(DemoNestedBooking)
|
126
|
+
expect(obj.booking_date).to eq(Date.today)
|
127
|
+
expect(obj.car).to be_kind_of(DemoNestedCar)
|
128
|
+
expect(obj.car.car_model).to eq('Opel')
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'converts each item in an array to the given object' do
|
132
|
+
obj = DemoNestedBooking.new(booking_id: 1, booking_date: Date.today, car: [{car_model: 'Opel'}])
|
133
|
+
expect(obj).to be_kind_of(DemoNestedBooking)
|
134
|
+
expect(obj.booking_date).to eq(Date.today)
|
135
|
+
expect(obj.car).to be_kind_of(Array)
|
136
|
+
expect(obj.car.first).to be_kind_of(DemoNestedCar)
|
137
|
+
expect(obj.car.first.car_model).to eq('Opel')
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'initialize' do
|
142
|
+
it 'can initialize a basic object' do
|
143
|
+
obj = DemoBasic.new
|
144
|
+
|
145
|
+
expect(obj).to be_kind_of(DemoBasic)
|
146
|
+
expect(obj).to be_kind_of(Objection::Base)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'optional fields can be queried' do
|
150
|
+
obj = DemoOptionals.new(optional_1: 'value_1', optional_2: 'value_2')
|
151
|
+
expect(obj.optional_1).to eq('value_1')
|
152
|
+
expect(obj.optional_2).to eq('value_2')
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'required fields can be queried' do
|
156
|
+
obj = DemoRequires.new(required_1: 'value_1', required_2: 'value_2')
|
157
|
+
expect(obj.required_1).to eq('value_1')
|
158
|
+
expect(obj.required_2).to eq('value_2')
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'raises an error. when unknown fields are supplied' do
|
162
|
+
expect{DemoOptionals.new(unknown_field: 'dummy')}.to raise_error(Objection::Base::UnknownFieldGiven, 'unknown_field')
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'raises an error. when not all required fields are supplied' do
|
166
|
+
expect{DemoRequires.new(required_2: 'dummy')}.to raise_error(Objection::Base::RequiredFieldMissing, 'required_1')
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'raises an error, when a required field is blank' do
|
170
|
+
expect{DemoRequires.new(required_1: 'value', required_2: '')}.to raise_error(Objection::Base::RequiredFieldEmpty, 'required_2')
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'raises an error, when a required field is nil' do
|
174
|
+
expect{DemoRequires.new(required_1: nil, required_2: 'value')}.to raise_error(Objection::Base::RequiredFieldEmpty, 'required_1')
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'normalize input' do
|
179
|
+
let(:object) { Objection::Base.new }
|
180
|
+
|
181
|
+
it 'returns an empty hash, when no arguments are given' do
|
182
|
+
hash = object.send(:normalize_input)
|
183
|
+
expect(hash).to eq({})
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'returns a symbolized hash, when a symbolized hash is given' do
|
187
|
+
input = {field_1: '1', field_2: '2'}
|
188
|
+
hash = object.send(:normalize_input, input)
|
189
|
+
expect(hash).to eq(input)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'returns a symbolized hash, when one symbolic is given' do
|
193
|
+
expected = {field_1: '1'}
|
194
|
+
hash = object.send(:normalize_input, field_1: '1')
|
195
|
+
expect(hash).to eq(expected)
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'returns a symbolized hash, when multiple symbolics are given' do
|
199
|
+
expected = {field_1: '1', field_2: '2'}
|
200
|
+
hash = object.send(:normalize_input, field_1: '1', field_2: '2')
|
201
|
+
expect(hash).to eq(expected)
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'returns a symbolized hash, when one string is given' do
|
205
|
+
expected = {field_1: '1'}
|
206
|
+
hash = object.send(:normalize_input, 'field_1' => '1')
|
207
|
+
expect(hash).to eq(expected)
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'returns a symbolized hash, when a stringified hash is given' do
|
211
|
+
input = {'field_1' => '1', 'field_2' => '2'}
|
212
|
+
expected = {field_1: '1', field_2: '2'}
|
213
|
+
hash = object.send(:normalize_input, input)
|
214
|
+
expect(hash).to eq(expected)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context '(un)known fields' do
|
219
|
+
let(:obj) { build_object_for_known_fields }
|
220
|
+
|
221
|
+
it 'builds a list from the required and optional fields' do
|
222
|
+
expect(obj.send(:known_fields)).to eq([:field_1, :field_2])
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'builds a list of the unknown fields' do
|
226
|
+
expect(obj).to receive(:present_fields).and_return([:field_1, :field_2, :fields_3])
|
227
|
+
expect(obj.send(:unknown_fields)).to eq([:fields_3])
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'return true, when unkown fields are present' do
|
231
|
+
allow(obj).to receive(:unknown_fields).and_return([:field_1])
|
232
|
+
expect(obj.send(:unknown_fields_present?)).to eq(true)
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'return false, when unkown fields are absent' do
|
236
|
+
allow(obj).to receive(:unknown_fields).and_return([])
|
237
|
+
expect(obj.send(:unknown_fields_present?)).to eq(false)
|
238
|
+
end
|
239
|
+
|
240
|
+
def build_object_for_known_fields
|
241
|
+
obj = Objection::Base.new
|
242
|
+
obj.class.instance_variable_set('@required_fields', [:field_1])
|
243
|
+
obj.class.instance_variable_set('@optional_fields', [:field_2])
|
244
|
+
obj
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context 'define accessors' do
|
249
|
+
let(:object) { build_object_for_accessors }
|
250
|
+
|
251
|
+
before do
|
252
|
+
object.send(:define_accessors)
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'object responds to getter for required field' do
|
256
|
+
expect(object.respond_to?(:required_1)).to eq(true)
|
257
|
+
end
|
258
|
+
it 'object responds to setter for required field' do
|
259
|
+
expect(object.respond_to?(:required_1=)).to eq(true)
|
260
|
+
end
|
261
|
+
it 'object responds to getter for optional field' do
|
262
|
+
expect(object.respond_to?(:optional_1)).to eq(true)
|
263
|
+
end
|
264
|
+
it 'object responds to setter for optional field' do
|
265
|
+
expect(object.respond_to?(:optional_1=)).to eq(true)
|
266
|
+
end
|
267
|
+
|
268
|
+
def build_object_for_accessors
|
269
|
+
obj = Objection::Base.new
|
270
|
+
allow(obj).to receive(:required_fields).and_return([:required_1])
|
271
|
+
allow(obj).to receive(:optional_fields).and_return([:optional_1])
|
272
|
+
obj
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
context 'to hash' do
|
277
|
+
it 'converts a single layered object to hash' do
|
278
|
+
obj = DemoNestedBooking.new(booking_id: 1, booking_date: Date.today)
|
279
|
+
expect(obj.to_hash).to eq({booking_id: 1, booking_date: Date.today})
|
280
|
+
end
|
281
|
+
it 'converts an object with converted hash into a hash' do
|
282
|
+
obj = DemoNestedBooking.new(booking_id: 1, booking_date: Date.today, car: {car_model: 'Opel'})
|
283
|
+
expect(obj.to_hash).to eq({booking_id: 1, booking_date: Date.today, car: {car_model: 'Opel'}})
|
284
|
+
end
|
285
|
+
it 'converts an object with converted array into a hash' do
|
286
|
+
obj = DemoNestedBooking.new(booking_id: 1, booking_date: Date.today, car: [{car_model: 'Opel'}])
|
287
|
+
expect(obj.to_hash).to eq({booking_id: 1, booking_date: Date.today, car: [{car_model: 'Opel'}]})
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class DemoNestedCar < Objection::Base
|
2
|
+
requires :car_model
|
3
|
+
optionals :licence_plate
|
4
|
+
input_types(
|
5
|
+
car_model: String
|
6
|
+
)
|
7
|
+
end
|
8
|
+
|
9
|
+
class DemoNestedBooking < Objection::Base
|
10
|
+
requires :booking_id, :booking_date
|
11
|
+
optionals :car
|
12
|
+
input_types(
|
13
|
+
booking_date: Date,
|
14
|
+
car: DemoNestedCar,
|
15
|
+
)
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: objection
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ewout Quax
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: A predefined contract, with required and optional fields, for use with
|
84
|
+
services.
|
85
|
+
email:
|
86
|
+
- ewout.quax@quicknet.nl
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/objection.rb
|
97
|
+
- lib/objection/version.rb
|
98
|
+
- objection.gemspec
|
99
|
+
- spec/objection/objection_spec.rb
|
100
|
+
- spec/spec_helper.rb
|
101
|
+
- spec/support/demo_basic.rb
|
102
|
+
- spec/support/demo_nested.rb
|
103
|
+
- spec/support/demo_optionals.rb
|
104
|
+
- spec/support/demo_requires.rb
|
105
|
+
- spec/support/demo_typed.rb
|
106
|
+
homepage: ''
|
107
|
+
licenses:
|
108
|
+
- MIT
|
109
|
+
metadata: {}
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options: []
|
112
|
+
require_paths:
|
113
|
+
- lib
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
requirements: []
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 2.4.5
|
127
|
+
signing_key:
|
128
|
+
specification_version: 4
|
129
|
+
summary: Declare a predefined contract for use with services
|
130
|
+
test_files:
|
131
|
+
- spec/objection/objection_spec.rb
|
132
|
+
- spec/spec_helper.rb
|
133
|
+
- spec/support/demo_basic.rb
|
134
|
+
- spec/support/demo_nested.rb
|
135
|
+
- spec/support/demo_optionals.rb
|
136
|
+
- spec/support/demo_requires.rb
|
137
|
+
- spec/support/demo_typed.rb
|