api_resource 0.5.1 → 0.6.0

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.
Files changed (163) hide show
  1. data/.document +5 -0
  2. data/.gitignore +55 -0
  3. data/.rspec +8 -0
  4. data/.travis.yml +9 -0
  5. data/Gemfile +3 -36
  6. data/Gemfile.lock +37 -57
  7. data/Guardfile +7 -1
  8. data/{LICENSE.txt → LICENSE} +4 -2
  9. data/README.md +29 -0
  10. data/README.rdoc +5 -1
  11. data/Rakefile +5 -36
  12. data/api_resource.gemspec +40 -104
  13. data/lib/api_resource.rb +4 -1
  14. data/lib/api_resource/associations.rb +30 -14
  15. data/lib/api_resource/associations/association_proxy.rb +97 -0
  16. data/lib/api_resource/associations/belongs_to_remote_object_proxy.rb +0 -1
  17. data/lib/api_resource/associations/has_many_remote_object_proxy.rb +0 -1
  18. data/lib/api_resource/associations/has_one_remote_object_proxy.rb +7 -4
  19. data/lib/api_resource/associations/multi_object_proxy.rb +11 -16
  20. data/lib/api_resource/associations/single_object_proxy.rb +8 -6
  21. data/lib/api_resource/attributes.rb +29 -41
  22. data/lib/api_resource/base.rb +31 -102
  23. data/lib/api_resource/conditions.rb +36 -0
  24. data/lib/api_resource/conditions/abstract_condition.rb +112 -0
  25. data/lib/api_resource/conditions/association_condition.rb +18 -0
  26. data/lib/api_resource/conditions/include_condition.rb +16 -0
  27. data/lib/api_resource/conditions/multi_object_association_condition.rb +17 -0
  28. data/lib/api_resource/conditions/scope_condition.rb +11 -0
  29. data/lib/api_resource/conditions/single_object_association_condition.rb +19 -0
  30. data/lib/api_resource/connection.rb +18 -12
  31. data/lib/api_resource/finders.rb +122 -0
  32. data/lib/api_resource/finders/abstract_finder.rb +89 -0
  33. data/lib/api_resource/finders/multi_object_association_finder.rb +39 -0
  34. data/lib/api_resource/finders/resource_finder.rb +33 -0
  35. data/lib/api_resource/finders/single_object_association_finder.rb +40 -0
  36. data/lib/api_resource/observing.rb +19 -3
  37. data/lib/api_resource/scopes.rb +3 -3
  38. data/lib/api_resource/typecast.rb +85 -0
  39. data/lib/api_resource/typecasters/array_typecaster.rb +19 -0
  40. data/lib/api_resource/typecasters/boolean_typecaster.rb +22 -0
  41. data/lib/api_resource/typecasters/date_typecaster.rb +35 -0
  42. data/lib/api_resource/typecasters/float_typecaster.rb +22 -0
  43. data/lib/api_resource/typecasters/integer_typecaster.rb +22 -0
  44. data/lib/api_resource/typecasters/string_typecaster.rb +19 -0
  45. data/lib/api_resource/typecasters/time_typecaster.rb +44 -0
  46. data/lib/api_resource/version.rb +3 -0
  47. data/spec/lib/api_resource_spec.rb +6 -0
  48. data/spec/lib/associations/association_scope_spec.rb +1 -1
  49. data/spec/lib/associations_spec.rb +17 -50
  50. data/spec/lib/base_spec.rb +22 -0
  51. data/spec/lib/conditions/abstract_conditions_spec.rb +78 -0
  52. data/spec/lib/connection_spec.rb +19 -0
  53. data/spec/lib/finders/multi_object_association_finder_spec.rb +43 -0
  54. data/spec/lib/finders/resource_finder_spec.rb +89 -0
  55. data/spec/lib/finders/single_object_association_finder_spec.rb +43 -0
  56. data/spec/lib/observing_spec.rb +96 -0
  57. data/spec/lib/typecast_spec.rb +174 -0
  58. data/spec/lib/typecasters/boolean_typecaster_spec.rb +33 -0
  59. data/spec/lib/typecasters/date_typecaster_spec.rb +62 -0
  60. data/spec/lib/typecasters/float_typecaster_spec.rb +39 -0
  61. data/spec/lib/typecasters/integer_typecaster_spec.rb +40 -0
  62. data/spec/lib/typecasters/string_typecaster_spec.rb +28 -0
  63. data/spec/lib/typecasters/time_typecaster_spec.rb +65 -0
  64. data/spec/spec_helper.rb +0 -1
  65. metadata +134 -194
  66. data/VERSION +0 -1
  67. data/coverage/assets/0.5.3/app.js +0 -88
  68. data/coverage/assets/0.5.3/fancybox/blank.gif +0 -0
  69. data/coverage/assets/0.5.3/fancybox/fancy_close.png +0 -0
  70. data/coverage/assets/0.5.3/fancybox/fancy_loading.png +0 -0
  71. data/coverage/assets/0.5.3/fancybox/fancy_nav_left.png +0 -0
  72. data/coverage/assets/0.5.3/fancybox/fancy_nav_right.png +0 -0
  73. data/coverage/assets/0.5.3/fancybox/fancy_shadow_e.png +0 -0
  74. data/coverage/assets/0.5.3/fancybox/fancy_shadow_n.png +0 -0
  75. data/coverage/assets/0.5.3/fancybox/fancy_shadow_ne.png +0 -0
  76. data/coverage/assets/0.5.3/fancybox/fancy_shadow_nw.png +0 -0
  77. data/coverage/assets/0.5.3/fancybox/fancy_shadow_s.png +0 -0
  78. data/coverage/assets/0.5.3/fancybox/fancy_shadow_se.png +0 -0
  79. data/coverage/assets/0.5.3/fancybox/fancy_shadow_sw.png +0 -0
  80. data/coverage/assets/0.5.3/fancybox/fancy_shadow_w.png +0 -0
  81. data/coverage/assets/0.5.3/fancybox/fancy_title_left.png +0 -0
  82. data/coverage/assets/0.5.3/fancybox/fancy_title_main.png +0 -0
  83. data/coverage/assets/0.5.3/fancybox/fancy_title_over.png +0 -0
  84. data/coverage/assets/0.5.3/fancybox/fancy_title_right.png +0 -0
  85. data/coverage/assets/0.5.3/fancybox/fancybox-x.png +0 -0
  86. data/coverage/assets/0.5.3/fancybox/fancybox-y.png +0 -0
  87. data/coverage/assets/0.5.3/fancybox/fancybox.png +0 -0
  88. data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.css +0 -363
  89. data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.pack.js +0 -44
  90. data/coverage/assets/0.5.3/favicon_green.png +0 -0
  91. data/coverage/assets/0.5.3/favicon_red.png +0 -0
  92. data/coverage/assets/0.5.3/favicon_yellow.png +0 -0
  93. data/coverage/assets/0.5.3/highlight.css +0 -129
  94. data/coverage/assets/0.5.3/highlight.pack.js +0 -1
  95. data/coverage/assets/0.5.3/jquery-1.6.2.min.js +0 -18
  96. data/coverage/assets/0.5.3/jquery.dataTables.min.js +0 -152
  97. data/coverage/assets/0.5.3/jquery.timeago.js +0 -141
  98. data/coverage/assets/0.5.3/jquery.url.js +0 -174
  99. data/coverage/assets/0.5.3/loading.gif +0 -0
  100. data/coverage/assets/0.5.3/magnify.png +0 -0
  101. data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  102. data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  103. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  104. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  105. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  106. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  107. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  108. data/coverage/assets/0.5.3/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  109. data/coverage/assets/0.5.3/smoothness/images/ui-icons_222222_256x240.png +0 -0
  110. data/coverage/assets/0.5.3/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  111. data/coverage/assets/0.5.3/smoothness/images/ui-icons_454545_256x240.png +0 -0
  112. data/coverage/assets/0.5.3/smoothness/images/ui-icons_888888_256x240.png +0 -0
  113. data/coverage/assets/0.5.3/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  114. data/coverage/assets/0.5.3/smoothness/jquery-ui-1.8.4.custom.css +0 -295
  115. data/coverage/assets/0.5.3/stylesheet.css +0 -383
  116. data/coverage/assets/0.7.1/application.css +0 -1110
  117. data/coverage/assets/0.7.1/application.js +0 -626
  118. data/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
  119. data/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
  120. data/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
  121. data/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
  122. data/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
  123. data/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
  124. data/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
  125. data/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
  126. data/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
  127. data/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
  128. data/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
  129. data/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
  130. data/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
  131. data/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
  132. data/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
  133. data/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
  134. data/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
  135. data/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
  136. data/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
  137. data/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
  138. data/coverage/assets/0.7.1/favicon_green.png +0 -0
  139. data/coverage/assets/0.7.1/favicon_red.png +0 -0
  140. data/coverage/assets/0.7.1/favicon_yellow.png +0 -0
  141. data/coverage/assets/0.7.1/loading.gif +0 -0
  142. data/coverage/assets/0.7.1/magnify.png +0 -0
  143. data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  144. data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  145. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  146. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  147. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  148. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  149. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  150. data/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  151. data/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
  152. data/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  153. data/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
  154. data/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
  155. data/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  156. data/coverage/index.html +0 -3564
  157. data/lib/api_resource/associations/abstract_scope.rb +0 -191
  158. data/lib/api_resource/associations/association_scope.rb +0 -47
  159. data/lib/api_resource/associations/resource_scope.rb +0 -21
  160. data/lib/api_resource/associations/scope.rb +0 -34
  161. data/spec/lib/associations/resource_scope_spec.rb +0 -24
  162. data/spec/tmp/api_resource_test_db.sqlite +0 -0
  163. data/tmp/rspec_guard_result +0 -1
