acfs 1.3.3 → 1.6.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +372 -0
  3. data/LICENSE +22 -0
  4. data/README.md +321 -0
  5. data/acfs.gemspec +38 -0
  6. data/lib/acfs.rb +51 -0
  7. data/lib/acfs/adapter/base.rb +26 -0
  8. data/lib/acfs/adapter/typhoeus.rb +82 -0
  9. data/lib/acfs/collection.rb +28 -0
  10. data/lib/acfs/collections/paginatable.rb +76 -0
  11. data/lib/acfs/configuration.rb +120 -0
  12. data/lib/acfs/errors.rb +147 -0
  13. data/lib/acfs/global.rb +101 -0
  14. data/lib/acfs/location.rb +76 -0
  15. data/lib/acfs/middleware/base.rb +24 -0
  16. data/lib/acfs/middleware/json.rb +31 -0
  17. data/lib/acfs/middleware/logger.rb +23 -0
  18. data/lib/acfs/middleware/msgpack.rb +32 -0
  19. data/lib/acfs/middleware/print.rb +23 -0
  20. data/lib/acfs/middleware/serializer.rb +41 -0
  21. data/lib/acfs/operation.rb +96 -0
  22. data/lib/acfs/request.rb +32 -0
  23. data/lib/acfs/request/callbacks.rb +54 -0
  24. data/lib/acfs/resource.rb +39 -0
  25. data/lib/acfs/resource/attributes.rb +270 -0
  26. data/lib/acfs/resource/attributes/base.rb +29 -0
  27. data/lib/acfs/resource/attributes/boolean.rb +39 -0
  28. data/lib/acfs/resource/attributes/date_time.rb +32 -0
  29. data/lib/acfs/resource/attributes/dict.rb +39 -0
  30. data/lib/acfs/resource/attributes/float.rb +33 -0
  31. data/lib/acfs/resource/attributes/integer.rb +29 -0
  32. data/lib/acfs/resource/attributes/list.rb +36 -0
  33. data/lib/acfs/resource/attributes/string.rb +26 -0
  34. data/lib/acfs/resource/attributes/uuid.rb +48 -0
  35. data/lib/acfs/resource/dirty.rb +37 -0
  36. data/lib/acfs/resource/initialization.rb +31 -0
  37. data/lib/acfs/resource/loadable.rb +35 -0
  38. data/lib/acfs/resource/locatable.rb +135 -0
  39. data/lib/acfs/resource/operational.rb +26 -0
  40. data/lib/acfs/resource/persistence.rb +258 -0
  41. data/lib/acfs/resource/query_methods.rb +266 -0
  42. data/lib/acfs/resource/service.rb +44 -0
  43. data/lib/acfs/resource/validation.rb +49 -0
  44. data/lib/acfs/response.rb +30 -0
  45. data/lib/acfs/response/formats.rb +27 -0
  46. data/lib/acfs/response/status.rb +33 -0
  47. data/lib/acfs/rspec.rb +13 -0
  48. data/lib/acfs/runner.rb +102 -0
  49. data/lib/acfs/service.rb +94 -0
  50. data/lib/acfs/service/middleware.rb +58 -0
  51. data/lib/acfs/service/middleware/stack.rb +65 -0
  52. data/lib/acfs/singleton_resource.rb +85 -0
  53. data/lib/acfs/stub.rb +199 -0
  54. data/lib/acfs/util.rb +22 -0
  55. data/lib/acfs/version.rb +16 -0
  56. data/lib/acfs/yard.rb +6 -0
  57. data/spec/acfs/adapter/typhoeus_spec.rb +55 -0
  58. data/spec/acfs/collection_spec.rb +157 -0
  59. data/spec/acfs/configuration_spec.rb +53 -0
  60. data/spec/acfs/global_spec.rb +140 -0
  61. data/spec/acfs/location_spec.rb +25 -0
  62. data/spec/acfs/middleware/json_spec.rb +79 -0
  63. data/spec/acfs/middleware/msgpack_spec.rb +62 -0
  64. data/spec/acfs/operation_spec.rb +12 -0
  65. data/spec/acfs/request/callbacks_spec.rb +48 -0
  66. data/spec/acfs/request_spec.rb +79 -0
  67. data/spec/acfs/resource/attributes/boolean_spec.rb +58 -0
  68. data/spec/acfs/resource/attributes/date_time_spec.rb +51 -0
  69. data/spec/acfs/resource/attributes/dict_spec.rb +77 -0
  70. data/spec/acfs/resource/attributes/float_spec.rb +61 -0
  71. data/spec/acfs/resource/attributes/integer_spec.rb +36 -0
  72. data/spec/acfs/resource/attributes/list_spec.rb +60 -0
  73. data/spec/acfs/resource/attributes/uuid_spec.rb +42 -0
  74. data/spec/acfs/resource/attributes_spec.rb +179 -0
  75. data/spec/acfs/resource/dirty_spec.rb +49 -0
  76. data/spec/acfs/resource/initialization_spec.rb +36 -0
  77. data/spec/acfs/resource/loadable_spec.rb +22 -0
  78. data/spec/acfs/resource/locatable_spec.rb +118 -0
  79. data/spec/acfs/resource/persistance_spec.rb +322 -0
  80. data/spec/acfs/resource/query_methods_spec.rb +548 -0
  81. data/spec/acfs/resource/validation_spec.rb +129 -0
  82. data/spec/acfs/response/formats_spec.rb +52 -0
  83. data/spec/acfs/response/status_spec.rb +71 -0
  84. data/spec/acfs/runner_spec.rb +95 -0
  85. data/spec/acfs/service/middleware_spec.rb +35 -0
  86. data/spec/acfs/service_spec.rb +48 -0
  87. data/spec/acfs/singleton_resource_spec.rb +17 -0
  88. data/spec/acfs/stub_spec.rb +345 -0
  89. data/spec/acfs_spec.rb +205 -0
  90. data/spec/fixtures/config.yml +14 -0
  91. data/spec/spec_helper.rb +42 -0
  92. data/spec/support/hash.rb +11 -0
  93. data/spec/support/response.rb +12 -0
  94. data/spec/support/service.rb +92 -0
  95. data/spec/support/shared/find_callbacks.rb +50 -0
  96. metadata +159 -26
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Attributes::Integer do
6
+ let(:type) { Acfs::Resource::Attributes::Integer.new }
7
+
8
+ describe '#cast' do
9
+ subject { -> { type.cast value } }
10
+
11
+ context 'with nil' do
12
+ let(:value) { nil }
13
+ it { expect(subject.call).to eq nil }
14
+ end
15
+
16
+ context 'with empty string' do
17
+ let(:value) { '' }
18
+ it { expect(subject.call).to eq 0 }
19
+ end
20
+
21
+ context 'with blank string' do
22
+ let(:value) { " \t" }
23
+ it { expect(subject.call).to eq 0 }
24
+ end
25
+
26
+ context 'with string' do
27
+ let(:value) { '123' }
28
+ it { expect(subject.call).to eq 123 }
29
+ end
30
+
31
+ context 'with invalid string' do
32
+ let(:value) { '123a' }
33
+ it { is_expected.to raise_error ArgumentError }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Attributes::List do
6
+ let(:type) { Acfs::Resource::Attributes::List.new }
7
+
8
+ describe '#cast' do
9
+ subject { -> { type.cast value } }
10
+
11
+ context 'with nil' do
12
+ let(:value) { nil }
13
+ it { expect(subject.call).to eq nil }
14
+ end
15
+
16
+ context 'with blank string (I)' do
17
+ let(:value) { '' }
18
+ it { expect(subject.call).to eq [] }
19
+ end
20
+
21
+ context 'with blank string (II)' do
22
+ let(:value) { " \t" }
23
+ it { expect(subject.call).to eq [] }
24
+ end
25
+
26
+ context 'with array' do
27
+ let(:value) { %w[abc cde efg] }
28
+ it { expect(subject.call).to eq value }
29
+ end
30
+
31
+ context 'with convertable object (I)' do
32
+ let(:value) do
33
+ Class.new do
34
+ def to_ary
35
+ [1, 2, 3]
36
+ end
37
+ end.new
38
+ end
39
+
40
+ it { expect(subject.call).to eq [1, 2, 3] }
41
+ end
42
+
43
+ context 'with convertable object (II)' do
44
+ let(:value) do
45
+ Class.new do
46
+ def to_a
47
+ [1, 2, 3]
48
+ end
49
+ end.new
50
+ end
51
+
52
+ it { expect(subject.call).to eq [1, 2, 3] }
53
+ end
54
+
55
+ context 'with non castable object' do
56
+ let(:value) { Object.new }
57
+ it { expect(subject.call).to eq [value] }
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Attributes::UUID do
6
+ let(:type) { Acfs::Resource::Attributes::UUID.new }
7
+
8
+ describe '#cast' do
9
+ subject { -> { type.cast(value) } }
10
+
11
+ context 'with nil' do
12
+ let(:value) { nil }
13
+ it { expect(subject.call).to eq nil }
14
+ end
15
+
16
+ context 'with empty string' do
17
+ let(:value) { '' }
18
+ it { expect(subject.call).to eq nil }
19
+ end
20
+
21
+ context 'with blank string' do
22
+ let(:value) { " \t" }
23
+ it { expect(subject.call).to eq nil }
24
+ end
25
+
26
+ context 'with string UUID' do
27
+ let(:value) { '450b7a40-94ad-11e3-baa8-0800200c9a66' }
28
+ it { expect(subject.call).to be_a String }
29
+ it { expect(subject.call).to eq value }
30
+ end
31
+
32
+ context 'with invalid string' do
33
+ let(:value) { 'invalid string' }
34
+ it { is_expected.to raise_error TypeError, /invalid UUID/i }
35
+ end
36
+
37
+ context 'with invalid UUID' do
38
+ let(:value) { 'xxxxxxxx-yyyy-11e3-baa8-0800200c9a66' }
39
+ it { is_expected.to raise_error TypeError, /invalid UUID/i }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Attributes do
6
+ let(:model) { Class.new Acfs::Resource }
7
+ let(:submodel) { Class.new model }
8
+
9
+ describe '#initialize' do
10
+ before { model.attribute :name, :string, default: 'John' }
11
+
12
+ it 'should have attribute list' do
13
+ expect(model.new.attributes).to include(:name)
14
+ end
15
+
16
+ it 'should set default attributes' do
17
+ expect(model.new.name).to be == 'John'
18
+ end
19
+
20
+ context 'with dynamic default value' do
21
+ before do
22
+ model.attribute :name, :string, default: 'John'
23
+ model.attribute :mail, :string, default: -> { "#{name}@srv.tld" }
24
+ end
25
+
26
+ it 'should set dynamic default attributes' do
27
+ expect(model.new.mail).to be == 'John@srv.tld'
28
+ end
29
+ end
30
+ end
31
+
32
+ describe '#attributes' do
33
+ before do
34
+ model.attribute :name, :string, default: 'John'
35
+ model.attribute :age, :integer, default: 25
36
+ end
37
+
38
+ it 'should return hash of all attributes' do
39
+ expect(model.new.attributes).to eq(name: 'John', age: 25)
40
+ end
41
+ end
42
+
43
+ describe '#write_attributes' do
44
+ before do
45
+ model.attribute :name, :string, default: 'John'
46
+ model.attribute :age, :integer, default: 25
47
+ model.send :define_method, :name= do |name|
48
+ write_attribute :name, "The Great #{name}"
49
+ end
50
+ end
51
+ let(:params) { {name: 'James'} }
52
+ let(:opts) { {} }
53
+ let(:m) { model.new }
54
+ let(:action) { -> { m.write_attributes(params, **opts) } }
55
+ subject { action }
56
+
57
+ it 'should update attributes' do
58
+ should change(m, :attributes)
59
+ .from(name: 'The Great John', age: 25)
60
+ .to(name: 'The Great James', age: 25)
61
+ end
62
+
63
+ context 'without non-hash params' do
64
+ let(:params) { 'James' }
65
+
66
+ it { should_not change(m, :attributes) }
67
+ its(:call) { should eq false }
68
+ end
69
+
70
+ context 'with unknown attributes' do
71
+ let(:params) { {name: 'James', born_at: 'today'} }
72
+
73
+ it { should_not raise_error }
74
+
75
+ it 'should update known attributes and store unknown' do
76
+ should change(m, :attributes)
77
+ .from(name: 'The Great John', age: 25)
78
+ .to(name: 'The Great James', age: 25, born_at: 'today')
79
+ end
80
+
81
+ context 'with unknown: :raise option' do
82
+ let(:opts) { {unknown: :raise} }
83
+
84
+ it { should raise_error(ArgumentError, /unknown attribute/i) }
85
+
86
+ it do
87
+ expect do
88
+ subject.call
89
+ rescue StandardError
90
+ true
91
+ end.to_not change(m, :attributes)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ describe '#_getter_' do
98
+ before { model.attribute :name, :string, default: 'John' }
99
+
100
+ it 'should return value' do
101
+ mo = model.new
102
+ mo.name = 'Paul'
103
+
104
+ expect(mo.name).to be == 'Paul'
105
+ end
106
+
107
+ it 'should return default value' do
108
+ expect(model.new.name).to be == 'John'
109
+ end
110
+ end
111
+
112
+ describe '#_setter_' do
113
+ before do
114
+ model.attribute :name, :string, default: 'John'
115
+ model.attribute :age, :integer, default: '25'
116
+ end
117
+
118
+ it 'should set value' do
119
+ o = model.new
120
+ o.name = 'Paul'
121
+
122
+ expect(o.name).to be == 'Paul'
123
+ end
124
+
125
+ it 'should update attributes hash' do
126
+ o = model.new
127
+ o.name = 'Johannes'
128
+
129
+ expect(o.attributes['name']).to be == 'Johannes'
130
+ end
131
+
132
+ it 'should cast values' do
133
+ o = model.new
134
+ o.age = '28'
135
+
136
+ expect(o.age).to be == 28
137
+ end
138
+ end
139
+
140
+ describe 'class' do
141
+ describe '#attributes' do
142
+ it 'should add an attribute to model attribute list' do
143
+ model.send :attribute, :name, :string
144
+
145
+ expect(model.attributes.symbolize_keys).to eq name: nil
146
+ end
147
+
148
+ it 'should accept a default value' do
149
+ model.send :attribute, :name, :string, default: 'John'
150
+
151
+ expect(model.attributes.symbolize_keys).to eq name: 'John'
152
+ end
153
+
154
+ it 'should accept a symbolic type' do
155
+ model.send :attribute, :age, :integer, default: '12'
156
+
157
+ expect(model.attributes.symbolize_keys).to eq age: 12
158
+ end
159
+
160
+ it 'should accept a class type' do
161
+ model.send :attribute, :age, Acfs::Resource::Attributes::Integer,
162
+ default: '12'
163
+
164
+ expect(model.attributes.symbolize_keys).to eq age: 12
165
+ end
166
+
167
+ context 'on inherited resources' do
168
+ before do
169
+ model.attribute :age, :integer, default: 5
170
+ submodel.attribute :born_at, :date_time
171
+ end
172
+
173
+ it 'includes superclass attributes' do
174
+ expect(submodel.attributes.keys).to match_array %w[age born_at]
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Dirty do
6
+ let(:model) { MyUser.new }
7
+ before do
8
+ stub_request(:get, 'http://users.example.org/users/1')
9
+ .to_return response id: 1, name: 'Anon', age: 12
10
+ stub_request(:post, 'http://users.example.org/users')
11
+ .to_return response id: 5, name: 'dhh', age: 12
12
+ end
13
+
14
+ it 'includes ActiveModel::Dirty' do
15
+ model.is_a? ActiveModel::Dirty
16
+ end
17
+
18
+ describe '#changed?' do
19
+ context 'after attribute change' do
20
+ let(:user) { MyUser.new name: 'dhh' }
21
+
22
+ it { expect(user).to be_changed }
23
+
24
+ context 'and saving' do
25
+ before { user.save }
26
+ it { expect(user).to_not be_changed }
27
+ end
28
+ end
29
+
30
+ context 'after model load' do
31
+ let(:user) { MyUser.find 1 }
32
+ before { user && Acfs.run }
33
+
34
+ it { expect(user).to_not be_changed }
35
+ end
36
+
37
+ context 'after model new without attrs' do
38
+ let(:user) { MyUser.new }
39
+
40
+ it { expect(user).to_not be_changed }
41
+ end
42
+
43
+ context 'after model new with attrs' do
44
+ let(:user) { MyUser.new name: 'Uschi' }
45
+
46
+ it { expect(user).to be_changed }
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'Acfs::Resource::Initialization' do
6
+ let(:model) do
7
+ Class.new(Acfs::Resource).tap do |c|
8
+ c.class_eval do
9
+ attr_accessor :name
10
+ attr_reader :age
11
+
12
+ private
13
+
14
+ attr_writer :age
15
+ end
16
+ end
17
+ end
18
+
19
+ describe '#initialize' do
20
+ it 'should allow to set attributes with initializer' do
21
+ m = model.new name: 'John'
22
+ expect(m.name).to eq 'John'
23
+ end
24
+
25
+ it 'should raise error when attributes with private setters are given' do
26
+ expect { model.new age: 25 }.to raise_error(NoMethodError)
27
+ end
28
+ end
29
+
30
+ describe '#persisted?' do
31
+ subject { model.new.persisted? }
32
+ it 'should be false' do
33
+ should be false
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Loadable do
6
+ let(:model) { MyUser.find 1 }
7
+ before do
8
+ stub_request(:get, 'http://users.example.org/users/1')
9
+ .to_return response id: 1, name: 'Anon', age: 12
10
+ end
11
+
12
+ describe '#loaded?' do
13
+ context 'before Acfs#run' do
14
+ it { expect(model).to_not be_loaded }
15
+ end
16
+
17
+ context 'afer Acfs#run' do
18
+ before { model && Acfs.run }
19
+ it { expect(model).to be_loaded }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Locatable do
6
+ let(:model) { MyUser }
7
+ before do
8
+ stub_request(:get, 'http://users.example.org/users/1')
9
+ .to_return response id: 1, name: 'Anon', age: 12
10
+ stub_request(:get, 'http://users.example.org/users/1/profile')
11
+ .to_return response user_id: 2, twitter_handle: '@anon'
12
+ end
13
+
14
+ describe '.url' do
15
+ it 'should return URL' do
16
+ expect(model.url).to be == 'http://users.example.org/users'
17
+ end
18
+
19
+ it 'should return URL with id path part if specified' do
20
+ expect(model.url(5)).to be == 'http://users.example.org/users/5'
21
+ end
22
+
23
+ context 'with attribute in path' do
24
+ let(:model) { Profile }
25
+
26
+ it 'should replace placeholder' do
27
+ expect(model.url(user_id: 1))
28
+ .to eq 'http://users.example.org/users/1/profile'
29
+ end
30
+
31
+ context 'without attributes' do
32
+ it 'should raise an error if attribute is missing' do
33
+ expect { model.url }.to raise_error ArgumentError
34
+ end
35
+ end
36
+ end
37
+
38
+ describe 'custom paths' do
39
+ let(:model) { Session }
40
+ let(:location) { Session.location action: action }
41
+ subject { location }
42
+
43
+ context ':list location' do
44
+ let(:action) { :list }
45
+
46
+ its(:raw_uri) do
47
+ should eq 'http://users.example.org/users/:user_id/sessions'
48
+ end
49
+ end
50
+
51
+ context ':create location' do
52
+ let(:action) { :create }
53
+ its(:raw_uri) { should eq 'http://users.example.org/sessions' }
54
+ end
55
+
56
+ context ':read location' do
57
+ let(:action) { :read }
58
+ its(:raw_uri) { should eq 'http://users.example.org/sessions/:id' }
59
+ end
60
+
61
+ context ':update location' do
62
+ let(:action) { :update }
63
+ its(:raw_uri) do
64
+ expect { subject }.to raise_error ArgumentError, /update.*disabled/
65
+ end
66
+ end
67
+
68
+ context ':delete location' do
69
+ let(:action) { :delete }
70
+ its(:raw_uri) do
71
+ should eq 'http://users.example.org/users/:user_id/sessions/del/:id'
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ describe '#url' do
78
+ context 'new resource' do
79
+ let(:m) { model.new }
80
+
81
+ it 'should return nil' do
82
+ expect(m.url).to be_nil
83
+ end
84
+
85
+ context 'new resource with id' do
86
+ let(:m) { model.new id: 475 }
87
+
88
+ it 'should return resource URL' do
89
+ expect(m.url).to eq 'http://users.example.org/users/475'
90
+ end
91
+ end
92
+
93
+ context 'with attribute in path' do
94
+ it 'should return nil' do
95
+ expect(m.url).to be_nil
96
+ end
97
+ end
98
+ end
99
+
100
+ context 'loaded resource' do
101
+ let(:m) { model.find 1 }
102
+ before { m && Acfs.run }
103
+
104
+ it "should return resource's URL" do
105
+ expect(m.url).to eq 'http://users.example.org/users/1'
106
+ end
107
+
108
+ context 'with attribute in path' do
109
+ let(:model) { Profile }
110
+ let(:m) { model.find user_id: 1 }
111
+
112
+ it "should return resource's URL" do
113
+ expect(m.url).to eq 'http://users.example.org/users/2/profile'
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end