api_resource 0.6.4 → 0.6.5

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.
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