daylight 0.9.0.rc1
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/README.md +113 -0
- data/app/controllers/daylight_documentation/documentation_controller.rb +27 -0
- data/app/helpers/daylight_documentation/documentation_helper.rb +57 -0
- data/app/views/daylight_documentation/documentation/_header.haml +4 -0
- data/app/views/daylight_documentation/documentation/index.haml +12 -0
- data/app/views/daylight_documentation/documentation/model.haml +114 -0
- data/app/views/layouts/documentation.haml +22 -0
- data/config/routes.rb +8 -0
- data/doc/actions.md +70 -0
- data/doc/benchmarks.md +17 -0
- data/doc/contribute.md +80 -0
- data/doc/develop.md +1205 -0
- data/doc/environment.md +109 -0
- data/doc/example.md +3 -0
- data/doc/framework.md +31 -0
- data/doc/install.md +128 -0
- data/doc/principles.md +42 -0
- data/doc/testing.md +107 -0
- data/doc/usage.md +970 -0
- data/lib/daylight/api.rb +293 -0
- data/lib/daylight/associations.rb +247 -0
- data/lib/daylight/client_reloader.rb +45 -0
- data/lib/daylight/collection.rb +161 -0
- data/lib/daylight/errors.rb +94 -0
- data/lib/daylight/inflections.rb +7 -0
- data/lib/daylight/mock.rb +282 -0
- data/lib/daylight/read_only.rb +88 -0
- data/lib/daylight/refinements.rb +63 -0
- data/lib/daylight/reflection_ext.rb +67 -0
- data/lib/daylight/resource_proxy.rb +226 -0
- data/lib/daylight/version.rb +10 -0
- data/lib/daylight.rb +27 -0
- data/rails/daylight/api_controller.rb +354 -0
- data/rails/daylight/documentation.rb +13 -0
- data/rails/daylight/helpers.rb +32 -0
- data/rails/daylight/params.rb +23 -0
- data/rails/daylight/refiners.rb +186 -0
- data/rails/daylight/server.rb +29 -0
- data/rails/daylight/tasks.rb +37 -0
- data/rails/extensions/array_ext.rb +9 -0
- data/rails/extensions/autosave_association_fix.rb +49 -0
- data/rails/extensions/has_one_serializer_ext.rb +111 -0
- data/rails/extensions/inflections.rb +6 -0
- data/rails/extensions/nested_attributes_ext.rb +94 -0
- data/rails/extensions/read_only_attributes.rb +35 -0
- data/rails/extensions/render_json_meta.rb +99 -0
- data/rails/extensions/route_options.rb +47 -0
- data/rails/extensions/versioned_url_for.rb +22 -0
- data/spec/config/dependencies.rb +2 -0
- data/spec/config/factory_girl.rb +4 -0
- data/spec/config/simplecov_rcov.rb +26 -0
- data/spec/config/test_api.rb +1 -0
- data/spec/controllers/documentation_controller_spec.rb +24 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +24 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/daylight.rb +1 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +59 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/helpers/documentation_helper_spec.rb +82 -0
- data/spec/lib/daylight/api_spec.rb +178 -0
- data/spec/lib/daylight/associations_spec.rb +325 -0
- data/spec/lib/daylight/collection_spec.rb +235 -0
- data/spec/lib/daylight/errors_spec.rb +111 -0
- data/spec/lib/daylight/mock_spec.rb +144 -0
- data/spec/lib/daylight/read_only_spec.rb +118 -0
- data/spec/lib/daylight/refinements_spec.rb +80 -0
- data/spec/lib/daylight/reflection_ext_spec.rb +50 -0
- data/spec/lib/daylight/resource_proxy_spec.rb +325 -0
- data/spec/rails/daylight/api_controller_spec.rb +421 -0
- data/spec/rails/daylight/helpers_spec.rb +41 -0
- data/spec/rails/daylight/params_spec.rb +45 -0
- data/spec/rails/daylight/refiners_spec.rb +178 -0
- data/spec/rails/extensions/array_ext_spec.rb +51 -0
- data/spec/rails/extensions/has_one_serializer_ext_spec.rb +135 -0
- data/spec/rails/extensions/nested_attributes_ext_spec.rb +177 -0
- data/spec/rails/extensions/render_json_meta_spec.rb +140 -0
- data/spec/rails/extensions/route_options_spec.rb +309 -0
- data/spec/rails/extensions/versioned_url_for_spec.rb +46 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/support/migration_helper.rb +40 -0
- metadata +422 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Daylight::Associations do
|
|
4
|
+
|
|
5
|
+
class RelatedTestClass < Daylight::API
|
|
6
|
+
self.password = nil
|
|
7
|
+
self.include_format_in_path = false
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class AssociationsTestClass < Daylight::API
|
|
11
|
+
self.password = nil
|
|
12
|
+
self.include_format_in_path = false
|
|
13
|
+
|
|
14
|
+
has_many :related_test_classes, through: :associated
|
|
15
|
+
has_many :things, class_name: 'RelatedTestClass', through: :associated
|
|
16
|
+
belongs_to :parent, class_name: 'RelatedTestClass'
|
|
17
|
+
has_one :grandparent, class_name: 'RelatedTestClass', through: :parent
|
|
18
|
+
has_one :associate, class_name: 'RelatedTestClass'
|
|
19
|
+
remote :remote_stuff, class_name: 'RelatedTestClass'
|
|
20
|
+
|
|
21
|
+
def id; 123; end
|
|
22
|
+
def parent_id; 456; end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe :has_many do
|
|
26
|
+
|
|
27
|
+
before do
|
|
28
|
+
data = [{name: 'one'}, {name: 'two'}]
|
|
29
|
+
[RelatedTestClass, AssociationsTestClass].each do |clazz|
|
|
30
|
+
stub_request(:get, %r{#{clazz.site}}).to_return(body: data.to_json)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
it "fetches the results out of the attributes if they exist" do
|
|
36
|
+
object = AssociationsTestClass.first
|
|
37
|
+
object.attributes['related_test_classes_attributes'] = ['yay']
|
|
38
|
+
collection = object.related_test_classes
|
|
39
|
+
collection.should be_instance_of(Array)
|
|
40
|
+
collection.first.should == 'yay'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe "with new resource" do
|
|
44
|
+
let(:new_resource) { AssociationsTestClass.new }
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
it "caches the results of the association" do
|
|
48
|
+
collection = new_resource.related_test_classes
|
|
49
|
+
new_resource.related_test_classes.should == collection
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "creates a method that construts an Array for the collection" do
|
|
53
|
+
collection = new_resource.related_test_classes
|
|
54
|
+
|
|
55
|
+
collection.should be_a Array
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "sets the associations directly the attributes hash" do
|
|
59
|
+
new_resource.related_test_classes = ["associated instances"]
|
|
60
|
+
|
|
61
|
+
new_resource.attributes['related_test_classes_attributes'].should == ["associated instances"]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "fetches the stored associations out of the attributes when they are set" do
|
|
65
|
+
new_resource.related_test_classes = ["associated instances"]
|
|
66
|
+
|
|
67
|
+
new_resource.related_test_classes.should == ["associated instances"]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
describe "with existing resource" do
|
|
73
|
+
let(:existing_resource) { AssociationsTestClass.first }
|
|
74
|
+
|
|
75
|
+
it "creates a method that construts an ResourceProxy for the association" do
|
|
76
|
+
proxy = existing_resource.related_test_classes
|
|
77
|
+
|
|
78
|
+
proxy.should be_a Daylight::ResourceProxy
|
|
79
|
+
proxy.resource_class.should == RelatedTestClass
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "creates a method that construts an ResourceProxy with context about the association" do
|
|
83
|
+
proxy = existing_resource.related_test_classes
|
|
84
|
+
|
|
85
|
+
proxy.association_name.should == :related_test_classes
|
|
86
|
+
proxy.association_resource.should == existing_resource
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "hits the association api endpoint" do
|
|
90
|
+
existing_resource.related_test_classes.load
|
|
91
|
+
|
|
92
|
+
a_request(:get, "http://daylight.test/v1/associations_test_classes/123/related_test_classes.json").should have_been_made
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "caches the results of the association" do
|
|
96
|
+
proxy = existing_resource.related_test_classes
|
|
97
|
+
existing_resource.related_test_classes.should == proxy
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "supports the :class_name option" do ## THIS ONE
|
|
101
|
+
proxy = existing_resource.things
|
|
102
|
+
proxy.resource_class.should == RelatedTestClass
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "chains using the proxy class for the associated model" do
|
|
106
|
+
proxy = existing_resource.related_test_classes.where(wibble: 'wobble')
|
|
107
|
+
|
|
108
|
+
proxy.resource_class.should == RelatedTestClass
|
|
109
|
+
proxy.to_params[:filters].should == {wibble: 'wobble'}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "sets the associations directly the attributes hash" do
|
|
113
|
+
existing_resource.related_test_classes = ["associated instances"]
|
|
114
|
+
|
|
115
|
+
existing_resource.attributes['related_test_classes_attributes'].should == ["associated instances"]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "fetches the stored associations out of the attributes when they exist" do
|
|
119
|
+
existing_resource.related_test_classes = ["associated instances"]
|
|
120
|
+
|
|
121
|
+
existing_resource.related_test_classes.should == ["associated instances"]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe :belongs_to do
|
|
127
|
+
|
|
128
|
+
before do
|
|
129
|
+
data = { parent: {name: 'three'}}
|
|
130
|
+
[RelatedTestClass, AssociationsTestClass].each do |clazz|
|
|
131
|
+
stub_request(:get, %r{#{clazz.site}}).to_return(body: data.to_json)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'still fetches the parent object' do
|
|
136
|
+
resource = AssociationsTestClass.find(1)
|
|
137
|
+
|
|
138
|
+
resource.parent.should_not be_nil
|
|
139
|
+
resource.parent.name.should == 'three'
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'sets the parent to a new object' do
|
|
143
|
+
resource = AssociationsTestClass.find(1)
|
|
144
|
+
resource.parent = RelatedTestClass.new(name: 'new parent')
|
|
145
|
+
|
|
146
|
+
resource.parent.name.should == 'new parent'
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'sets the parent foreign key' do
|
|
150
|
+
resource = AssociationsTestClass.find(1)
|
|
151
|
+
resource.parent = RelatedTestClass.new(id: 789, name: 'new parent')
|
|
152
|
+
|
|
153
|
+
resource.attributes['parent_id'].should == 789
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it 'sets the parent directly in the nested attributes hash' do
|
|
157
|
+
resource = AssociationsTestClass.find(1)
|
|
158
|
+
resource.parent = RelatedTestClass.new(id: 789, name: 'new parent')
|
|
159
|
+
|
|
160
|
+
resource.attributes['parent_attributes'].should == resource.parent
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
describe :belongs_to_through do
|
|
165
|
+
|
|
166
|
+
before do
|
|
167
|
+
association_data = { through: {
|
|
168
|
+
id: 1,
|
|
169
|
+
parent_id: 456, # ignored because of parent_id method
|
|
170
|
+
parent_attributes: {
|
|
171
|
+
id: 456,
|
|
172
|
+
grandparent_id: 3
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
embedded_data = { through: {
|
|
178
|
+
id: 2,
|
|
179
|
+
parent_id: 456, # ignored because of parent_id method
|
|
180
|
+
parent_attributes: {
|
|
181
|
+
id: 456,
|
|
182
|
+
grandparent: { id: 4, name: 'embed' }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
related_data = {id: nil, name: 'related'}
|
|
188
|
+
|
|
189
|
+
stub_request(:get, %r{#{AssociationsTestClass.element_path(1)}}).to_return(body: association_data.to_json)
|
|
190
|
+
stub_request(:get, %r{#{AssociationsTestClass.element_path(2)}}).to_return(body: embedded_data.to_json)
|
|
191
|
+
stub_request(:get, %r{#{RelatedTestClass.element_path(456)}}).to_return(body: related_data.merge(id: 456).to_json)
|
|
192
|
+
stub_request(:get, %r{#{RelatedTestClass.element_path(3)}}).to_return(body: related_data.merge(id: 3).to_json)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'still fetches the parent object' do
|
|
196
|
+
resource = AssociationsTestClass.find(1)
|
|
197
|
+
|
|
198
|
+
resource.parent.should_not be_nil
|
|
199
|
+
resource.parent.id.should == 456
|
|
200
|
+
resource.parent.name.should == 'related'
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it 'fetches the "through" object' do
|
|
204
|
+
resource = AssociationsTestClass.find(1)
|
|
205
|
+
|
|
206
|
+
resource.grandparent.should_not be_nil
|
|
207
|
+
resource.grandparent.id.should == 3
|
|
208
|
+
resource.grandparent.name.should == 'related'
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it 'fetches embedded "through" object' do
|
|
212
|
+
resource = AssociationsTestClass.find(2)
|
|
213
|
+
|
|
214
|
+
resource.grandparent.should be_kind_of(ActiveResource::Base)
|
|
215
|
+
resource.grandparent.id.should == 4
|
|
216
|
+
resource.grandparent.name.should == 'embed'
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it 'sets the "through" object foreign key' do
|
|
220
|
+
resource = AssociationsTestClass.find(1)
|
|
221
|
+
resource.grandparent = RelatedTestClass.new(id: 789, name: 'new grandparent')
|
|
222
|
+
|
|
223
|
+
resource.attributes['parent_attributes']['grandparent_id'].should == 789
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it 'sets the through object directly in the nested attributes hash' do
|
|
227
|
+
resource = AssociationsTestClass.find(1)
|
|
228
|
+
resource.grandparent = RelatedTestClass.new(id: 789, name: 'new grandparent')
|
|
229
|
+
|
|
230
|
+
resource.attributes['parent_attributes']['grandparent_attributes'].should == resource.grandparent
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
describe :has_one do
|
|
235
|
+
before do
|
|
236
|
+
associated = { id: nil, name: 'Hardy', associate_attributes: { id: 100 } }
|
|
237
|
+
related = { id: 100, name: 'Laurel' }
|
|
238
|
+
stub_request(:get, %r{#{AssociationsTestClass.element_path(1)}}).to_return(body: associated.to_json)
|
|
239
|
+
# It uses the filter method instead of default ActiveResource behavior
|
|
240
|
+
# http://daylight.test/v1/related_test_classes?filters%5Bassociations_test_class_id%5D=123&limit=1
|
|
241
|
+
stub_request(:get, %r{filters%5Bassociations_test_class_id%5D=123}).to_return(body: [related].to_json)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it 'still fetches the associate object' do
|
|
245
|
+
resource = AssociationsTestClass.find(1)
|
|
246
|
+
|
|
247
|
+
resource.associate.should_not be_nil
|
|
248
|
+
resource.associate.id.should == 100
|
|
249
|
+
resource.associate.name.should == 'Laurel'
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
it 'sets the associate to a new object' do
|
|
253
|
+
resource = AssociationsTestClass.find(1)
|
|
254
|
+
resource.associate = RelatedTestClass.new(name: 'Rik Mayall')
|
|
255
|
+
|
|
256
|
+
resource.associate.name.should == 'Rik Mayall'
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it 'sets the associate foreign key' do
|
|
260
|
+
resource = AssociationsTestClass.find(1)
|
|
261
|
+
resource.associate = RelatedTestClass.new(id: 333, name: 'Rik Mayall')
|
|
262
|
+
|
|
263
|
+
resource.associate.associations_test_class_id.should == resource.id
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it 'sets the associate directly in the nested attributes hash' do
|
|
267
|
+
resource = AssociationsTestClass.find(1)
|
|
268
|
+
resource.associate = RelatedTestClass.new(id: 333, name: 'Rik Mayall')
|
|
269
|
+
|
|
270
|
+
resource.attributes['associate_attributes'].should == resource.associate
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
describe :remote do
|
|
275
|
+
|
|
276
|
+
def respond_with(data)
|
|
277
|
+
stub_request(:get, %r{#{AssociationsTestClass.site}}).to_return(body: data.to_json)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
let(:subject) { AssociationsTestClass.new }
|
|
281
|
+
|
|
282
|
+
it "loads data from the remote" do
|
|
283
|
+
respond_with({remote_stuff: {id: 2, foo: 'bar'}})
|
|
284
|
+
|
|
285
|
+
subject.remote_stuff.foo.should == 'bar'
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
it "handles collections" do
|
|
289
|
+
respond_with({remote_stuff: [{id: 2, foo: 'first'}, {id: 3, foo: 'second'}]})
|
|
290
|
+
|
|
291
|
+
subject.remote_stuff.first.foo.should == 'first'
|
|
292
|
+
subject.remote_stuff.last.foo.should == 'second'
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
it "caches the data" do
|
|
296
|
+
respond_with({remote_stuff: {cache: 'cachey cache'}})
|
|
297
|
+
|
|
298
|
+
subject.should_receive(:get).once.and_call_original
|
|
299
|
+
|
|
300
|
+
subject.remote_stuff.cache.should == 'cachey cache'
|
|
301
|
+
subject.remote_stuff.cache.should == 'cachey cache'
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it "handles metadata with an object" do
|
|
305
|
+
respond_with({remote_stuff: {id: 2, foo: 'bar'}, meta: {}})
|
|
306
|
+
|
|
307
|
+
subject.remote_stuff.foo.should == 'bar'
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it "handles metadata with a collection" do
|
|
311
|
+
respond_with({remote_stuff: [{id: 2, foo: 'first'}, {id: 3, foo: 'second'}], meta: {}})
|
|
312
|
+
|
|
313
|
+
subject.remote_stuff.first.foo.should == 'first'
|
|
314
|
+
subject.remote_stuff.last.foo.should == 'second'
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
it "returns data from the attributes if that already exists" do
|
|
318
|
+
subject.attributes[:remote_stuff] = 'wibble'
|
|
319
|
+
|
|
320
|
+
subject.remote_stuff.should == 'wibble'
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
end
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Daylight::Collection do
|
|
4
|
+
|
|
5
|
+
class CollectionTestClass < Daylight::API
|
|
6
|
+
self.password = nil
|
|
7
|
+
|
|
8
|
+
scopes :foo, :bar
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
before do
|
|
12
|
+
stub_request(:get, %r{#{CollectionTestClass.site}}).to_return(body: [].to_json)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe :metadata do
|
|
16
|
+
before do
|
|
17
|
+
data = { collection: [{name: 'one'}, {name: 'two'}], meta: {data: 'baz'} }
|
|
18
|
+
stub_request(:get, %r{#{CollectionTestClass.site}}).to_return(body: data.to_json)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'is retrieved before parsing' do
|
|
22
|
+
collection = CollectionTestClass.all
|
|
23
|
+
|
|
24
|
+
collection.metadata.should == {'data' => 'baz'}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'is passed to child elements' do
|
|
28
|
+
collection = CollectionTestClass.all
|
|
29
|
+
collection.each do |child|
|
|
30
|
+
child.metadata.should == {'data' => 'baz'}
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe :first_or_initialize do
|
|
36
|
+
|
|
37
|
+
describe 'with results' do
|
|
38
|
+
before do
|
|
39
|
+
data = [{name: 'one'}, {name: 'two'}]
|
|
40
|
+
stub_request(:get, %r{#{CollectionTestClass.site}}).to_return(body: data.to_json)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'returns the first result' do
|
|
44
|
+
result = CollectionTestClass.where(name: 'one').first_or_initialize
|
|
45
|
+
result.should be_kind_of(CollectionTestClass)
|
|
46
|
+
result.name.should == 'one'
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'reraises NoMethodErrors on initialize' do
|
|
51
|
+
CollectionTestClass.stub(:new).and_raise(NoMethodError)
|
|
52
|
+
|
|
53
|
+
expect { CollectionTestClass.where(name: 'one').first_or_initialize }.to \
|
|
54
|
+
raise_error(StandardError, 'Cannot create resource from resource type: CollectionTestClass')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'returns an unsaved instance' do
|
|
58
|
+
result = CollectionTestClass.where(name: 'two').first_or_initialize
|
|
59
|
+
|
|
60
|
+
result.should be_kind_of(CollectionTestClass)
|
|
61
|
+
result.should_not be_persisted
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'adds known parameter values to attributes' do
|
|
65
|
+
result = CollectionTestClass.where(name: 'two').first_or_initialize
|
|
66
|
+
|
|
67
|
+
result.should be_kind_of(CollectionTestClass)
|
|
68
|
+
result.should_not be_persisted
|
|
69
|
+
|
|
70
|
+
result.name.should == 'two'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'adds query parameter to prefix_options' do
|
|
74
|
+
result = CollectionTestClass.foo.bar.where(name: 'two').first_or_initialize
|
|
75
|
+
|
|
76
|
+
result.should be_kind_of(CollectionTestClass)
|
|
77
|
+
result.should_not be_persisted
|
|
78
|
+
|
|
79
|
+
result.prefix_options.should == {scopes: [:foo, :bar]}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'keeps collection parameters from being merged into attributes' do
|
|
83
|
+
result = CollectionTestClass.where(name: 'two').limit(10).offset(100).order(name: 'asc').first_or_initialize
|
|
84
|
+
|
|
85
|
+
result.should be_kind_of(CollectionTestClass)
|
|
86
|
+
result.should_not be_persisted
|
|
87
|
+
|
|
88
|
+
result.attributes[:limit].should be_nil
|
|
89
|
+
result.attributes[:offset].should be_nil
|
|
90
|
+
result.attributes[:order].should be_nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'leaves unknown paramters so they are merged into attributes' do
|
|
94
|
+
result = CollectionTestClass.find(:all, params: {a: 1, b: 2}).first_or_initialize
|
|
95
|
+
|
|
96
|
+
result.should be_kind_of(CollectionTestClass)
|
|
97
|
+
result.should_not be_persisted
|
|
98
|
+
|
|
99
|
+
result.a.should == 1
|
|
100
|
+
result.b.should == 2
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
describe :first_or_create do
|
|
105
|
+
|
|
106
|
+
describe 'with results' do
|
|
107
|
+
before do
|
|
108
|
+
data = [{name: 'one'}, {name: 'two'}]
|
|
109
|
+
stub_request(:get, %r{#{CollectionTestClass.site}}).to_return(body: data.to_json)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'returns the first result' do
|
|
113
|
+
result = CollectionTestClass.where(name: 'one').first_or_create
|
|
114
|
+
result.should be_kind_of(CollectionTestClass)
|
|
115
|
+
result.name.should == 'one'
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
describe 'with errors' do
|
|
120
|
+
before do
|
|
121
|
+
errors = {errors: {status: ["can't be blank", "is not included in the list"]} }
|
|
122
|
+
stub_request(:post, %r{#{CollectionTestClass.site}}).to_return(body: errors.to_json, status: 422)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'returns an unsaved instance with errors' do
|
|
126
|
+
result = CollectionTestClass.where(name: 'one').first_or_create
|
|
127
|
+
result.should be_kind_of(CollectionTestClass)
|
|
128
|
+
result.should_not be_persisted
|
|
129
|
+
|
|
130
|
+
result.name.should == 'one'
|
|
131
|
+
result.errors.full_messages.should == ["Status can't be blank", "Status is not included in the list"]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'reraises NoMethodErrors on initialize' do
|
|
135
|
+
CollectionTestClass.stub(:new).and_raise(NoMethodError)
|
|
136
|
+
|
|
137
|
+
expect { CollectionTestClass.where(name: 'one').first_or_create }.to \
|
|
138
|
+
raise_error(StandardError, 'Cannot build resource from resource type: CollectionTestClass')
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
describe 'with create' do
|
|
143
|
+
before do
|
|
144
|
+
resource = {id: 1, name: 'one'}
|
|
145
|
+
stub_request(:post, %r{#{CollectionTestClass.site}}).to_return(body: resource.to_json, status: 201)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'returns a saved instance' do
|
|
149
|
+
result = CollectionTestClass.where(name: 'one').first_or_create
|
|
150
|
+
|
|
151
|
+
result.should be_kind_of(CollectionTestClass)
|
|
152
|
+
result.should be_persisted
|
|
153
|
+
|
|
154
|
+
result.id.should == 1
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it 'adds known parameter values to attributes' do
|
|
158
|
+
result = CollectionTestClass.where(name: 'two').first_or_create
|
|
159
|
+
|
|
160
|
+
result.should be_kind_of(CollectionTestClass)
|
|
161
|
+
result.should be_persisted
|
|
162
|
+
|
|
163
|
+
result.name.should == 'one'
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
describe 'before save' do
|
|
167
|
+
before do
|
|
168
|
+
CollectionTestClass.any_instance.stub(save: true)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'adds query parameter to prefix_options before save' do
|
|
172
|
+
result = CollectionTestClass.foo.bar.where(name: 'two').first_or_initialize
|
|
173
|
+
|
|
174
|
+
result.should be_kind_of(CollectionTestClass)
|
|
175
|
+
result.should_not be_persisted
|
|
176
|
+
|
|
177
|
+
result.prefix_options.should == {scopes: [:foo, :bar]}
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'keeps collection parameters from being merged into attributes' do
|
|
181
|
+
result = CollectionTestClass.where(name: 'two').limit(10).offset(100).order(name: 'asc').first_or_create
|
|
182
|
+
|
|
183
|
+
result.should be_kind_of(CollectionTestClass)
|
|
184
|
+
result.should_not be_persisted
|
|
185
|
+
|
|
186
|
+
result.attributes[:limit].should be_nil
|
|
187
|
+
result.attributes[:offset].should be_nil
|
|
188
|
+
result.attributes[:order].should be_nil
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it 'keeps unknown paramters so they are merged into attributes' do
|
|
192
|
+
result = CollectionTestClass.find(:all, params: {a: 1, b: 2}).first_or_create
|
|
193
|
+
|
|
194
|
+
result.should be_kind_of(CollectionTestClass)
|
|
195
|
+
result.should_not be_persisted
|
|
196
|
+
|
|
197
|
+
result.a.should == 1
|
|
198
|
+
result.b.should == 2
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
describe 'after save' do
|
|
203
|
+
it 'has no prefix_options' do
|
|
204
|
+
result = CollectionTestClass.foo.bar.where(name: 'two').first_or_create
|
|
205
|
+
|
|
206
|
+
result.should be_kind_of(CollectionTestClass)
|
|
207
|
+
result.should be_persisted
|
|
208
|
+
|
|
209
|
+
result.prefix_options.should == {}
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it 'has no collection parameters' do
|
|
213
|
+
result = CollectionTestClass.where(name: 'two').limit(10).offset(100).order(name: 'asc').first_or_create
|
|
214
|
+
|
|
215
|
+
result.should be_kind_of(CollectionTestClass)
|
|
216
|
+
result.should be_persisted
|
|
217
|
+
|
|
218
|
+
result.attributes[:limit].should be_nil
|
|
219
|
+
result.attributes[:offset].should be_nil
|
|
220
|
+
result.attributes[:order].should be_nil
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it 'keeps unknown paramters' do
|
|
224
|
+
result = CollectionTestClass.find(:all, params: {a: 1, b: 2}).first_or_create
|
|
225
|
+
|
|
226
|
+
result.should be_kind_of(CollectionTestClass)
|
|
227
|
+
result.should be_persisted
|
|
228
|
+
|
|
229
|
+
result.a.should == 1
|
|
230
|
+
result.b.should == 2
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Daylight::Errors do
|
|
4
|
+
|
|
5
|
+
class BaseErrorTest < StandardError
|
|
6
|
+
def initialize resposne, message=nill
|
|
7
|
+
@message = 'base'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_s
|
|
11
|
+
@message.dup
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class ErrorTest < BaseErrorTest
|
|
16
|
+
include Daylight::Errors
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def mock_response headers, body
|
|
20
|
+
double(header: headers, body: body)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'ignores parsing with no body and content-type' do
|
|
24
|
+
error = ErrorTest.new(mock_response({}, nil))
|
|
25
|
+
|
|
26
|
+
error.messages.should == []
|
|
27
|
+
error.to_s.should == 'base'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'sets no message for unknown content-type' do
|
|
31
|
+
error = ErrorTest.new(mock_response({'content-type' => 'application/foo'}, 'bar error, no drink'))
|
|
32
|
+
|
|
33
|
+
error.messages.should == []
|
|
34
|
+
error.to_s.should == 'base'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
describe :xml do
|
|
39
|
+
let(:xml_error) { '<errors><error>message</error></errors>' }
|
|
40
|
+
let(:xml_errors) { '<errors><error>problem 1</error><error>problem 2</error></errors>' }
|
|
41
|
+
|
|
42
|
+
def xml_response body=nil
|
|
43
|
+
mock_response({'content-type' => 'application/xml; charset=utf-8'}, body)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'parses no error' do
|
|
47
|
+
error = ErrorTest.new(xml_response)
|
|
48
|
+
|
|
49
|
+
error.messages.should == []
|
|
50
|
+
error.to_s.should == 'base'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'parses one error' do
|
|
54
|
+
error = ErrorTest.new(xml_response(xml_error))
|
|
55
|
+
|
|
56
|
+
error.messages.should == ['message']
|
|
57
|
+
error.to_s.should == 'base Root Cause = message'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'parses multiple errors' do
|
|
61
|
+
error = ErrorTest.new(xml_response(xml_errors))
|
|
62
|
+
|
|
63
|
+
error.messages.should == ['problem 1', 'problem 2']
|
|
64
|
+
error.to_s.should == 'base Root Cause = problem 1, problem 2'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'handles decode errors and sets no messages' do
|
|
68
|
+
error = ErrorTest.new(xml_response('bad data'))
|
|
69
|
+
|
|
70
|
+
error.messages.should == []
|
|
71
|
+
error.to_s.should == 'base'
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe :json do
|
|
76
|
+
let(:json_error) { { errors: 'message'}.to_json }
|
|
77
|
+
let(:json_errors) { { errors: ['problem 1', 'problem 2'] }.to_json }
|
|
78
|
+
|
|
79
|
+
def json_response body=nil
|
|
80
|
+
mock_response({'content-type' => 'application/json; charset=utf-8'}, body)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'parses no error' do
|
|
84
|
+
error = ErrorTest.new(json_response)
|
|
85
|
+
|
|
86
|
+
error.messages.should == []
|
|
87
|
+
error.to_s.should == 'base'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'parses one error' do
|
|
91
|
+
error = ErrorTest.new(json_response(json_error))
|
|
92
|
+
|
|
93
|
+
error.messages.should == ['message']
|
|
94
|
+
error.to_s.should == 'base Root Cause = message'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'parses multiple errors' do
|
|
98
|
+
error = ErrorTest.new(json_response(json_errors))
|
|
99
|
+
|
|
100
|
+
error.messages.should == ['problem 1', 'problem 2']
|
|
101
|
+
error.to_s.should == 'base Root Cause = problem 1, problem 2'
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'handles decode errors and sets no messages' do
|
|
105
|
+
error = ErrorTest.new(json_response('<bad data>'))
|
|
106
|
+
|
|
107
|
+
error.messages.should == []
|
|
108
|
+
error.to_s.should == 'base'
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|