castle-her 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +17 -0
  5. data/.yardopts +2 -0
  6. data/CONTRIBUTING.md +26 -0
  7. data/Gemfile +10 -0
  8. data/LICENSE +7 -0
  9. data/README.md +1017 -0
  10. data/Rakefile +11 -0
  11. data/UPGRADE.md +110 -0
  12. data/castle-her.gemspec +30 -0
  13. data/gemfiles/Gemfile.activemodel-3.2.x +7 -0
  14. data/gemfiles/Gemfile.activemodel-4.0 +7 -0
  15. data/gemfiles/Gemfile.activemodel-4.1 +7 -0
  16. data/gemfiles/Gemfile.activemodel-4.2 +7 -0
  17. data/gemfiles/Gemfile.activemodel-5.0.x +7 -0
  18. data/lib/castle-her.rb +20 -0
  19. data/lib/castle-her/api.rb +113 -0
  20. data/lib/castle-her/collection.rb +12 -0
  21. data/lib/castle-her/errors.rb +27 -0
  22. data/lib/castle-her/json_api/model.rb +46 -0
  23. data/lib/castle-her/middleware.rb +12 -0
  24. data/lib/castle-her/middleware/accept_json.rb +17 -0
  25. data/lib/castle-her/middleware/first_level_parse_json.rb +36 -0
  26. data/lib/castle-her/middleware/json_api_parser.rb +36 -0
  27. data/lib/castle-her/middleware/parse_json.rb +21 -0
  28. data/lib/castle-her/middleware/second_level_parse_json.rb +36 -0
  29. data/lib/castle-her/model.rb +75 -0
  30. data/lib/castle-her/model/associations.rb +141 -0
  31. data/lib/castle-her/model/associations/association.rb +103 -0
  32. data/lib/castle-her/model/associations/association_proxy.rb +45 -0
  33. data/lib/castle-her/model/associations/belongs_to_association.rb +96 -0
  34. data/lib/castle-her/model/associations/has_many_association.rb +100 -0
  35. data/lib/castle-her/model/associations/has_one_association.rb +79 -0
  36. data/lib/castle-her/model/attributes.rb +284 -0
  37. data/lib/castle-her/model/base.rb +33 -0
  38. data/lib/castle-her/model/deprecated_methods.rb +61 -0
  39. data/lib/castle-her/model/http.rb +114 -0
  40. data/lib/castle-her/model/introspection.rb +65 -0
  41. data/lib/castle-her/model/nested_attributes.rb +45 -0
  42. data/lib/castle-her/model/orm.rb +207 -0
  43. data/lib/castle-her/model/parse.rb +216 -0
  44. data/lib/castle-her/model/paths.rb +126 -0
  45. data/lib/castle-her/model/relation.rb +164 -0
  46. data/lib/castle-her/version.rb +3 -0
  47. data/spec/api_spec.rb +114 -0
  48. data/spec/collection_spec.rb +26 -0
  49. data/spec/json_api/model_spec.rb +166 -0
  50. data/spec/middleware/accept_json_spec.rb +10 -0
  51. data/spec/middleware/first_level_parse_json_spec.rb +62 -0
  52. data/spec/middleware/json_api_parser_spec.rb +32 -0
  53. data/spec/middleware/second_level_parse_json_spec.rb +35 -0
  54. data/spec/model/associations/association_proxy_spec.rb +31 -0
  55. data/spec/model/associations_spec.rb +504 -0
  56. data/spec/model/attributes_spec.rb +389 -0
  57. data/spec/model/callbacks_spec.rb +145 -0
  58. data/spec/model/dirty_spec.rb +91 -0
  59. data/spec/model/http_spec.rb +158 -0
  60. data/spec/model/introspection_spec.rb +76 -0
  61. data/spec/model/nested_attributes_spec.rb +134 -0
  62. data/spec/model/orm_spec.rb +506 -0
  63. data/spec/model/parse_spec.rb +345 -0
  64. data/spec/model/paths_spec.rb +347 -0
  65. data/spec/model/relation_spec.rb +226 -0
  66. data/spec/model/validations_spec.rb +42 -0
  67. data/spec/model_spec.rb +44 -0
  68. data/spec/spec_helper.rb +26 -0
  69. data/spec/support/extensions/array.rb +5 -0
  70. data/spec/support/extensions/hash.rb +5 -0
  71. data/spec/support/macros/her_macros.rb +17 -0
  72. data/spec/support/macros/model_macros.rb +36 -0
  73. data/spec/support/macros/request_macros.rb +27 -0
  74. metadata +290 -0
