api_resource 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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