data/lib/api_resource.rb CHANGED
@@ -22,16 +22,19 @@ module ApiResource
22
22
  autoload :Attributes
23
23
  autoload :Base
24
24
  autoload :Callbacks
25
+ autoload :Conditions
25
26
  autoload :Connection
26
27
  autoload :CustomMethods
27
28
  autoload :Decorators
28
29
  autoload :Formats
30
+ autoload :Finders
29
31
  autoload :Local
30
32
  autoload :LogSubscriber
31
33
  autoload :Mocks
32
34
  autoload :ModelErrors
33
35
  autoload :Observing
34
36
  autoload :Scopes
37
+ autoload :Typecast
35
38
  autoload :Validations
36
39
 
37
40
  require 'api_resource/railtie'
@@ -65,7 +68,7 @@ module ApiResource
65
68
  :token, :token=, :timeout,
66
69
  :open_timeout,
67
70
  :reset_connection, :ttl, :ttl=,
68
- :to => ApiResource::Base
71
+ :to => "ApiResource::Base"
69
72
 
70
73
  end
71
74
 
@@ -1,22 +1,22 @@
1
1
  require 'active_support'
2
2
  require 'active_support/string_inquirer'
3
- require 'api_resource/association_activation'
4
- require 'api_resource/associations/abstract_scope'
5
- require 'api_resource/associations/scope'
6
- require 'api_resource/associations/resource_scope'
7
- require 'api_resource/associations/association_scope'
8
- require 'api_resource/associations/single_object_proxy'
9
- require 'api_resource/associations/multi_object_proxy'
10
- require 'api_resource/associations/belongs_to_remote_object_proxy'
11
- require 'api_resource/associations/has_one_remote_object_proxy'
12
- require 'api_resource/associations/has_many_remote_object_proxy'
13
- require 'api_resource/associations/has_many_through_remote_object_proxy'
14
- require 'api_resource/associations/related_object_hash'
15
3
 