@@ -0,0 +1,389 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
+
4
+ describe Her::Model::Attributes do
5
+ context "mapping data to Ruby objects" do
6
+ before { spawn_model "Foo::User" }
7
+
8
+ it "handles new resource" do
9
+ @new_user = Foo::User.new(:fullname => "Tobias Fünke")
10
+ expect(@new_user.new?).to be_truthy
11
+ expect(@new_user.fullname).to eq("Tobias Fünke")
12
+ end
13
+
14
+ it "accepts new resource with strings as hash keys" do
15
+ @new_user = Foo::User.new('fullname' => "Tobias Fünke")
16
+ expect(@new_user.fullname).to eq("Tobias Fünke")
17
+ end
18
+
19
+ it "handles method missing for getter" do
20
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
21
+ expect { @new_user.unknown_method_for_a_user }.to raise_error(NoMethodError)
22
+ expect { @new_user.fullname }.not_to raise_error()
23
+ end
24
+
25
+ it "handles method missing for setter" do
26
+ @new_user = Foo::User.new
27
+ expect { @new_user.fullname = "Tobias Fünke" }.not_to raise_error()
28
+ end
29
+
30
+ it "handles method missing for query" do
31
+ @new_user = Foo::User.new
32
+ expect { @new_user.fullname? }.not_to raise_error()
33
+ end
34
+
35
+ it "handles respond_to for getter" do
36
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
37
+ expect(@new_user).not_to respond_to(:unknown_method_for_a_user)
38
+ expect(@new_user).to respond_to(:fullname)
39
+ end
40
+
41
+ it "handles respond_to for setter" do
42
+ @new_user = Foo::User.new
43
+ expect(@new_user).to respond_to(:fullname=)
44
+ end
45
+
46
+ it "handles respond_to for query" do
47
+ @new_user = Foo::User.new
48
+ expect(@new_user).to respond_to(:fullname?)
49
+ end
50
+
51
+ it "handles has_attribute? for getter" do
52
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
53
+ expect(@new_user).not_to have_attribute(:unknown_method_for_a_user)
54
+ expect(@new_user).to have_attribute(:fullname)
55
+ end
56
+
57
+ it "handles get_attribute for getter" do
58
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
59
+ expect(@new_user.get_attribute(:unknown_method_for_a_user)).to eq(nil)
60
+ expect(@new_user.get_attribute(:fullname)).to eq('Mayonegg')
61
+ end
62
+
63
+ it "handles get_attribute for getter with dash" do
64
+ @new_user = Foo::User.new(:'life-span' => '3 years')
65
+ expect(@new_user.get_attribute(:unknown_method_for_a_user)).to eq(nil)
66
+ expect(@new_user.get_attribute(:'life-span')).to eq('3 years')
67
+ end
68
+ end
69
+
70
+
71
+ context "assigning new resource data" do
72
+ before do
73
+ spawn_model "Foo::User"
74
+ @user = Foo::User.new(:active => false)
75
+ end
76
+
77
+ it "handles data update through #assign_attributes" do
78
+ @user.assign_attributes :active => true
79
+ expect(@user).to be_active
80
+ end
81
+ end
82
+
83
+ context "checking resource equality" do
84
+ before do
85
+ Her::API.setup :url => "https://api.example.com" do |builder|
86
+ builder.use Her::Middleware::FirstLevelParseJSON
87
+ builder.use Faraday::Request::UrlEncoded
88
+ builder.adapter :test do |stub|
89
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke" }.to_json] }
90
+ stub.get("/users/2") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
91
+ stub.get("/admins/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke" }.to_json] }
92
+ end
93
+ end
94
+
95
+ spawn_model "Foo::User"
96
+ spawn_model "Foo::Admin"
97
+ end
98
+
99
+ let(:user) { Foo::User.find(1) }
100
+
101
+ it "returns true for the exact same object" do
102
+ expect(user).to eq(user)
103
+ end
104
+
105
+ it "returns true for the same resource via find" do
106
+ expect(user).to eq(Foo::User.find(1))
107
+ end
108
+
109
+ it "returns true for the same class with identical data" do
110
+ expect(user).to eq(Foo::User.new(:id => 1, :fullname => "Lindsay Fünke"))
111
+ end
112
+
113
+ it "returns true for a different resource with the same data" do
114
+ expect(user).to eq(Foo::Admin.find(1))
115
+ end
116
+
117
+ it "returns false for the same class with different data" do
118
+ expect(user).not_to eq(Foo::User.new(:id => 2, :fullname => "Tobias Fünke"))
119
+ end
120
+
121
+ it "returns false for a non-resource with the same data" do
122
+ fake_user = double(:data => { :id => 1, :fullname => "Lindsay Fünke" })
123
+ expect(user).not_to eq(fake_user)
124
+ end
125
+
126
+ it "delegates eql? to ==" do
127
+ other = Object.new
128
+ expect(user).to receive(:==).with(other).and_return(true)
129
+ expect(user.eql?(other)).to be_truthy
130
+ end
131
+
132
+ it "treats equal resources as equal for Array#uniq" do
133
+ user2 = Foo::User.find(1)
134
+ expect([user, user2].uniq).to eq([user])
135
+ end
136
+
137
+ it "treats equal resources as equal for hash keys" do
138
+ Foo::User.find(1)
139
+ hash = { user => true }
140
+ hash[Foo::User.find(1)] = false
141
+ expect(hash.size).to eq(1)
142
+ expect(hash).to eq({ user => false })
143
+ end
144
+ end
145
+
146
+ context "handling metadata and errors" do
147
+ before do
148
+ Her::API.setup :url => "https://api.example.com" do |builder|
149
+ builder.use Her::Middleware::FirstLevelParseJSON
150
+ builder.adapter :test do |stub|
151
+ stub.post("/users") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
152
+ end
153
+ end
154
+
155
+ spawn_model 'Foo::User' do
156
+ store_response_errors :errors
157
+ store_metadata :my_data
158
+ end
159
+
160
+ @user = Foo::User.new(:_errors => ["Foo", "Bar"], :_metadata => { :secret => true })
161
+ end
162
+
163
+ it "should return response_errors stored in the method provided by `store_response_errors`" do
164
+ expect(@user.errors).to eq(["Foo", "Bar"])
165
+ end
166
+
167
+ it "should remove the default method for errors" do
168
+ expect { @user.response_errors }.to raise_error(NoMethodError)
169
+ end
170
+
171
+ it "should return metadata stored in the method provided by `store_metadata`" do
172
+ expect(@user.my_data).to eq({ :secret => true })
173
+ end
174
+
175
+ it "should remove the default method for metadata" do
176
+ expect { @user.metadata }.to raise_error(NoMethodError)
177
+ end
178
+
179
+ it "should work with #save" do
180
+ @user.assign_attributes(:fullname => "Tobias Fünke")
181
+ @user.save
182
+ expect { @user.metadata }.to raise_error(NoMethodError)
183
+ expect(@user.my_data).to be_empty
184
+ expect(@user.errors).to be_empty
185
+ end
186
+ end
187
+
188
+ context "overwriting default attribute methods" do
189
+ context "for getter method" do
190
+ before do
191
+ Her::API.setup :url => "https://api.example.com" do |builder|
192
+ builder.use Her::Middleware::FirstLevelParseJSON
193
+ builder.adapter :test do |stub|
194
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :document => { :url => "http://example.com" } }.to_json] }
195
+ end
196
+ end
197
+
198
+ spawn_model 'Foo::User' do
199
+ def document
200
+ @attributes[:document][:url]
201
+ end
202
+ end
203
+ end
204
+
205
+ it "bypasses Her's method" do
206
+ @user = Foo::User.find(1)
207
+ expect(@user.document).to eq("http://example.com")
208
+
209
+ @user = Foo::User.find(1)
210
+ expect(@user.document).to eq("http://example.com")
211
+ end
212
+ end
213
+
214
+ context "for setter method" do
215
+ before do
216
+ Her::API.setup :url => "https://api.example.com" do |builder|
217
+ builder.use Her::Middleware::FirstLevelParseJSON
218
+ builder.adapter :test do |stub|
219
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :document => { :url => "http://example.com" } }.to_json] }
220
+ end
221
+ end
222
+
223
+ spawn_model 'Foo::User' do
224
+ def document=(document)
225
+ @attributes[:document] = document[:url]
226
+ end
227
+ end
228
+ end
229
+
230
+ it "bypasses Her's method" do
231
+ @user = Foo::User.find(1)
232
+ expect(@user.document).to eq("http://example.com")
233
+
234
+ @user = Foo::User.find(1)
235
+ expect(@user.document).to eq("http://example.com")
236
+ end
237
+ end
238
+
239
+ context "for predicate method" do
240
+ before do
241
+ Her::API.setup :url => "https://api.example.com" do |builder|
242
+ builder.use Her::Middleware::FirstLevelParseJSON
243
+ builder.adapter :test do |stub|
244
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke", :document => { :url => nil } }.to_json] }
245
+ stub.get("/users/2") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :document => { :url => "http://example.com" } }.to_json] }
246
+ end
247
+ end
248
+
249
+ spawn_model 'Foo::User' do
250
+ def document?
251
+ document[:url].present?
252
+ end
253
+ end
254
+ end
255
+
256
+ it "byoasses Her's method" do
257
+ @user = Foo::User.find(1)
258
+ expect(@user.document?).to be_falsey
259
+
260
+ @user = Foo::User.find(1)
261
+ expect(@user.document?).to be_falsey
262
+
263
+ @user = Foo::User.find(2)
264
+ expect(@user.document?).to be_truthy
265
+ end
266
+ end
267
+ end
268
+
269
+ context "attributes class method" do
270
+ before do
271
+ spawn_model 'Foo::User' do
272
+ attributes :fullname, :document
273
+ end
274
+ end
275
+
276
+ context "instance" do
277
+ subject { Foo::User.new }
278
+
279
+ it { should respond_to(:fullname) }
280
+ it { should respond_to(:fullname=) }
281
+ it { should respond_to(:fullname?) }
282
+ end
283
+
284
+ it "defines setter that affects @attributes" do
285
+ user = Foo::User.new
286
+ user.fullname = 'Tobias Fünke'
287
+ expect(user.attributes[:fullname]).to eq('Tobias Fünke')
288
+ end
289
+
290
+ it "defines getter that reads @attributes" do
291
+ user = Foo::User.new
292
+ user.assign_attributes(fullname: 'Tobias Fünke')
293
+ expect(user.fullname).to eq('Tobias Fünke')
294
+ end
295
+
296
+ it "defines predicate that reads @attributes" do
297
+ user = Foo::User.new
298
+ expect(user.fullname?).to be_falsey
299
+ user.assign_attributes(fullname: 'Tobias Fünke')
300
+ expect(user.fullname?).to be_truthy
301
+ end
302
+
303
+ context "when attribute methods are already defined" do
304
+ before do
305
+ class AbstractUser
306
+ attr_accessor :fullname
307
+
308
+ def fullname?
309
+ @fullname.present?
310
+ end
311
+ end
312
+ @spawned_models << :AbstractUser
313
+
314
+ spawn_model 'Foo::User', super_class: AbstractUser do
315
+ attributes :fullname
316
+ end
317
+ end
318
+
319
+ it "overrides getter method" do
320
+ expect(Foo::User.generated_attribute_methods.instance_methods).to include(:fullname)
321
+ end
322
+
323
+ it "overrides setter method" do
324
+ expect(Foo::User.generated_attribute_methods.instance_methods).to include(:fullname=)
325
+ end
326
+
327
+ it "overrides predicate method" do
328
+ expect(Foo::User.generated_attribute_methods.instance_methods).to include(:fullname?)
329
+ end
330
+
331
+ it "defines setter that affects @attributes" do
332
+ user = Foo::User.new
333
+ user.fullname = 'Tobias Fünke'
334
+ expect(user.attributes[:fullname]).to eq('Tobias Fünke')
335
+ end
336
+
337
+ it "defines getter that reads @attributes" do
338
+ user = Foo::User.new
339
+ user.attributes[:fullname] = 'Tobias Fünke'
340
+ expect(user.fullname).to eq('Tobias Fünke')
341
+ end
342
+
343
+ it "defines predicate that reads @attributes" do
344
+ user = Foo::User.new
345
+ expect(user.fullname?).to be_falsey
346
+ user.attributes[:fullname] = 'Tobias Fünke'
347
+ expect(user.fullname?).to be_truthy
348
+ end
349
+ end
350
+
351
+ if ActiveModel::VERSION::MAJOR < 4
352
+ it "creates a new mutex" do
353
+ expect(Mutex).to receive(:new).once.and_call_original
354
+ spawn_model 'Foo::User' do
355
+ attributes :fullname
356
+ end
357
+ Foo::User.attribute_methods_mutex.to_not eq(Foo::User.generated_attribute_methods)
358
+ end
359
+
360
+ it "works well with Module#synchronize monkey patched by ActiveSupport" do
361
+ Module.class_eval do
362
+ def synchronize(*args)
363
+ raise 'gotcha!'
364
+ end
365
+ end
366
+ expect(Mutex).to receive(:new).once.and_call_original
367
+ spawn_model 'Foo::User' do
368
+ attributes :fullname
369
+ end
370
+ Foo::User.attribute_methods_mutex.to_not eq(Foo::User.generated_attribute_methods)
371
+ Module.class_eval do
372
+ undef :synchronize
373
+ end
374
+ end
375
+ else
376
+ it "uses ActiveModel's mutex" do
377
+ expect(Foo::User.attribute_methods_mutex).to eq(Foo::User.generated_attribute_methods)
378
+ end
379
+ end
380
+
381
+ it "uses a mutex" do
382
+ spawn_model 'Foo::User'
383
+ expect(Foo::User.attribute_methods_mutex).to receive(:synchronize).once.and_call_original
384
+ Foo::User.class_eval do
385
+ attributes :fullname, :documents
386
+ end
387
+ end
388
+ end
389
+ end
@@ -0,0 +1,145 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
+
4
+ describe "Her::Model and ActiveModel::Callbacks" do
5
+ before do
6
+ Her::API.setup :url => "https://api.example.com" do |builder|
7
+ builder.use Her::Middleware::FirstLevelParseJSON
8
+ end
9
+
10
+ spawn_model "Foo::User"
11
+ end
12
+
13
+ context :before_save do
14
+ subject { Foo::User.create(:name => "Tobias Funke") }
15
+ before do
16
+ Her::API.default_api.connection.adapter :test do |stub|
17
+ stub.post("/users") { |env| [200, {}, { :id => 1, :name => env[:body][:name] }.to_json] }
18
+ stub.put("/users/1") { |env| [200, {}, { :id => 1, :name => env[:body][:name] }.to_json] }
19
+ end
20
+ end
21
+
22
+ context "when using a symbol callback" do
23
+ before do
24
+ class Foo::User
25
+ before_save :alter_name
26
+ def alter_name; self.name.upcase!; end
27
+ end
28
+ end
29
+
30
+ its(:name) { should == "TOBIAS FUNKE" }
31
+ end
32
+
33
+ context "when using a block callback" do
34
+ before do
35
+ class Foo::User
36
+ before_save lambda { self.name.upcase! }
37
+ end
38
+ end
39
+
40
+ its(:name) { should == "TOBIAS FUNKE" }
41
+ end
42
+
43
+ context "when changing a value of an existing resource in a callback" do
44
+ before do
45
+ class Foo::User
46
+ before_save :alter_name
47
+ def alter_name
48
+ self.name = "Lumberjack" if persisted?
49
+ end
50
+ end
51
+ end
52
+
53
+ it "should call the server with the canged value" do
54
+ expect(subject.name).to eq("Tobias Funke")
55
+ subject.save
56
+ expect(subject.name).to eq("Lumberjack")
57
+ end
58
+ end
59
+ end
60
+
61
+ context :before_create do
62
+ subject { Foo::User.create(:name => "Tobias Funke") }
63
+ before do
64
+ Her::API.default_api.connection.adapter :test do |stub|
65
+ stub.post("/users") { |env| [200, {}, { :id => 1, :name => env[:body][:name] }.to_json] }
66
+ end
67
+ end
68
+
69
+ context "when using a symbol callback" do
70
+ before do
71
+ class Foo::User
72
+ before_create :alter_name
73
+ def alter_name; self.name.upcase!; end
74
+ end
75
+ end
76
+
77
+ its(:name) { should == "TOBIAS FUNKE" }
78
+ end
79
+
80
+ context "when using a block callback" do
81
+ before do
82
+ class Foo::User
83
+ before_create lambda { self.name.upcase! }
84
+ end
85
+ end
86
+
87
+ its(:name) { should == "TOBIAS FUNKE" }
88
+ end
89
+ end
90
+
91
+ context :after_find do
92
+ subject { Foo::User.find(1) }
93
+ before do
94
+ Her::API.default_api.connection.adapter :test do |stub|
95
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Funke" }.to_json] }
96
+ end
97
+ end
98
+
99
+ context "when using a symbol callback" do
100
+ before do
101
+ class Foo::User
102
+ after_find :alter_name
103
+ def alter_name; self.name.upcase!; end
104
+ end
105
+ end
106
+
107
+ its(:name) { should == "TOBIAS FUNKE" }
108
+ end
109
+
110
+ context "when using a block callback" do
111
+ before do
112
+ class Foo::User
113
+ after_find lambda { self.name.upcase! }
114
+ end
115
+ end
116
+
117
+ its(:name) { should == "TOBIAS FUNKE" }
118
+ end
119
+ end
120
+
121
+ context :after_initialize do
122
+ subject { Foo::User.new(:name => "Tobias Funke") }
123
+
124
+ context "when using a symbol callback" do
125
+ before do
126
+ class Foo::User
127
+ after_initialize :alter_name
128
+ def alter_name; self.name.upcase!; end
129
+ end
130
+ end
131
+
132
+ its(:name) { should == "TOBIAS FUNKE" }
133
+ end
134
+
135
+ context "when using a block callback" do
136
+ before do
137
+ class Foo::User
138
+ after_initialize lambda { self.name.upcase! }
139
+ end
140
+ end
141
+
142
+ its(:name) { should == "TOBIAS FUNKE" }
143
+ end
144
+ end
145
+ end