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.
- data/.document +5 -0
- data/.gitignore +55 -0
- data/.rspec +8 -0
- data/.travis.yml +9 -0
- data/Gemfile +3 -36
- data/Gemfile.lock +37 -57
- data/Guardfile +7 -1
- data/{LICENSE.txt → LICENSE} +4 -2
- data/README.md +29 -0
- data/README.rdoc +5 -1
- data/Rakefile +5 -36
- data/api_resource.gemspec +40 -104
- data/lib/api_resource.rb +4 -1
- data/lib/api_resource/associations.rb +30 -14
- data/lib/api_resource/associations/association_proxy.rb +97 -0
- data/lib/api_resource/associations/belongs_to_remote_object_proxy.rb +0 -1
- data/lib/api_resource/associations/has_many_remote_object_proxy.rb +0 -1
- data/lib/api_resource/associations/has_one_remote_object_proxy.rb +7 -4
- data/lib/api_resource/associations/multi_object_proxy.rb +11 -16
- data/lib/api_resource/associations/single_object_proxy.rb +8 -6
- data/lib/api_resource/attributes.rb +29 -41
- data/lib/api_resource/base.rb +31 -102
- data/lib/api_resource/conditions.rb +36 -0
- data/lib/api_resource/conditions/abstract_condition.rb +112 -0
- data/lib/api_resource/conditions/association_condition.rb +18 -0
- data/lib/api_resource/conditions/include_condition.rb +16 -0
- data/lib/api_resource/conditions/multi_object_association_condition.rb +17 -0
- data/lib/api_resource/conditions/scope_condition.rb +11 -0
- data/lib/api_resource/conditions/single_object_association_condition.rb +19 -0
- data/lib/api_resource/connection.rb +18 -12
- data/lib/api_resource/finders.rb +122 -0
- data/lib/api_resource/finders/abstract_finder.rb +89 -0
- data/lib/api_resource/finders/multi_object_association_finder.rb +39 -0
- data/lib/api_resource/finders/resource_finder.rb +33 -0
- data/lib/api_resource/finders/single_object_association_finder.rb +40 -0
- data/lib/api_resource/observing.rb +19 -3
- data/lib/api_resource/scopes.rb +3 -3
- data/lib/api_resource/typecast.rb +85 -0
- data/lib/api_resource/typecasters/array_typecaster.rb +19 -0
- data/lib/api_resource/typecasters/boolean_typecaster.rb +22 -0
- data/lib/api_resource/typecasters/date_typecaster.rb +35 -0
- data/lib/api_resource/typecasters/float_typecaster.rb +22 -0
- data/lib/api_resource/typecasters/integer_typecaster.rb +22 -0
- data/lib/api_resource/typecasters/string_typecaster.rb +19 -0
- data/lib/api_resource/typecasters/time_typecaster.rb +44 -0
- data/lib/api_resource/version.rb +3 -0
- data/spec/lib/api_resource_spec.rb +6 -0
- data/spec/lib/associations/association_scope_spec.rb +1 -1
- data/spec/lib/associations_spec.rb +17 -50
- data/spec/lib/base_spec.rb +22 -0
- data/spec/lib/conditions/abstract_conditions_spec.rb +78 -0
- data/spec/lib/connection_spec.rb +19 -0
- data/spec/lib/finders/multi_object_association_finder_spec.rb +43 -0
- data/spec/lib/finders/resource_finder_spec.rb +89 -0
- data/spec/lib/finders/single_object_association_finder_spec.rb +43 -0
- data/spec/lib/observing_spec.rb +96 -0
- data/spec/lib/typecast_spec.rb +174 -0
- data/spec/lib/typecasters/boolean_typecaster_spec.rb +33 -0
- data/spec/lib/typecasters/date_typecaster_spec.rb +62 -0
- data/spec/lib/typecasters/float_typecaster_spec.rb +39 -0
- data/spec/lib/typecasters/integer_typecaster_spec.rb +40 -0
- data/spec/lib/typecasters/string_typecaster_spec.rb +28 -0
- data/spec/lib/typecasters/time_typecaster_spec.rb +65 -0
- data/spec/spec_helper.rb +0 -1
- metadata +134 -194
- data/VERSION +0 -1
- data/coverage/assets/0.5.3/app.js +0 -88
- data/coverage/assets/0.5.3/fancybox/blank.gif +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_close.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_loading.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_nav_left.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_nav_right.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_e.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_n.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_ne.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_nw.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_s.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_se.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_sw.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_shadow_w.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_title_left.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_title_main.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_title_over.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancy_title_right.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancybox-x.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancybox-y.png +0 -0
- data/coverage/assets/0.5.3/fancybox/fancybox.png +0 -0
- data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.css +0 -363
- data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.pack.js +0 -44
- data/coverage/assets/0.5.3/favicon_green.png +0 -0
- data/coverage/assets/0.5.3/favicon_red.png +0 -0
- data/coverage/assets/0.5.3/favicon_yellow.png +0 -0
- data/coverage/assets/0.5.3/highlight.css +0 -129
- data/coverage/assets/0.5.3/highlight.pack.js +0 -1
- data/coverage/assets/0.5.3/jquery-1.6.2.min.js +0 -18
- data/coverage/assets/0.5.3/jquery.dataTables.min.js +0 -152
- data/coverage/assets/0.5.3/jquery.timeago.js +0 -141
- data/coverage/assets/0.5.3/jquery.url.js +0 -174
- data/coverage/assets/0.5.3/loading.gif +0 -0
- data/coverage/assets/0.5.3/magnify.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/assets/0.5.3/smoothness/jquery-ui-1.8.4.custom.css +0 -295
- data/coverage/assets/0.5.3/stylesheet.css +0 -383
- data/coverage/assets/0.7.1/application.css +0 -1110
- data/coverage/assets/0.7.1/application.js +0 -626
- data/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
- data/coverage/assets/0.7.1/favicon_green.png +0 -0
- data/coverage/assets/0.7.1/favicon_red.png +0 -0
- data/coverage/assets/0.7.1/favicon_yellow.png +0 -0
- data/coverage/assets/0.7.1/loading.gif +0 -0
- data/coverage/assets/0.7.1/magnify.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/index.html +0 -3564
- data/lib/api_resource/associations/abstract_scope.rb +0 -191
- data/lib/api_resource/associations/association_scope.rb +0 -47
- data/lib/api_resource/associations/resource_scope.rb +0 -21
- data/lib/api_resource/associations/scope.rb +0 -34
- data/spec/lib/associations/resource_scope_spec.rb +0 -24
- data/spec/tmp/api_resource_test_db.sqlite +0 -0
- 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 =
|
|
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 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
|
-
|
|
17
|
-
|
|
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 <
|
|
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.
|
|
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
|
-
|
|
76
|
-
return self.klass.instantiate_collection(data)
|
|
78
|
+
self.to_condition.find
|
|
77
79
|
end
|
|
78
80
|
|
|
79
|
-
|
|
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 <
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
264
|
+
|
|
265
|
+
return val
|
|
278
266
|
end
|
|
279
267
|
|
|
280
268
|
end
|