16
4
  module ApiResource
17
5
 
18
6
  module Associations
7
+
8
+ #TODO: many of these methods should also force loading of the resource definition
19
9
  extend ActiveSupport::Concern
10
+ extend ActiveSupport::Autoload
11
+
12
+ autoload :AssociationProxy
13
+ autoload :BelongsToRemoteObjectProxy
14
+ autoload :HasManyRemoteObjectProxy
15
+ autoload :HasManyThroughRemoteObjectProxy
16
+ autoload :HasOneRemoteObjectProxy
17
+ autoload :MultiObjectProxy
18
+ autoload :RelatedObjectHash
19
+ autoload :SingleObjectProxy
20
20
 
21
21
  included do
22
22
 
@@ -81,7 +81,7 @@ module ApiResource
81
81
  options = options.with_indifferent_access
82
82
  # Raise an error if we have multiple args and options
83
83
  raise "Invalid arguments to #{assoc}" unless options.blank? || args.length == 1
84
- args.each do |arg|
84
+ args.each do |arg|
85
85
  klass_name = (options[:class_name] ? options[:class_name].to_s.classify : arg.to_s.classify)
86
86
  # add this to any descendants - the other methods etc are handled by inheritance
87
87
  ([self] + self.descendants).each do |klass|
@@ -140,6 +140,18 @@ module ApiResource
140
140
  return self.find_namespaced_class_name(ret[1]) if ret
