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/base.rb
CHANGED
|
@@ -6,6 +6,30 @@ require 'active_support/string_inquirer'
|
|
|
6
6
|
module ApiResource
|
|
7
7
|
|
|
8
8
|
class Base
|
|
9
|
+
|
|
10
|
+
# TODO: There's way too much in this class as it stands, some glaring problems:
|
|
11
|
+
# => 1) class_attributes for connection options belong in the connection class
|
|
12
|
+
# => 2) Attributes about serialization belong with the serializers
|
|
13
|
+
# => 3) Logging seems to be an unmitigated disaster
|
|
14
|
+
# => 4) All the resource definition should be in its own module with the data mapper implementation
|
|
15
|
+
# => 5) Random shit about tokens all over the place, clearly does not belong
|
|
16
|
+
# => 6) Everything related to the pathing should go somewhere by itself
|
|
17
|
+
# => 7) Everything about saving should go in a persistence module
|
|
18
|
+
# => 8) Everything about loading data should go in a Finder module
|
|
19
|
+
# => 9) Delete serializable_hash and start again
|
|
20
|
+
# => 10) setup_create_call and setup_update_call should modify the results from another method
|
|
21
|
+
# => 11) nil_attributes probably belongs with the attributes
|
|
22
|
+
# => 12) Add instrumentation in some key places
|
|
23
|
+
|
|
24
|
+
# Other Random TODOs:
|
|
25
|
+
# => 1) Profile and benchmark this code
|
|
26
|
+
# => 2) Deal with arrays and hashes as attributes in a more reasonable way
|
|
27
|
+
# => 3) Finish implementing typecasting (to_api portion)
|
|
28
|
+
# => 4) Add a reasonable way to load and activate observers, verify that it works
|
|
29
|
+
# => 5) Hook up the custom_methods module
|
|
30
|
+
# => 6) Implement an IdentityMap
|
|
31
|
+
# => 7) Write documentation
|
|
32
|
+
# => 8) Write Examples
|
|
9
33
|
|
|
10
34
|
class_attribute :site, :proxy, :user, :password, :auth_type, :format,
|
|
11
35
|
:timeout, :open_timeout, :ssl_options, :token, :ttl
|
|
@@ -297,51 +321,6 @@ module ApiResource
|
|
|
297
321
|
def create(attributes = {})
|
|
298
322
|
self.new(attributes).tap{ |resource| resource.save }
|
|
299
323
|
end
|
|
300
|
-
|
|
301
|
-
# This decides which finder method to call.
|
|
302
|
-
# It accepts arguments of the form "scope", "options={}"
|
|
303
|
-
# where options can be standard rails options or :expires_in.
|
|
304
|
-
# If :expires_in is set, it caches it for expires_in seconds.
|
|
305
|
-
def find(*arguments)
|
|
306
|
-
|
|
307
|
-
# make sure we have class data before loading
|
|
308
|
-
self.load_resource_definition
|
|
309
|
-
|
|
310
|
-
scope = arguments.slice!(0)
|
|
311
|
-
options = arguments.slice!(0) || {}
|
|
312
|
-
|
|
313
|
-
expiry = options.delete(:expires_in) || ApiResource::Base.ttl || 0
|
|
314
|
-
ApiResource.with_ttl(expiry.to_f) do
|
|
315
|
-
case scope
|
|
316
|
-
when :all then find_every(options)
|
|
317
|
-
when :first then find_every(options).first
|
|
318
|
-
when :last then find_every(options).last
|
|
319
|
-
when :one then find_one(options)
|
|
320
|
-
else find_single(scope, options)
|
|
321
|
-
end
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
# A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass
|
|
327
|
-
# in all the same arguments to this method as you can to
|
|
328
|
-
# <tt>find(:first)</tt>.
|
|
329
|
-
def first(*args)
|
|
330
|
-
find(:first, *args)
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
# A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass
|
|
334
|
-
# in all the same arguments to this method as you can to
|
|
335
|
-
# <tt>find(:last)</tt>.
|
|
336
|
-
def last(*args)
|
|
337
|
-
find(:last, *args)
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
# This is an alias for find(:all). You can pass in all the same
|
|
341
|
-
# arguments to this method as you can to <tt>find(:all)</tt>
|
|
342
|
-
def all(*args)
|
|
343
|
-
find(:all, *args)
|
|
344
|
-
end
|
|
345
324
|
|
|
346
325
|
|
|
347
326
|
# Deletes the resources with the ID in the +id+ parameter.
|
|
@@ -362,24 +341,6 @@ module ApiResource
|
|
|
362
341
|
connection.delete(element_path(id, options))
|
|
363
342
|
end
|
|
364
343
|
|
|
365
|
-
def instantiate_collection(collection)
|
|
366
|
-
collection.collect{|record|
|
|
367
|
-
instantiate_record(record)
|
|
368
|
-
}
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
def instantiate_record(record)
|
|
372
|
-
self.load_resource_definition
|
|
373
|
-
ret = self.allocate
|
|
374
|
-
ret.instance_variable_set(
|
|
375
|
-
:@attributes, record.with_indifferent_access
|
|
376
|
-
)
|
|
377
|
-
ret.instance_variable_set(
|
|
378
|
-
:@attributes_cache, HashWithIndifferentAccess.new
|
|
379
|
-
)
|
|
380
|
-
ret
|
|
381
|
-
end
|
|
382
|
-
|
|
383
344
|
protected
|
|
384
345
|
def method_missing(meth, *args, &block)
|
|
385
346
|
# make one attempt to load remote attrs
|
|
@@ -390,44 +351,6 @@ module ApiResource
|
|
|
390
351
|
end
|
|
391
352
|
|
|
392
353
|
private
|
|
393
|
-
# Find every resource
|
|
394
|
-
def find_every(options)
|
|
395
|
-
begin
|
|
396
|
-
case from = options[:from]
|
|
397
|
-
when Symbol
|
|
398
|
-
instantiate_collection(get(from, options[:params]))
|
|
399
|
-
when String
|
|
400
|
-
path = "#{from}#{query_string(options[:params])}"
|
|
401
|
-
instantiate_collection(connection.get(path, headers) || [])
|
|
402
|
-
else
|
|
403
|
-
prefix_options, query_options = split_options(options[:params])
|
|
404
|
-
path = collection_path(prefix_options, query_options)
|
|
405
|
-
instantiate_collection( (connection.get(path, headers) || []))
|
|
406
|
-
end
|
|
407
|
-
rescue ApiResource::ResourceNotFound
|
|
408
|
-
# Swallowing ResourceNotFound exceptions and return nil - as per
|
|
409
|
-
# ActiveRecord.
|
|
410
|
-
nil
|
|
411
|
-
end
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
# Find a single resource from a one-off URL
|
|
415
|
-
def find_one(options)
|
|
416
|
-
case from = options[:from]
|
|
417
|
-
when Symbol
|
|
418
|
-
instantiate_record(get(from, options[:params]))
|
|
419
|
-
when String
|
|
420
|
-
path = "#{from}#{query_string(options[:params])}"
|
|
421
|
-
instantiate_record(connection.get(path, headers))
|
|
422
|
-
end
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
# Find a single resource from the default URL
|
|
426
|
-
def find_single(scope, options)
|
|
427
|
-
prefix_options, query_options = split_options(options[:params])
|
|
428
|
-
path = element_path(scope, prefix_options, query_options)
|
|
429
|
-
instantiate_record(connection.get(path, headers))
|
|
430
|
-
end
|
|
431
354
|
|
|
432
355
|
# Accepts a URI and creates the site URI from that.
|
|
433
356
|
def create_site_uri_from(site)
|
|
@@ -582,6 +505,10 @@ module ApiResource
|
|
|
582
505
|
self.class.include_root_in_json ? {self.class.element_name => self.serializable_hash(options)}.to_json : self.serializable_hash(options).to_json
|
|
583
506
|
end
|
|
584
507
|
|
|
508
|
+
# TODO: this method needs to change seriously to fit in with the
|
|
509
|
+
# new typecasting scheme, it should call self.outgoing_attributes which
|
|
510
|
+
# should return the converted versions after calling to_api, that should
|
|
511
|
+
# be implemented in the attributes module though
|
|
585
512
|
def serializable_hash(options = {})
|
|
586
513
|
|
|
587
514
|
action = options[:action]
|
|
@@ -683,6 +610,7 @@ module ApiResource
|
|
|
683
610
|
opts[:include_associations] = opts[:include_associations] ? opts[:include_associations].concat(args) : []
|
|
684
611
|
opts[:include_extras] ||= []
|
|
685
612
|
opts[:action] = "create"
|
|
613
|
+
# TODO: Remove this dependency for saving files
|
|
686
614
|
body = RestClient::Payload.has_file?(self.attributes) ? self.serializable_hash(opts) : encode(opts)
|
|
687
615
|
end
|
|
688
616
|
|
|
@@ -706,6 +634,7 @@ module ApiResource
|
|
|
706
634
|
opts[:include_extras] ||= []
|
|
707
635
|
opts[:action] = "update"
|
|
708
636
|
opts[:except] = [:id] if self.class.include_all_attributes_on_update
|
|
637
|
+
# TODO: Remove this dependency for saving files
|
|
709
638
|
body = RestClient::Payload.has_file?(self.attributes) ? self.serializable_hash(opts) : encode(opts)
|
|
710
639
|
end
|
|
711
640
|
|
|
@@ -724,7 +653,7 @@ module ApiResource
|
|
|
724
653
|
include AssociationActivation
|
|
725
654
|
self.activate_associations
|
|
726
655
|
|
|
727
|
-
include Scopes, Callbacks, Attributes, ModelErrors
|
|
656
|
+
include Scopes, Callbacks, Observing, Attributes, ModelErrors, Conditions, Finders
|
|
728
657
|
|
|
729
658
|
end
|
|
730
659
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module ApiResource
|
|
2
|
+
|
|
3
|
+
module Conditions
|
|
4
|
+
|
|
5
|
+
extend ActiveSupport::Autoload
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
autoload :AbstractCondition
|
|
9
|
+
autoload :AssociationCondition
|
|
10
|
+
autoload :SingleObjectAssociationCondition
|
|
11
|
+
autoload :MultiObjectAssociationCondition
|
|
12
|
+
autoload :IncludeCondition
|
|
13
|
+
autoload :ScopeCondition
|
|
14
|
+
|
|
15
|
+
module ClassMethods
|
|
16
|
+
|
|
17
|
+
def includes(*args)
|
|
18
|
+
|
|
19
|
+
# everything in args must be an association
|
|
20
|
+
args.each do |arg|
|
|
21
|
+
unless self.association?(arg)
|
|
22
|
+
raise ArgumentError, "Unknown association #{arg} to eager load"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Everything looks good so just create the scope
|
|
27
|
+
ApiResource::Conditions::IncludeCondition.new(self, args)
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module ApiResource
|
|
2
|
+
|
|
3
|
+
module Conditions
|
|
4
|
+
|
|
5
|
+
class AbstractCondition
|
|
6
|
+
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
attr_reader :conditions, :klass, :included_objects, :internal_object, :association,
|
|
10
|
+
:remote_path
|
|
11
|
+
|
|
12
|
+
# TODO: add the other load forcing methods here for collections
|
|
13
|
+
delegate :[], :[]=, :<<, :first, :second, :last, :blank?, :nil?, :include?, :push, :pop,
|
|
14
|
+
:+, :concat, :flatten, :flatten!, :compact, :compact!, :empty?, :fetch, :map,
|
|
15
|
+
:reject, :reject!, :reverse, :select, :select!, :size, :sort, :sort!, :uniq, :uniq!,
|
|
16
|
+
:to_a, :sample, :slice, :slice!, :count, :present?, :to => :internal_object
|
|
17
|
+
|
|
18
|
+
# need to figure out what to do with args in the subclass,
|
|
19
|
+
# parent is the set of scopes we have right now
|
|
20
|
+
def initialize(args, klass)
|
|
21
|
+
@klass = klass
|
|
22
|
+
|
|
23
|
+
@conditions = args.with_indifferent_access
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def each(&block)
|
|
27
|
+
self.internal_object.each(&block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def blank_conditions?
|
|
31
|
+
self.conditions.blank?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def eager_load?
|
|
35
|
+
self.included_objects.present?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def included_objects
|
|
39
|
+
Array.wrap(@included_objects)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_query
|
|
43
|
+
CGI.unescape(to_query_safe_hash(self.to_hash).to_query)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_hash
|
|
47
|
+
self.conditions
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def internal_object
|
|
51
|
+
@internal_object ||= begin
|
|
52
|
+
@loaded = true
|
|
53
|
+
self.instantiate_finder.find
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
alias_method :find, :internal_object
|
|
58
|
+
alias_method :all, :internal_object
|
|
59
|
+
|
|
60
|
+
def loaded?
|
|
61
|
+
@loaded == true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def reload
|
|
65
|
+
if instance_variable_defined?(:@internal_object)
|
|
66
|
+
remove_instance_variable(:@internal_object)
|
|
67
|
+
end
|
|
68
|
+
@loaded = false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def method_missing(sym, *args, &block)
|
|
72
|
+
result = @klass.send(sym, *args, &block)
|
|
73
|
+
|
|
74
|
+
if result.is_a?(ApiResource::Conditions::AbstractCondition)
|
|
75
|
+
return self.dup.merge!(result)
|
|
76
|
+
else
|
|
77
|
+
return result
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def expires_in(time)
|
|
82
|
+
ApiResource::Decorators::CachingDecorator.new(self, time)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# TODO: Remove the bang, this doesn't modify anything
|
|
86
|
+
def merge!(cond)
|
|
87
|
+
@included_objects = (@included_objects || []).concat(cond.included_objects || []).uniq
|
|
88
|
+
@conditions = @conditions.merge(cond.to_hash)
|
|
89
|
+
@association = cond.association || self.association
|
|
90
|
+
@remote_path = self.remote_path ? self.remote_path : cond.remote_path
|
|
91
|
+
return self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
protected
|
|
95
|
+
|
|
96
|
+
def instantiate_finder
|
|
97
|
+
ApiResource::Finders::ResourceFinder.new(self.klass, self)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def to_query_safe_hash(hash)
|
|
101
|
+
hash.each_pair do |k,v|
|
|
102
|
+
hash[k] = to_query_safe_hash(v) if v.is_a?(Hash)
|
|
103
|
+
hash[k] = true if v == {}
|
|
104
|
+
end
|
|
105
|
+
return hash
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ApiResource
|
|
2
|
+
|
|
3
|
+
module Conditions
|
|
4
|
+
|
|
5
|
+
class MultiObjectAssociationCondition < AssociationCondition
|
|
6
|
+
|
|
7
|
+
protected
|
|
8
|
+
|
|
9
|
+
def instantiate_finder
|
|
10
|
+
ApiResource::Finders::MultiObjectAssociationFinder.new(self.klass, self)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module ApiResource
|
|
2
|
+
|
|
3
|
+
module Conditions
|
|
4
|
+
|
|
5
|
+
# Special class for passing into a finder object
|
|
6
|
+
# just defines all the proper methods
|
|
7
|
+
class SingleObjectAssociationCondition < AssociationCondition
|
|
8
|
+
|
|
9
|
+
protected
|
|
10
|
+
|
|
11
|
+
def instantiate_finder
|
|
12
|
+
ApiResource::Finders::SingleObjectAssociationFinder.new(self.klass, self)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
@@ -70,22 +70,28 @@ module ApiResource
|
|
|
70
70
|
request(:head, path, build_request_headers(headers, :head, self.site.merge(path)))
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
# make a put request
|
|
74
74
|
def put(path, body = {}, headers = self.headers)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
format.decode(
|
|
76
|
+
request(
|
|
77
|
+
:put,
|
|
78
|
+
path,
|
|
79
|
+
body,
|
|
80
|
+
build_request_headers(headers, :put, self.site.merge(path))
|
|
81
|
+
)
|
|
82
|
+
)
|
|
81
83
|
end
|
|
82
84
|
|
|
85
|
+
# make a post request
|
|
83
86
|
def post(path, body = {}, headers = self.headers)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
format.decode(
|
|
88
|
+
request(
|
|
89
|
+
:post,
|
|
90
|
+
path,
|
|
91
|
+
body,
|
|
92
|
+
build_request_headers(headers, :post, self.site.merge(path))
|
|
93
|
+
)
|
|
94
|
+
)
|
|
89
95
|
end
|
|
90
96
|
|
|
91
97
|
protected
|