api_resource 0.6.4 → 0.6.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- api_resource (0.6.3)
4
+ api_resource (0.6.4)
5
5
  colorize
6
6
  differ
7
7
  json
@@ -28,7 +28,9 @@ module ApiResource
28
28
  klass.api_resource_generated_methods.module_eval <<-EOE, __FILE__, __LINE__ + 1
29
29
  def #{assoc_name}
30
30
  @attributes_cache[:#{assoc_name}] ||= begin
31
- instance = #{self}.new(#{associated_class}, self)
31
+ instance = #{self}.new(
32
+ #{associated_class}, self, #{opts}
33
+ )
32
34
  if @attributes[:#{assoc_name}].present?
33
35
  instance.internal_object = @attributes[:#{assoc_name}]
34
36
  end
@@ -70,8 +72,8 @@ module ApiResource
70
72
 
71
73
  public
72
74
 
73
- def initialize(klass, owner, opts = {})
74
-
75
+ def initialize(klass, owner, options = {})
76
+
75
77
  # the base class for our scope, e.g. ApiResource::SomeClass
76
78
  @klass = klass.is_a?(String) ? klass.constantize : klass
77
79
 
@@ -79,8 +81,9 @@ module ApiResource
79
81
  @klass.load_resource_definition
80
82
 
81
83
  @owner = owner
82
-
83
- self.internal_object = opts
84
+
85
+ # store our options
86
+ @options = options
84
87
  end
85
88
 
86
89
  def ttl
@@ -2,11 +2,23 @@ module ApiResource
2
2
  module Associations
3
3
  class BelongsToRemoteObjectProxy < SingleObjectProxy
4
4
 
5
- def initialize(klass, owner)
6
- super(klass, owner)
7
-
8
- # now if we have an owner and a foreign key, we set the data up to load
9
- if key = owner.send(self.klass.to_s.foreign_key)
5
+ attr_reader :foreign_key
6
+
7
+
8
+ # sets some defaults for the foreign key name for this association
9
+ def self.define_association_as_attribute(klass, assoc_name, opts = {})
10
+ opts["foreign_key"] = self.foreign_key_name(assoc_name)
11
+ super(klass, assoc_name, opts)
12
+ end
13
+
14
+ # constructor
15
+ def initialize(klass, owner, opts = {})
16
+ super
17
+
18
+ @foreign_key = opts["foreign_key"] || @klass.to_s.foreign_key
19
+ # now if we have an owner and a foreign key, we set
20
+ # the data up to load
21
+ if key = owner.send(self.foreign_key)
10
22
  self.remote_path = self.klass.element_path(key)
11
23
  end
12
24
  end
@@ -30,17 +30,10 @@ module ApiResource
30
30
 
31
31
  # because of how this works we use a multi object proxy and return the first element
32
32
  def to_condition
33
- ApiResource::Conditions::MultiObjectAssociationCondition.new(
33
+ ApiResource::Conditions::SingleObjectAssociationCondition.new(
34
34
  self.klass, self.remote_path
35
35
  )
36
36
  end
37
-
38
- def load(opts = {})
39
- res = Array.wrap(self.to_condition.load).first
40
- @loaded = true
41
- res
42
- end
43
-
44
37
  end
45
38
  end
46
39
  end
@@ -6,12 +6,6 @@ module ApiResource
6
6
 
7
7
  include Enumerable
8
8
 
9
- # override the constructor to set data to nil by
10
- # default
11
- def initialize(klass, owner, data = nil)
12
- super
13
- end
14
-
15
9
  def all
16
10
  self.internal_object
17
11
  end
@@ -14,14 +14,13 @@ module ApiResource
14
14
  end
15
15
 
16
16
  def internal_object
17
- unless instance_variable_defined?(:@internal_object)
18
- if self.remote_path.present?
19
- instance_variable_set(:@internal_object, self.load)
17
+ @internal_object ||= begin
18
+ if self.remote_path.present? && !self.loaded?
19
+ self.load
20
20
  else
21
- instance_variable_set(:@internal_object, nil)
21
+ nil
22
22
  end
23
23
  end
24
- instance_variable_get(:@internal_object)
25
24
  end
26
25
 
27
26
  def internal_object=(contents)
@@ -67,7 +66,9 @@ module ApiResource
67
66
  def to_condition
68
67
  obj = nil
69
68
  obj = self.internal_object if self.loaded?
70
- ApiResource::Conditions::SingleObjectAssociationCondition.new(self.klass, self.remote_path, obj)
69
+ ApiResource::Conditions::SingleObjectAssociationCondition.new(
70
+ self.klass, self.remote_path, obj
71
+ )
71
72
  end
72
73
 
73
74
  # Should make a proper conditions object and call find on it
@@ -185,6 +185,8 @@ module ApiResource
185
185
  end
186
186
 
187
187
  def define_association_as_attribute(assoc_type, assoc_name, opts)
188
+ opts.stringify_keys!
189
+
188
190
  id_method_name = association_foreign_key_field(
189
191
  assoc_name, assoc_type
190
192
  )
@@ -209,10 +211,9 @@ module ApiResource
209
211
  klass = klass.constantize
210
212
 
211
213
  # gives us the namespaced classname
212
- opts[:class_name] = self.find_namespaced_class_name(
213
- opts[:class_name] || assoc_name.to_s.classify
214
+ opts["class_name"] = self.find_namespaced_class_name(
215
+ opts["class_name"] || assoc_name.to_s.classify
214
216
  )
215
-
216
217
  klass.define_association_as_attribute(self, assoc_name, opts)
217
218
  end
218
219
 
@@ -264,6 +264,20 @@ module ApiResource
264
264
 
265
265
  return val
266
266
  end
267
+
268
+ private
269
+
270
+ # this is here for compatibility with ActiveModel::AttributeMethods
271
+ # it is the fallback called in method_missing
272
+ def attribute(name)
273
+ read_attribute(name)
274
+ end
275
+
276
+ # this is here for compatibility with ActiveModel::AttributeMethods
277
+ # it is the fallback called in method_missing
278
+ def attribute=(name, val)
279
+ write_attribute(name, val)
280
+ end
267
281
 
268
282
  end
269
283
 
@@ -22,6 +22,9 @@ module ApiResource
22
22
  data = self.klass.connection.get(self.build_load_path)
23
23
  return [] if data.blank?
24
24
 
25
+ # handle non-array data for more flexibility in our endpoints
26
+ data = [data] unless data.is_a?(Array)
27
+
25
28
  @internal_object = self.klass.instantiate_collection(data)
26
29
  end
27
30
 
@@ -22,6 +22,11 @@ module ApiResource
22
22
  data = self.klass.connection.get(self.build_load_path)
23
23
 
24
24
  return nil if data.blank?
25
+
26
+ # we want to handle an array if we get one back from our endpoint
27
+ # this allows for more flexibility
28
+ data = data.first if data.is_a?(Array)
29
+
25
30
  @internal_object = self.klass.instantiate_record(data)
26
31
  end
27
32
 
@@ -1,3 +1,3 @@
1
1
  module ApiResource
2
- VERSION = "0.6.4"
2
+ VERSION = "0.6.5"
3
3
  end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ module ApiResource
4
+ module Associations
5
+
6
+ describe BelongsToRemoteObjectProxy do
7
+
8
+ before(:all) do
9
+ TestResource.reload_resource_definition
10
+ end
11
+
12
+ context "#remote_path" do
13
+
14
+ it "allows for custom named relationships and constructs the
15
+ correct remote path" do
16
+
17
+ tr = TestResource.new(:custom_name_id => 123)
18
+ tr.custom_name.remote_path.should eql(
19
+ "/belongs_to_objects/123.json"
20
+ )
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -5,6 +5,10 @@ module ApiResource
5
5
 
6
6
  describe HasManyRemoteObjectProxy do
7
7
 
8
+ before(:all) do
9
+ TestResource.reload_resource_definition
10
+ end
11
+
8
12
  context "#<<" do
9
13
 
10
14
  it "implements the shift operator" do
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ module ApiResource
4
+ module Associations
5
+
6
+ describe HasOneRemoteObjectProxy do
7
+
8
+ before(:all) do
9
+ TestResource.reload_resource_definition
10
+ end
11
+
12
+ context "#load" do
13
+
14
+ it "correctly loads data from an endpoint that returns
15
+ a single record" do
16
+
17
+ tr = TestResource.new(
18
+ :has_one_object => {:service_uri => "/has_one_objects/1.json"}
19
+ )
20
+
21
+ tr.has_one_object.internal_object.should be_instance_of(
22
+ HasOneObject
23
+ )
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -9,12 +9,14 @@ describe "Associations" do
9
9
  end
10
10
 
11
11
  let(:ap) do
12
- Associations::SingleObjectProxy.new(
12
+ res = Associations::SingleObjectProxy.new(
13
13
  "TestResource",
14
- HasManyObject.new, {
15
- :service_uri => '/single_object_association'
16
- }
14
+ HasManyObject.new
17
15
  )
16
+ res.internal_object = {
17
+ :service_uri => '/single_object_association'
18
+ }
19
+ res
18
20
  end
19
21
 
20
22
  context "Comparison" do
@@ -238,10 +240,11 @@ describe "Associations" do
238
240
  it "should be able to extract a service uri from the contents hash" do
239
241
  ap = Associations::SingleObjectProxy.new(
240
242
  "TestResource",
241
- HasManyObject.new, {
242
- :service_uri => '/path'
243
- }
243
+ HasManyObject.new
244
244
  )
245
+ ap.internal_object = {
246
+ :service_uri => '/path'
247
+ }
245
248
  ap.remote_path.should eql("/path")
246
249
  end
247
250
 
@@ -251,11 +254,13 @@ describe "Associations" do
251
254
  TestResource.define_attributes :test
252
255
  ap = Associations::SingleObjectProxy.new(
253
256
  "TestResource",
254
- HasManyObject.new, {
255
- :service_uri => '/path',
256
- :test => "testval"
257
- }
257
+ HasManyObject.new
258
258
  )
259
+
260
+ ap.internal_object = {
261
+ :service_uri => '/path',
262
+ :test => "testval"
263
+ }
259
264
  ap.scope?("test").should be_false
260
265
  ap.test.should eql("testval")
261
266
 
@@ -302,9 +307,9 @@ describe "Associations" do
302
307
  it "should be able to recognize a settings hash if it has a service_uri" do
303
308
  ap = Associations::MultiObjectProxy.new(
304
309
  "TestResource",
305
- BelongsToObject.new,
306
- [{:service_uri => "/route"}]
310
+ BelongsToObject.new
307
311
  )
312
+ ap.internal_object = [{:service_uri => "/route"}]
308
313
  ap.remote_path.should eql("/route")
309
314
  end
310
315
 
@@ -312,40 +317,31 @@ describe "Associations" do
312
317
  Associations::MultiObjectProxy.remote_path_element = :the_element
313
318
  ap = Associations::MultiObjectProxy.new(
314
319
  "TestResource",
315
- BelongsToObject.new,
316
- [{:the_element => "/route"}]
320
+ BelongsToObject.new
317
321
  )
322
+ ap.internal_object = [{:the_element => "/route"}]
318
323
  ap.remote_path.should eql("/route")
319
324
  end
320
325
 
321
326
  end
322
327
 
323
328
  context "Loading hash contents" do
324
- it "should not be able to load a hash without a 'service_uri'" do
325
- lambda {
326
- Associations::MultiObjectProxy.new(
327
- "TestResource",
328
- BelongsToObject,
329
- {:hi => 3}
330
- )
331
- }.should raise_error
332
- end
333
329
 
334
330
  it "should be able to recognize settings from a hash" do
335
331
  ap = Associations::MultiObjectProxy.new(
336
332
  "TestResource",
337
- BelongsToObject,
338
- {:service_uri => "/route"}
333
+ BelongsToObject
339
334
  )
335
+ ap.internal_object = {:service_uri => "/route"}
340
336
  ap.remote_path.should eql("/route")
341
337
  end
342
338
 
343
339
  it "should be able to recognize settings from a hash as a string" do
344
340
  ap = Associations::MultiObjectProxy.new(
345
341
  "TestResource",
346
- BelongsToObject,
347
- {"service_uri" => "/route"}
342
+ BelongsToObject
348
343
  )
344
+ ap.internal_object = {"service_uri" => "/route"}
349
345
  ap.remote_path.should eql("/route")
350
346
  end
351
347
 
@@ -353,9 +349,9 @@ describe "Associations" do
353
349
  Associations::MultiObjectProxy.remote_path_element = :the_element
354
350
  ap = Associations::MultiObjectProxy.new(
355
351
  "TestResource",
356
- BelongsToObject,
357
- {:the_element => "/route"}
352
+ BelongsToObject
358
353
  )
354
+ ap.internal_object = {:the_element => "/route"}
359
355
  ap.remote_path.should eql("/route")
360
356
  end
361
357
 
@@ -457,10 +453,11 @@ describe "Associations" do
457
453
  it "should proxy unknown methods to the object loading if it hasn't already" do
458
454
  ap = Associations::SingleObjectProxy.new(
459
455
  "TestResource",
460
- HasManyObject.new, {
461
- :service_uri => '/single_object_association'
462
- }
456
+ HasManyObject.new
463
457
  )
458
+ ap.internal_object = {
459
+ :service_uri => '/single_object_association'
460
+ }
464
461
  ap.should_not be_loaded
465
462
  ap.id.should_not be_blank
466
463
  ap.should be_loaded
@@ -469,10 +466,11 @@ describe "Associations" do
469
466
  it "should load scopes with caching" do
470
467
  ap = Associations::SingleObjectProxy.new(
471
468
  "TestResource",
472
- HasManyObject.new, {
473
- :service_uri => '/single_object_association'
474
- }
469
+ HasManyObject.new
475
470
  )
471
+ ap.internal_object = {
472
+ :service_uri => "/single_object_association"
473
+ }
476
474
  ap.should_not be_loaded
477
475
  ap.active.expires_in(30).internal_object
478
476
 
@@ -484,30 +482,33 @@ describe "Associations" do
484
482
  it "should check that ttl matches the expiration parameter" do
485
483
  ap = Associations::SingleObjectProxy.new(
486
484
  "TestResource",
487
- HasManyObject.new, {
488
- :service_uri => '/single_object_association'
489
- }
485
+ HasManyObject.new
490
486
  )
487
+ ap.internal_object = {
488
+ :service_uri => "/single_object_association"
489
+ }
491
490
  ap.active.expires_in(10).ttl.should eql(10)
492
491
  end
493
492
 
494
493
  it "should cache scopes when caching enabled" do
495
494
  ap = Associations::SingleObjectProxy.new(
496
495
  "TestResource",
497
- HasManyObject.new, {
498
- :service_uri => '/single_object_association'
499
- }
496
+ HasManyObject.new
500
497
  )
498
+ ap.internal_object = {
499
+ :service_uri => "/single_object_association"
500
+ }
501
501
  ap.active(:expires_in => 10).internal_object
502
502
  end
503
503
 
504
504
  it "should be able to clear it's loading cache" do
505
505
  ap = Associations::SingleObjectProxy.new(
506
506
  "TestResource",
507
- HasManyObject.new, {
508
- :service_uri => '/single_object_association'
509
- }
507
+ HasManyObject.new
510
508
  )
509
+ ap.internal_object = {
510
+ :service_uri => '/single_object_association'
511
+ }
511
512
  active = ap.active
512
513
 
513
514
  active.internal_object
@@ -523,10 +524,11 @@ describe "Associations" do
523
524
  it "should be able to reload a single-object association" do
524
525
  ap = Associations::SingleObjectProxy.new(
525
526
  "TestResource",
526
- HasManyObject.new, {
527
- :service_uri => '/single_object_association'
528
- }
527
+ HasManyObject.new
529
528
  )
529
+ ap.internal_object = {
530
+ :service_uri => '/single_object_association'
531
+ }
530
532
 
531
533
  old_name = ap.name
532
534
 
@@ -544,10 +546,11 @@ describe "Associations" do
544
546
 
545
547
  ap = Associations::MultiObjectProxy.new(
546
548
  "TestResource",
547
- BelongsToObject.new, {
548
- :service_uri => '/multi_object_association'
549
- }
549
+ BelongsToObject.new
550
550
  )
551
+ ap.internal_object = {
552
+ :service_uri => '/multi_object_association'
553
+ }
551
554
 
552
555
  old_name = ap.first.name
553
556
 
@@ -566,10 +569,11 @@ describe "Associations" do
566
569
  it "should be able to load 'all'" do
567
570
  ap = Associations::MultiObjectProxy.new(
568
571
  "TestResource",
569
- BelongsToObject.new, {
570
- :service_uri => '/multi_object_association'
571
- }
572
+ BelongsToObject.new
572
573
  )
574
+ ap.internal_object = {
575
+ :service_uri => '/multi_object_association'
576
+ }
573
577
  results = ap.all
574
578
  results.size.should eql(5)
575
579
  results.first.is_active?.should be_false
@@ -578,10 +582,11 @@ describe "Associations" do
578
582
  it "should be able to load a scope" do
579
583
  ap = Associations::MultiObjectProxy.new(
580
584
  "TestResource",
581
- BelongsToObject.new, {
582
- :service_uri => '/multi_object_association'
583
- }
585
+ BelongsToObject.new
584
586
  )
587
+ ap.internal_object = {
588
+ :service_uri => '/multi_object_association'
589
+ }
585
590
  results = ap.active
586
591
  results.size.should eql(5)
587
592
  record = results.first
@@ -591,10 +596,11 @@ describe "Associations" do
591
596
  it "should be able to load a chain of scopes" do
592
597
  ap = Associations::MultiObjectProxy.new(
593
598
  "TestResource",
594
- BelongsToObject.new, {
595
- :service_uri => '/multi_object_association'
596
- }
599
+ BelongsToObject.new
597
600
  )
601
+ ap.internal_object = {
602
+ :service_uri => '/multi_object_association'
603
+ }
598
604
  results = ap.active.birthday(Date.today)
599
605
  results.first.is_active.should be_true
600
606
  results.first.bday.should_not be_blank
@@ -605,10 +611,11 @@ describe "Associations" do
605
611
 
606
612
  ap = Associations::MultiObjectProxy.new(
607
613
  "TestResource",
608
- BelongsToObject.new, {
609
- :service_uri => '/multi_object_association'
610
- }
614
+ BelongsToObject.new
611
615
  )
616
+ ap.internal_object = {
617
+ :service_uri => '/multi_object_association'
618
+ }
612
619
  active = ap.active
613
620
 
614
621
  active.internal_object
@@ -622,10 +629,11 @@ describe "Associations" do
622
629
  it "should be enumerable" do
623
630
  ap = Associations::MultiObjectProxy.new(
624
631
  "TestResource",
625
- BelongsToObject.new, {
626
- :service_uri => '/multi_object_association'
627
- }
632
+ BelongsToObject.new
628
633
  )
634
+ ap.internal_object = {
635
+ :service_uri => '/multi_object_association'
636
+ }
629
637
  ap.each do |tr|
630
638
  tr.name.should_not be_blank
631
639
  end
@@ -61,6 +61,29 @@ describe "Base" do
61
61
 
62
62
  end
63
63
 
64
+ context "#method_missing" do
65
+
66
+ after(:all) do
67
+ TestResource.reload_resource_definition
68
+ end
69
+
70
+ it "should attempt to reload the resource definition if a method
71
+ is not found" do
72
+
73
+ TestResource.class_eval do
74
+ remove_method :bday
75
+ end
76
+
77
+ tr = TestResource.new
78
+
79
+ lambda{
80
+ tr.bday
81
+ }.should_not raise_error
82
+
83
+ end
84
+
85
+ end
86
+
64
87
 
65
88
  context "Prefixes" do
66
89
 
@@ -46,6 +46,10 @@ Mocks.define do
46
46
  :params => {:test_resource_id => 1}.matcher
47
47
  )
48
48
  end
49
+
50
+ endpoint("/has_one_objects/:id.json") do
51
+ get(HashDealer.roll(:has_one_object))
52
+ end
49
53
 
50
54
  endpoint("/has_many_objects/new") do
51
55
  get({
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api_resource
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.4
4
+ version: 0.6.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -424,7 +424,9 @@ files:
424
424
  - nohup.out
425
425
  - spec/lib/api_resource_spec.rb
426
426
  - spec/lib/associations/association_scope_spec.rb
427
+ - spec/lib/associations/belongs_to_remote_object_proxy_spec.rb
427
428
  - spec/lib/associations/has_many_remote_object_proxy_spec.rb
429
+ - spec/lib/associations/has_one_remote_object_proxy_spec.rb
428
430
  - spec/lib/associations_spec.rb
429
431
  - spec/lib/attributes_spec.rb
430
432
  - spec/lib/base_spec.rb
@@ -485,7 +487,9 @@ summary: ActiveRecord for restful APIs
485
487
  test_files:
486
488
  - spec/lib/api_resource_spec.rb
487
489
  - spec/lib/associations/association_scope_spec.rb
490
+ - spec/lib/associations/belongs_to_remote_object_proxy_spec.rb
488
491
  - spec/lib/associations/has_many_remote_object_proxy_spec.rb
492
+ - spec/lib/associations/has_one_remote_object_proxy_spec.rb
489
493
  - spec/lib/associations_spec.rb
490
494
  - spec/lib/attributes_spec.rb
491
495
  - spec/lib/base_spec.rb