141
141
  end
142
142
  end
143
+
144
+ # TODO: add a special foreign_key option to associations
145
+ def association_foreign_key_field(assoc)
146
+ # for now just use the association name
147
+ str = assoc.to_s.singularize.foreign_key
148
+
149
+ if has_many?(assoc)
150
+ str = str.pluralize
151
+ end
152
+
153
+ str.to_sym
154
+ end
143
155
 
144
156
  protected
145
157
 
@@ -172,10 +184,13 @@ module ApiResource
172
184
  define_attribute_method(assoc_name)
173
185
  end
174
186
 
187
+ # TODO: Come up with a better implementation for the foreign key thing
188
+ # implement the rest of the active record methods, refactor this into something
189
+ # a littel bit more sensible
175
190
  self.class_eval <<-EOE, __FILE__, __LINE__ + 1
176
191
  def #{assoc_name}
177
192
  @attributes_cache[:#{assoc_name}] ||= begin
178
- klass = #{self.association_types[assoc_type.to_sym].to_s.classify}ObjectProxy
193
+ klass = Associations::#{self.association_types[assoc_type.to_sym].to_s.classify}ObjectProxy
179
194
  instance = klass.new(
180
195
  self.association_class('#{assoc_name}'), self
181
196
  )
@@ -195,6 +210,7 @@ module ApiResource
195
210
  def #{assoc_name}?
196
211
  self.#{assoc_name}.internal_object.present?
197
212
  end
213
+
198
214
  EOE
199
215
  end
200
216
 
@@ -0,0 +1,97 @@
1
+ module ApiResource
2
+
3
+ module Associations
4
+
5
+ class AssociationProxy
6
+
7
+
8
+ class_attribute :remote_path_element
9
+ self.remote_path_element = :service_uri
10
+
11
+ attr_accessor :remote_path
12
+ attr_reader :owner, :klass, :finder_opts
13
+
14
+ # TODO: add the other load forcing methods here for collections
15
+ delegate :[], :[]=, :<<, :first, :second, :last, :blank?, :nil?, :include?, :push, :pop,
16
+ :+, :concat, :flatten, :flatten!, :compact, :compact!, :empty?, :fetch, :map,
17
+ :reject, :reject!, :reverse, :select, :select!, :size, :sort, :sort!, :uniq, :uniq!,
18
+ :to_a, :sample, :slice, :slice!, :count, :present?, :to => :internal_object
19
+
20
+ def initialize(klass, owner, opts = {})
21
+
22
+ # the base class for our scope, e.g. ApiResource::SomeClass
23
+ @klass = klass.is_a?(String) ? klass.constantize : klass
24
+
25
+ # load the resource definition
26
+ @klass.load_resource_definition
27
+
28
+ @owner = owner
29
+
30
+ self.internal_object = opts
31
+ end
32
+
33
+ def ttl
34
+ @ttl || 0
35
+ end
36
+
37
+ # Use this method to access the internal data, this guarantees that loading only occurs once per object
38
+ def internal_object
39
+ if instance_variable_defined?(:@internal_object)
40
+ return instance_variable_get(:@internal_object)
41
+ end
42
+ instance_variable_set(:@internal_object, self.load)
43
+ end
44
+
45
+ # has the scope been loaded?
46
+ def loaded?
47
+ @loaded == true
48
+ end
49
+
50
+ def load_resource_definition
51
+ self.klass.load_resource_definition
52
+ end
53
+
54
+ # unset all of our scope values and our internal object
55
+ def reload
56
+ @loaded = false
57
+ if instance_variable_defined?(:@internal_object)
58
+ remove_instance_variable(:@internal_object)
59
+ end
60
+ self
61
+ end
62
+
63
+ def expires_in(ttl)
64
+ ApiResource::Decorators::CachingDecorator.new(self, ttl)
65
+ end
66
+
67
+
68
+ def includes(*args)
69
+ self.to_condition.merge!(self.klass.includes(*args))
70
+ end
71
+
72
+ def ==(other)
73
+ raise "Not Implemented: This method must be implemented in a subclass"
74
+ end
75
+
76
+ protected
77
+
78
+ def method_missing(method, *args, &block)
79
+ # If we are calling a scoped method that should be allowed
80
+ if self.klass.scope?(method)
81
+ cond = self.klass.send(method, *args, &block)
82
+ self.to_condition.merge!(cond)
83
+ else
84
+ self.internal_object.send(method, *args, &block)
85
+ end
86
+ end
87
+
88
+ # require our subclasses to implement a way to find records
89
+ def load
90
+ raise NotImplementedError.new("#{self.class} must implement #load")
91
+ end
92
+
93
+ end
94
+
95
+ end
96
+
97
+ end
@@ -1,4 +1,3 @@
1
- require 'api_resource/associations/single_object_proxy'
2
1
  module ApiResource
3
2
  module Associations
4
3
  class BelongsToRemoteObjectProxy < SingleObjectProxy
@@ -1,4 +1,3 @@
1
- require 'api_resource/associations/multi_object_proxy'
2
1
  module ApiResource
3
2
  module Associations
4
3
  class HasManyRemoteObjectProxy < MultiObjectProxy
@@ -1,4 +1,3 @@
1
- require 'api_resource/associations/single_object_proxy'
2
1
  module ApiResource
3
2
  module Associations
4
3
  class HasOneRemoteObjectProxy < SingleObjectProxy
@@ -11,11 +10,15 @@ module ApiResource
11
10
  end
12
11
 
13
12
  protected
13
+
14
+ # because of how this works we use a multi object proxy and return the first element
15
+ def to_condition
16
+ ApiResource::Conditions::MultiObjectAssociationCondition.new(self.klass, self.remote_path)
17
+ end
14
18
 
15
19
  def load(opts = {})
16
- data = self.klass.connection.get(self.build_load_path(opts))
17
- return nil if data.blank?
18
- return self.klass.new(data.first)
20
+ @loaded = true
21
+ Array.wrap(self.to_condition.find).first
19
22
  end
20
23
 
21
24
  end
@@ -1,10 +1,8 @@
1
- require 'api_resource/associations/association_scope'
2
-
3
1
  module ApiResource
4
2
 
5
3
  module Associations
6
4
 
7
- class MultiObjectProxy < AssociationScope
5
+ class MultiObjectProxy < AssociationProxy
8
6
 
9
7
  include Enumerable
10
8
 
@@ -34,7 +32,7 @@ module ApiResource
34
32
 
35
33
  def internal_object=(contents)
36
34
  # if we were passed in a service uri, stop here
37
- return true if self.set_remote_path_and_scopes(contents)
35
+ return true if self.set_remote_path(contents)
38
36
 
39
37
  if contents.try(:first).is_a?(self.klass)
40
38
  return @internal_object = contents
@@ -69,18 +67,19 @@ module ApiResource
69
67
  self.internal_object.collect{|obj| obj.serializable_hash(options) }
70
68
  end
71
69
 
70
+ protected
71
+
72
+ def to_condition
73
+ ApiResource::Conditions::MultiObjectAssociationCondition.new(self.klass, self.remote_path)
74
+ end
75
+
72
76
  def load(opts = {})
73
- data = self.klass.connection.get(self.build_load_path(opts))
74
77
  @loaded = true
75
- return [] if data.blank?
76
- return self.klass.instantiate_collection(data)
78
+ self.to_condition.find
77
79
  end
78
80
 
79
- protected
80
-
81
- # set up the remote path from a set of options passed in
82
- def set_remote_path_and_scopes(opts)
83
- if opts.is_a?(Array) && opts.first.is_a?(Hash)
81
+ def set_remote_path(opts)
82
+ if opts.is_a?(Array) && opts.first.is_a?(Hash)
84
83
  if opts.first.symbolize_keys[self.class.remote_path_element.to_sym]
85
84
  service_uri_el = opts.shift
86
85
  else
@@ -96,10 +95,6 @@ module ApiResource
96
95
  self.class.remote_path_element.to_sym
97
96
  )
