cistern 2.2.7 → 2.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 +4 -4
- data/.travis.yml +1 -3
- data/CHANGELOG.md +57 -0
- data/README.md +13 -13
- data/lib/cistern/attributes.rb +82 -46
- data/lib/cistern/client.rb +73 -18
- data/lib/cistern/collection.rb +31 -6
- data/lib/cistern/model.rb +46 -11
- data/lib/cistern/request.rb +33 -8
- data/lib/cistern/singular.rb +12 -4
- data/lib/cistern/version.rb +1 -1
- data/spec/attributes_spec.rb +166 -0
- data/spec/collection_spec.rb +18 -2
- data/spec/coverage_spec.rb +35 -0
- data/spec/mock_data_spec.rb +2 -2
- data/spec/model_spec.rb +11 -163
- data/spec/request_spec.rb +29 -8
- data/spec/singular_spec.rb +9 -2
- data/spec/spec_helper.rb +9 -3
- metadata +6 -2
data/lib/cistern/collection.rb
CHANGED
@@ -5,23 +5,48 @@ module Cistern::Collection
|
|
5
5
|
:keep_if, :pop, :shift, :delete_at, :compact
|
6
6
|
].to_set # :nodoc:
|
7
7
|
|
8
|
-
def self.
|
9
|
-
|
8
|
+
def self.cistern_collection(cistern, klass, name)
|
9
|
+
cistern.const_get(:Collections).module_eval <<-EOS, __FILE__, __LINE__
|
10
10
|
def #{name}(attributes={})
|
11
|
-
#{klass.name}.new(attributes.merge(
|
11
|
+
#{klass.name}.new(attributes.merge(cistern: self))
|
12
12
|
end
|
13
13
|
EOS
|
14
14
|
end
|
15
15
|
|
16
|
-
attr_accessor :records, :loaded, :
|
16
|
+
attr_accessor :records, :loaded, :cistern
|
17
|
+
|
18
|
+
def service=(service)
|
19
|
+
Cistern.deprecation(
|
20
|
+
'#service= is deprecated. Please use #cistern=',
|
21
|
+
caller[0]
|
22
|
+
)
|
23
|
+
@cistern = service
|
24
|
+
end
|
25
|
+
|
26
|
+
def service
|
27
|
+
Cistern.deprecation(
|
28
|
+
'#service is deprecated. Please use #cistern',
|
29
|
+
caller[0]
|
30
|
+
)
|
31
|
+
@cistern
|
32
|
+
end
|
17
33
|
|
18
34
|
module ClassMethods
|
19
35
|
def model(new_model = nil)
|
20
36
|
@_model ||= new_model
|
21
37
|
end
|
22
38
|
|
39
|
+
# @deprecated Use {#cistern_method} instead
|
23
40
|
def service_method(name = nil)
|
24
|
-
|
41
|
+
Cistern.deprecation(
|
42
|
+
'#service_method is deprecated. Please use #cistern_method',
|
43
|
+
caller[0]
|
44
|
+
)
|
45
|
+
@_cistern_method ||= name
|
46
|
+
end
|
47
|
+
|
48
|
+
def cistern_method(name = nil)
|
49
|
+
@_cistern_method ||= name
|
25
50
|
end
|
26
51
|
end
|
27
52
|
|
@@ -80,7 +105,7 @@ module Cistern::Collection
|
|
80
105
|
model.new(
|
81
106
|
{
|
82
107
|
collection: self,
|
83
|
-
|
108
|
+
cistern: cistern,
|
84
109
|
}.merge(attributes)
|
85
110
|
)
|
86
111
|
end
|
data/lib/cistern/model.rb
CHANGED
@@ -7,21 +7,46 @@ module Cistern::Model
|
|
7
7
|
klass.send(:extend, Cistern::Model::ClassMethods)
|
8
8
|
end
|
9
9
|
|
10
|
-
def self.
|
11
|
-
|
10
|
+
def self.cistern_model(cistern, klass, name)
|
11
|
+
cistern.const_get(:Collections).module_eval <<-EOS, __FILE__, __LINE__
|
12
12
|
def #{name}(attributes={})
|
13
|
-
|
13
|
+
#{klass.name}.new(attributes.merge(cistern: self))
|
14
14
|
end
|
15
15
|
EOS
|
16
16
|
end
|
17
17
|
|
18
18
|
module ClassMethods
|
19
|
+
# @deprecated Use {#cistern_method} instead
|
19
20
|
def service_method(name = nil)
|
20
|
-
|
21
|
+
Cistern.deprecation(
|
22
|
+
'#service_method is deprecated. Please use #cistern_method',
|
23
|
+
caller[0]
|
24
|
+
)
|
25
|
+
@_cistern_method ||= name
|
26
|
+
end
|
27
|
+
|
28
|
+
def cistern_method(name = nil)
|
29
|
+
@_cistern_method ||= name
|
21
30
|
end
|
22
31
|
end
|
23
32
|
|
24
|
-
attr_accessor :collection, :
|
33
|
+
attr_accessor :collection, :cistern
|
34
|
+
|
35
|
+
def service=(service)
|
36
|
+
Cistern.deprecation(
|
37
|
+
'#service= is deprecated. Please use #cistern=',
|
38
|
+
caller[0]
|
39
|
+
)
|
40
|
+
@cistern = service
|
41
|
+
end
|
42
|
+
|
43
|
+
def service
|
44
|
+
Cistern.deprecation(
|
45
|
+
'#service is deprecated. Please use #cistern',
|
46
|
+
caller[0]
|
47
|
+
)
|
48
|
+
@cistern
|
49
|
+
end
|
25
50
|
|
26
51
|
def inspect
|
27
52
|
Cistern.formatter.call(self)
|
@@ -31,8 +56,10 @@ module Cistern::Model
|
|
31
56
|
merge_attributes(attributes)
|
32
57
|
end
|
33
58
|
|
59
|
+
# Merge #attributes and call {#save}. Valid and change attributes are available in {#dirty_attributes}
|
60
|
+
# @param attributes [Hash]
|
34
61
|
def update(attributes)
|
35
|
-
|
62
|
+
stage_attributes(attributes)
|
36
63
|
save
|
37
64
|
end
|
38
65
|
|
@@ -67,15 +94,23 @@ module Cistern::Model
|
|
67
94
|
end
|
68
95
|
end
|
69
96
|
|
70
|
-
def wait_for(timeout =
|
71
|
-
|
97
|
+
def wait_for(timeout = cistern_class.timeout, interval = cistern_class.poll_interval, &block)
|
98
|
+
cistern_class.wait_for(timeout, interval) { reload && block.call(self) }
|
72
99
|
end
|
73
100
|
|
74
|
-
def wait_for!(timeout =
|
75
|
-
|
101
|
+
def wait_for!(timeout = cistern_class.timeout, interval = cistern_class.poll_interval, &block)
|
102
|
+
cistern_class.wait_for!(timeout, interval) { reload && block.call(self) }
|
76
103
|
end
|
77
104
|
|
78
105
|
def service_class
|
79
|
-
|
106
|
+
Cistern.deprecation(
|
107
|
+
'#service_class is deprecated. Please use #cistern_class',
|
108
|
+
caller[0]
|
109
|
+
)
|
110
|
+
cistern ? cistern.class : Cistern
|
111
|
+
end
|
112
|
+
|
113
|
+
def cistern_class
|
114
|
+
cistern ? cistern.class : Cistern
|
80
115
|
end
|
81
116
|
end
|
data/lib/cistern/request.rb
CHANGED
@@ -1,31 +1,56 @@
|
|
1
1
|
module Cistern::Request
|
2
|
-
def self.
|
3
|
-
unless klass.name
|
2
|
+
def self.cistern_request(cistern, klass, name)
|
3
|
+
unless klass.name || klass.cistern_method
|
4
4
|
fail ArgumentError, "can't turn anonymous class into a Cistern request"
|
5
5
|
end
|
6
6
|
|
7
|
-
|
7
|
+
cistern::Mock.module_eval <<-EOS, __FILE__, __LINE__
|
8
8
|
def #{name}(*args)
|
9
9
|
#{klass}.new(self)._mock(*args)
|
10
10
|
end
|
11
11
|
EOS
|
12
12
|
|
13
|
-
|
13
|
+
cistern::Real.module_eval <<-EOS, __FILE__, __LINE__
|
14
14
|
def #{name}(*args)
|
15
15
|
#{klass}.new(self)._real(*args)
|
16
16
|
end
|
17
17
|
EOS
|
18
18
|
end
|
19
19
|
|
20
|
-
|
20
|
+
def self.service_request(*args)
|
21
|
+
Cistern.deprecation(
|
22
|
+
'#service_request is deprecated. Please use #cistern_request',
|
23
|
+
caller[0]
|
24
|
+
)
|
25
|
+
cistern_request(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :cistern
|
29
|
+
|
30
|
+
def service
|
31
|
+
Cistern.deprecation(
|
32
|
+
'#service is deprecated. Please use #cistern',
|
33
|
+
caller[0]
|
34
|
+
)
|
35
|
+
@cistern
|
36
|
+
end
|
21
37
|
|
22
|
-
def initialize(
|
23
|
-
@
|
38
|
+
def initialize(cistern)
|
39
|
+
@cistern = cistern
|
24
40
|
end
|
25
41
|
|
26
42
|
module ClassMethods
|
43
|
+
# @deprecated Use {#cistern_method} instead
|
27
44
|
def service_method(name = nil)
|
28
|
-
|
45
|
+
Cistern.deprecation(
|
46
|
+
'#service_method is deprecated. Please use #cistern_method',
|
47
|
+
caller[0]
|
48
|
+
)
|
49
|
+
@_cistern_method ||= name
|
50
|
+
end
|
51
|
+
|
52
|
+
def cistern_method(name = nil)
|
53
|
+
@_cistern_method ||= name
|
29
54
|
end
|
30
55
|
end
|
31
56
|
end
|
data/lib/cistern/singular.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Cistern::Singular
|
2
|
-
def self.
|
3
|
-
|
2
|
+
def self.cistern_singular(cistern, klass, name)
|
3
|
+
cistern.const_get(:Collections).module_eval <<-EOS, __FILE__, __LINE__
|
4
4
|
def #{name}(attributes={})
|
5
|
-
|
5
|
+
#{klass.name}.new(attributes.merge(cistern: self))
|
6
6
|
end
|
7
7
|
EOS
|
8
8
|
end
|
@@ -13,7 +13,15 @@ module Cistern::Singular
|
|
13
13
|
klass.send(:extend, Cistern::Model::ClassMethods)
|
14
14
|
end
|
15
15
|
|
16
|
-
attr_accessor :
|
16
|
+
attr_accessor :cistern
|
17
|
+
|
18
|
+
def service
|
19
|
+
Cistern.deprecation(
|
20
|
+
'#service is deprecated. Please use #cistern',
|
21
|
+
caller[0]
|
22
|
+
)
|
23
|
+
@cistern
|
24
|
+
end
|
17
25
|
|
18
26
|
def inspect
|
19
27
|
Cistern.formatter.call(self)
|
data/lib/cistern/version.rb
CHANGED
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cistern::Attributes, 'requires' do
|
4
|
+
class RequireSpec < Sample::Model
|
5
|
+
identity :id
|
6
|
+
attribute :name, type: :string
|
7
|
+
attribute :type
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'raises if required attributes are not present' do
|
11
|
+
expect {
|
12
|
+
Sample.new.require_spec.requires :name
|
13
|
+
}.to raise_exception(ArgumentError, /name is required/)
|
14
|
+
|
15
|
+
data = { name: '1' }
|
16
|
+
return_value = Sample.new.require_spec(data).requires :name
|
17
|
+
|
18
|
+
expect(return_value).to eq(data)
|
19
|
+
|
20
|
+
expect {
|
21
|
+
Sample.new.require_spec.requires :name, :type
|
22
|
+
}.to raise_exception(ArgumentError, /name and type are required/)
|
23
|
+
|
24
|
+
data = { name: '1', type: 'sample' }
|
25
|
+
return_values = Sample.new.require_spec(data).requires :name, :type
|
26
|
+
expect(return_values).to eq(data)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'raises if a required attribute attribute is not present' do
|
30
|
+
expect {
|
31
|
+
Sample.new.require_spec.requires_one :name, :type
|
32
|
+
}.to raise_exception(ArgumentError, /name or type are required/)
|
33
|
+
|
34
|
+
data = { name: '1' }
|
35
|
+
return_value = Sample.new.require_spec(data).requires_one :name, :type
|
36
|
+
|
37
|
+
expect(return_value).to eq(data)
|
38
|
+
|
39
|
+
data = { name: '1', type: 'sample' }
|
40
|
+
return_values = Sample.new.require_spec(data).requires_one :name, :type
|
41
|
+
expect(return_values).to eq(data)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe Cistern::Attributes, 'parsing' do
|
46
|
+
class TypeSpec < Sample::Model
|
47
|
+
identity :id
|
48
|
+
attribute :name, type: :string
|
49
|
+
attribute :created_at, type: :time
|
50
|
+
attribute :flag, type: :boolean
|
51
|
+
attribute :list, type: :array
|
52
|
+
attribute :number, type: :integer
|
53
|
+
attribute :floater, type: :float
|
54
|
+
attribute :butternut_id, squash: %w(squash id), type: :integer
|
55
|
+
attribute :butternut_type, squash: %w(squash type)
|
56
|
+
attribute :squash
|
57
|
+
attribute :vegetable, aliases: 'squash'
|
58
|
+
attribute :custom, parser: lambda { |v, _| "X!#{v}" }
|
59
|
+
attribute :default, default: 'im a squash'
|
60
|
+
attribute :string_allow_nil, type: :string, allow_nil: true
|
61
|
+
|
62
|
+
attribute :same_alias_1, aliases: 'nested'
|
63
|
+
attribute :same_alias_2, aliases: 'nested'
|
64
|
+
|
65
|
+
attribute :same_alias_squashed_1, squash: %w(nested attr_1)
|
66
|
+
attribute :same_alias_squashed_2, squash: %w(nested attr_2)
|
67
|
+
attribute :same_alias_squashed_3, squash: %w(nested attr_2)
|
68
|
+
attribute :adam_attributes, aliases: 'attributes'
|
69
|
+
|
70
|
+
def save
|
71
|
+
requires :flag
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should parse string' do
|
76
|
+
expect(TypeSpec.new(name: 1).name).to eq('1')
|
77
|
+
expect(TypeSpec.new(name: "b").name).to eq('b')
|
78
|
+
expect(TypeSpec.new(name: nil).name).to eq("")
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should allow nils in string types' do
|
82
|
+
expect(TypeSpec.new(string_allow_nil: nil).string_allow_nil).to eq(nil)
|
83
|
+
end
|
84
|
+
it "should handle a 'attributes' aliased attribute" do
|
85
|
+
expect(TypeSpec.new(attributes: 'x').adam_attributes).to eq('x')
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should parse time' do
|
89
|
+
time = Time.now
|
90
|
+
created_at = TypeSpec.new(created_at: time.to_s).created_at
|
91
|
+
expect(created_at).to be_a(Time)
|
92
|
+
expect(created_at.to_i).to eq(time.to_i)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should parse boolean' do
|
96
|
+
expect(TypeSpec.new(flag: 'false').flag).to be_falsey
|
97
|
+
expect(TypeSpec.new(flag: 'true').flag).to be_truthy
|
98
|
+
expect(TypeSpec.new(flag: false).flag).to be_falsey
|
99
|
+
expect(TypeSpec.new(flag: true).flag).to be_truthy
|
100
|
+
expect(TypeSpec.new(flag: '0').flag).to be_falsey
|
101
|
+
expect(TypeSpec.new(flag: '1').flag).to be_truthy
|
102
|
+
expect(TypeSpec.new(flag: 0).flag).to be_falsey
|
103
|
+
expect(TypeSpec.new(flag: 1).flag).to be_truthy
|
104
|
+
expect(TypeSpec.new(flag: false)).not_to be_flag
|
105
|
+
expect(TypeSpec.new(flag: true)).to be_flag
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should parse an array' do
|
109
|
+
expect(TypeSpec.new(list: []).list).to eq([])
|
110
|
+
expect(TypeSpec.new(list: 'item').list).to eq(['item'])
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should parse a float' do
|
114
|
+
expect(TypeSpec.new(floater: '0.01').floater).to eq(0.01)
|
115
|
+
expect(TypeSpec.new(floater: 0.01).floater).to eq(0.01)
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should use custom parser' do
|
119
|
+
expect(TypeSpec.new(custom: '15').custom).to eq('X!15')
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should squash, cast, alias an attribute and keep a vanilla reference' do
|
123
|
+
# vanilla squash
|
124
|
+
expect(TypeSpec.new({ 'squash' => { 'id' => '12', 'type' => 'fred' } }).butternut_type).to eq('fred')
|
125
|
+
expect(TypeSpec.new({ 'squash' => { 'id' => '12', 'type' => nil } }).butternut_type).to be_nil
|
126
|
+
expect(TypeSpec.new({ 'squash' => nil }).butternut_type).to be_nil
|
127
|
+
|
128
|
+
# composite processors: squash and cast
|
129
|
+
expect(TypeSpec.new({ 'squash' => { 'id' => '12', 'type' => 'fred' } }).butternut_id).to eq(12)
|
130
|
+
expect(TypeSpec.new({ 'squash' => { 'id' => nil, 'type' => 'fred' } }).butternut_id).to be_nil
|
131
|
+
expect(TypeSpec.new({ 'squash' => { 'type' => 'fred' } }).butternut_id).to be_nil
|
132
|
+
|
133
|
+
# override intermediate processing
|
134
|
+
expect(TypeSpec.new({ 'squash' => { 'id' => '12', 'type' => 'fred' } }).squash).to eq({ 'id' => '12', 'type' => 'fred' })
|
135
|
+
|
136
|
+
# alias of override
|
137
|
+
expect(TypeSpec.new({ 'squash' => { 'id' => '12', 'type' => 'fred' } }).vegetable).to eq({ 'id' => '12', 'type' => 'fred' })
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should set a default value' do
|
141
|
+
expect(TypeSpec.new.default).to eq('im a squash')
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should override a default value' do
|
145
|
+
expect(TypeSpec.new(default: 'now im a different squash').default).to eq('now im a different squash')
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'allowing the same alias for multiple attributes' do
|
149
|
+
it 'should do so when not squashing' do
|
150
|
+
type_spec = TypeSpec.new({ 'nested' => 'bamboo' })
|
151
|
+
expect(type_spec.same_alias_1).to eq('bamboo')
|
152
|
+
expect(type_spec.same_alias_2).to eq('bamboo')
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should do so when squashing' do
|
156
|
+
type_spec = TypeSpec.new({ 'nested' => { 'attr_1' => 'bamboo', 'attr_2' => 'panda' } })
|
157
|
+
expect(type_spec.same_alias_squashed_1).to eq('bamboo')
|
158
|
+
expect(type_spec.same_alias_squashed_2).to eq('panda')
|
159
|
+
expect(type_spec.same_alias_squashed_3).to eq('panda')
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should slice out unaccounted for attributes' do
|
164
|
+
expect(TypeSpec.new({ 'something' => { 'id' => '12' } }).attributes.keys).not_to include('something')
|
165
|
+
end
|
166
|
+
end
|
data/spec/collection_spec.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'Cistern::Collection' do
|
4
|
-
class SampleService
|
4
|
+
class SampleService
|
5
|
+
include Cistern::Client
|
5
6
|
end
|
6
7
|
|
7
8
|
class Drug < SampleService::Model
|
@@ -18,7 +19,7 @@ describe 'Cistern::Collection' do
|
|
18
19
|
end
|
19
20
|
|
20
21
|
class Tacs < SampleService::Collection
|
21
|
-
|
22
|
+
cistern_method :toes
|
22
23
|
end
|
23
24
|
|
24
25
|
it 'should generate a default collection method' do
|
@@ -64,4 +65,19 @@ describe 'Cistern::Collection' do
|
|
64
65
|
it 'should ==' do
|
65
66
|
Drugs.new.all == Drugs.new.all
|
66
67
|
end
|
68
|
+
|
69
|
+
describe 'deprecation', :deprecated do
|
70
|
+
class DeprecatedCollectionService
|
71
|
+
include Cistern::Client
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'responds to #service' do
|
75
|
+
class DeprecationCollection < DeprecatedCollectionService::Collection
|
76
|
+
service_method :deprecator
|
77
|
+
end
|
78
|
+
|
79
|
+
sample = DeprecatedCollectionService.new.deprecator
|
80
|
+
expect(sample.service).to eq(sample.cistern)
|
81
|
+
end
|
82
|
+
end
|
67
83
|
end
|