daylight 0.9.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +113 -0
  3. data/app/controllers/daylight_documentation/documentation_controller.rb +27 -0
  4. data/app/helpers/daylight_documentation/documentation_helper.rb +57 -0
  5. data/app/views/daylight_documentation/documentation/_header.haml +4 -0
  6. data/app/views/daylight_documentation/documentation/index.haml +12 -0
  7. data/app/views/daylight_documentation/documentation/model.haml +114 -0
  8. data/app/views/layouts/documentation.haml +22 -0
  9. data/config/routes.rb +8 -0
  10. data/doc/actions.md +70 -0
  11. data/doc/benchmarks.md +17 -0
  12. data/doc/contribute.md +80 -0
  13. data/doc/develop.md +1205 -0
  14. data/doc/environment.md +109 -0
  15. data/doc/example.md +3 -0
  16. data/doc/framework.md +31 -0
  17. data/doc/install.md +128 -0
  18. data/doc/principles.md +42 -0
  19. data/doc/testing.md +107 -0
  20. data/doc/usage.md +970 -0
  21. data/lib/daylight/api.rb +293 -0
  22. data/lib/daylight/associations.rb +247 -0
  23. data/lib/daylight/client_reloader.rb +45 -0
  24. data/lib/daylight/collection.rb +161 -0
  25. data/lib/daylight/errors.rb +94 -0
  26. data/lib/daylight/inflections.rb +7 -0
  27. data/lib/daylight/mock.rb +282 -0
  28. data/lib/daylight/read_only.rb +88 -0
  29. data/lib/daylight/refinements.rb +63 -0
  30. data/lib/daylight/reflection_ext.rb +67 -0
  31. data/lib/daylight/resource_proxy.rb +226 -0
  32. data/lib/daylight/version.rb +10 -0
  33. data/lib/daylight.rb +27 -0
  34. data/rails/daylight/api_controller.rb +354 -0
  35. data/rails/daylight/documentation.rb +13 -0
  36. data/rails/daylight/helpers.rb +32 -0
  37. data/rails/daylight/params.rb +23 -0
  38. data/rails/daylight/refiners.rb +186 -0
  39. data/rails/daylight/server.rb +29 -0
  40. data/rails/daylight/tasks.rb +37 -0
  41. data/rails/extensions/array_ext.rb +9 -0
  42. data/rails/extensions/autosave_association_fix.rb +49 -0
  43. data/rails/extensions/has_one_serializer_ext.rb +111 -0
  44. data/rails/extensions/inflections.rb +6 -0
  45. data/rails/extensions/nested_attributes_ext.rb +94 -0
  46. data/rails/extensions/read_only_attributes.rb +35 -0
  47. data/rails/extensions/render_json_meta.rb +99 -0
  48. data/rails/extensions/route_options.rb +47 -0
  49. data/rails/extensions/versioned_url_for.rb +22 -0
  50. data/spec/config/dependencies.rb +2 -0
  51. data/spec/config/factory_girl.rb +4 -0
  52. data/spec/config/simplecov_rcov.rb +26 -0
  53. data/spec/config/test_api.rb +1 -0
  54. data/spec/controllers/documentation_controller_spec.rb +24 -0
  55. data/spec/dummy/README.rdoc +28 -0
  56. data/spec/dummy/Rakefile +6 -0
  57. data/spec/dummy/app/assets/images/.keep +0 -0
  58. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  59. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  60. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  61. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  62. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  63. data/spec/dummy/app/mailers/.keep +0 -0
  64. data/spec/dummy/app/models/.keep +0 -0
  65. data/spec/dummy/app/models/concerns/.keep +0 -0
  66. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  67. data/spec/dummy/bin/bundle +3 -0
  68. data/spec/dummy/bin/rails +4 -0
  69. data/spec/dummy/bin/rake +4 -0
  70. data/spec/dummy/config/application.rb +24 -0
  71. data/spec/dummy/config/boot.rb +5 -0
  72. data/spec/dummy/config/database.yml +25 -0
  73. data/spec/dummy/config/environment.rb +5 -0
  74. data/spec/dummy/config/environments/development.rb +29 -0
  75. data/spec/dummy/config/environments/production.rb +80 -0
  76. data/spec/dummy/config/environments/test.rb +36 -0
  77. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  78. data/spec/dummy/config/initializers/daylight.rb +1 -0
  79. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  80. data/spec/dummy/config/initializers/inflections.rb +16 -0
  81. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  82. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  83. data/spec/dummy/config/initializers/session_store.rb +3 -0
  84. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  85. data/spec/dummy/config/locales/en.yml +23 -0
  86. data/spec/dummy/config/routes.rb +59 -0
  87. data/spec/dummy/config.ru +4 -0
  88. data/spec/dummy/lib/assets/.keep +0 -0
  89. data/spec/dummy/log/.keep +0 -0
  90. data/spec/dummy/public/404.html +58 -0
  91. data/spec/dummy/public/422.html +58 -0
  92. data/spec/dummy/public/500.html +57 -0
  93. data/spec/dummy/public/favicon.ico +0 -0
  94. data/spec/helpers/documentation_helper_spec.rb +82 -0
  95. data/spec/lib/daylight/api_spec.rb +178 -0
  96. data/spec/lib/daylight/associations_spec.rb +325 -0
  97. data/spec/lib/daylight/collection_spec.rb +235 -0
  98. data/spec/lib/daylight/errors_spec.rb +111 -0
  99. data/spec/lib/daylight/mock_spec.rb +144 -0
  100. data/spec/lib/daylight/read_only_spec.rb +118 -0
  101. data/spec/lib/daylight/refinements_spec.rb +80 -0
  102. data/spec/lib/daylight/reflection_ext_spec.rb +50 -0
  103. data/spec/lib/daylight/resource_proxy_spec.rb +325 -0
  104. data/spec/rails/daylight/api_controller_spec.rb +421 -0
  105. data/spec/rails/daylight/helpers_spec.rb +41 -0
  106. data/spec/rails/daylight/params_spec.rb +45 -0
  107. data/spec/rails/daylight/refiners_spec.rb +178 -0
  108. data/spec/rails/extensions/array_ext_spec.rb +51 -0
  109. data/spec/rails/extensions/has_one_serializer_ext_spec.rb +135 -0
  110. data/spec/rails/extensions/nested_attributes_ext_spec.rb +177 -0
  111. data/spec/rails/extensions/render_json_meta_spec.rb +140 -0
  112. data/spec/rails/extensions/route_options_spec.rb +309 -0
  113. data/spec/rails/extensions/versioned_url_for_spec.rb +46 -0
  114. data/spec/spec_helper.rb +43 -0
  115. data/spec/support/migration_helper.rb +40 -0
  116. 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