98
97
 
99
- service_uri_el.each_pair do |scope_name, scope_def|
100
- self.define_subscope(scope_name, scope_def)
101
- end
102
-
103
98
  return @remote_path.present?
104
99
  end
105
100
 
@@ -1,10 +1,8 @@
1
- require 'api_resource/associations/association_scope'
2
-
3
1
  module ApiResource
4
2
 
5
3
  module Associations
6
4
 
7
- class SingleObjectProxy < AssociationScope
5
+ class SingleObjectProxy < AssociationProxy
8
6
 
9
7
  def serializable_hash(options = {})
10
8
  return if self.internal_object.nil?
@@ -57,12 +55,16 @@ module ApiResource
57
55
  return self == other
58
56
  end
59
57
 
58
+ protected
59
+
60
+ def to_condition
61
+ ApiResource::Conditions::SingleObjectAssociationCondition.new(self.klass, self.remote_path)
62
+ end
60
63
 
64
+ # Should make a proper conditions object and call find on it
61
65
  def load(opts = {})
62
- data = self.klass.connection.get(self.build_load_path(opts))
63
66
  @loaded = true
64
- return nil if data.blank?
65
- return self.klass.instantiate_record(data)
67
+ @internal_object = self.to_condition.find
66
68
  end
67
69
 
68
70
 
@@ -7,6 +7,9 @@ module ApiResource
7
7
  include ActiveModel::Dirty
8
8
 
9
9
  included do
10
+
11
+ # include ApiResource::Typecast if it isn't already
12
+ include ApiResource::Typecast
10
13
 
11
14
  alias_method_chain :save, :dirty_tracking
12
15
 
@@ -16,8 +19,6 @@ module ApiResource
16
19
  :protected_attribute_names,
17
20
  :attribute_types
18
21
  )
19
-
20
- cattr_accessor :valid_typecasts; self.valid_typecasts = [:date, :time, :float, :integer, :int, :fixnum, :string, :array]
21
22
 
22
23
  self.attribute_names = []
23
24
  self.public_attribute_names = []
@@ -61,38 +62,21 @@ module ApiResource
61
62
  module ClassMethods
62
63
 
63
64
  def define_attributes(*args)
64
-
65
65
  args.each do |arg|
66
- if arg.is_a?(Array)
67
- self.define_attribute_type(arg.first, arg.second)
68
- arg = arg.first
69
- end
70
- self.attribute_names += [arg.to_sym]
71
- self.public_attribute_names += [arg.to_sym]
72
- self.define_accessor_methods(arg)
66
+ self.store_attribute_data(arg, :public)
73
67
  end
74
-
75
68
  self.attribute_names.uniq!
76
69
  self.public_attribute_names.uniq!
77
70
  end
78
71
 
79
72
  def define_protected_attributes(*args)
80
73
  args.each do |arg|
81
-
82
- if arg.is_a?(Array)
83
- self.define_attribute_type(arg.first, arg.second)
84
- arg = arg.first
85
- end
86
-
87
- self.attribute_names += [arg.to_sym]
88
- self.protected_attribute_names += [arg.to_sym]
89
-
90
- self.define_accessor_methods(arg)
74
+ self.store_attribute_data(arg, :protected)
91
75
  end
92
76
  self.attribute_names.uniq!
93
77
  self.protected_attribute_names.uniq!
94
78
  end
95
-
79
+
96
80
  def define_accessor_methods(meth)
97
81
  # Override the setter for dirty tracking
98
82
  self.class_eval <<-EOE, __FILE__, __LINE__ + 1
@@ -113,7 +97,7 @@ module ApiResource
113
97
  end
114
98
 
115
99
  def define_attribute_type(field, type)
116
- unless self.valid_typecasts.include?(type.to_sym)
100
+ unless self.typecasters.keys.include?(type.to_sym)
117
101
  raise "#{type} is not a valid type"
118
102
  end
119
103
  self.attribute_types[field] = type.to_sym
@@ -133,6 +117,22 @@ module ApiResource
133
117
  self.public_attribute_names.clear
134
118
  self.protected_attribute_names.clear
135
119
  end
120
+
121
+ # stores the attribute type data and the name of the
122
+ # attributes we are creating
123
+ def store_attribute_data(arg, type)
124
+ if arg.is_a?(Array)
125
+ self.define_attribute_type(arg.first, arg.second)
126
+ arg = arg.first
127
+ end
128
+ self.attribute_names += [arg.to_sym]
129
+ self.send(
130
+ "#{type}_attribute_names=",
131
+ self.send("#{type}_attribute_names") + [arg.to_sym]
132
+ )
133
+ self.define_accessor_methods(arg)
134
+ end
135
+
136
136
  end
137
137
 
138
138
  # override the initializer to set up some default values
@@ -255,26 +255,14 @@ module ApiResource
255
255
  def typecast_attribute(field, val)
256
256
  # if we have a valid value and we are planning to typecast go
257
257
  # into this case statement
258
- if self.class.attribute_types.include?(field.to_sym) && val.present?
259
- val = case self.class.attribute_types[field.to_sym]
260
- when :date
261
- val.class == Date ? val.dup : Date.parse(val)
262
- when :time
263
- val.class == Time ? val.dup : Time.parse(val)
264
- when :integer, :int, :fixnum
265
- val.class == Fixnum ? val.dup : val.to_i rescue val
266
- when :float
267
- val.class == Float ? val.dup : val.to_f rescue val
268
- when :string
269
- val.class == String ? val.dup : val.to_s rescue val
270
- when :array
271
- val.class == Array ? val.dup : Array.wrap(val)
272
- else
273
- # catches the nil case and just leaves it alone
274
- val.dup rescue val
258
+ if self.class.attribute_types.include?(field.to_sym)
259
+ caster = self.class.typecasters[self.class.attribute_types[field.to_sym]]
260
+ if caster.present?
261
+ val = caster.from_api(val)
275
262
  end
276
263
  end
277
- val
264
+
265
+ return val
278
266
  end
279
267
 
280
268
  end