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
@@ -1,191 +0,0 @@
1
- module ApiResource
2
-
3
- module Associations
4
-
5
- class AbstractScope
6
-
7
- attr_reader :klass, :finder_opts
8
-
9
- def initialize(klass, finder_opts = {})
10
-
11
- # the base class for our scope, e.g. ApiResource::SomeClass
12
- @klass = klass.is_a?(String) ? klass.constantize : klass
13
-
14
- # load the resource definition
15
- @klass.load_resource_definition
16
-
17
- # the parent scope - for composing all of the finder options
18
- @parent = finder_opts.delete(:__parent)
19
-
20
- @finder_opts = finder_opts
21
-
22
- # Where subscope is any scope down the chain, e.g. active.*future*
23
- @klass.scopes.each do |scope_name, scope_definition|
24
- self.define_subscope(scope_name, scope_definition)
25
- end
26
- end
27
-
28
- def ttl
29
- @ttl || 0
30
- end
31
-
32
- # Use this method to access the internal data, this guarantees that loading only occurs once per object
33
- def internal_object
34
- if instance_variable_defined?(:@internal_object)
35
- return instance_variable_get(:@internal_object)
36
- end
37
- instance_variable_set(:@internal_object, self.load)
38
- end
39
-
40
- # has the scope been loaded?
41
- def loaded?
42
- @loaded == true
43
- end
44
-
45
- def load_resource_definition
46
- self.klass.load_resource_definition
47
- end
48
-
49
- def scopes
50
- @scopes ||= HashWithIndifferentAccess.new
51
- end
52
-
53
- def scope?(scp)
54
- self.scopes.key?(scp.to_s)
55
- end
56
-
57
- # def current_scope
58
- # ActiveSupport::StringInquirer.new(@current_scope.join("_and_").concat("_scope"))
59
- # end
60
-
61
- def to_hash
62
- self.parent_hash.merge(self.finder_opts)
63
- end
64
-
65
- # takes empty hashes and replaces them with true so that to_query doesn't strip them out
66
- def to_query_safe_hash(hash)
67
- hash.each_pair do |k, v|
68
- hash[k] = to_query_safe_hash(v) if v.is_a?(Hash)
69
- hash[k] = true if v == {}
70
- end
71
- return hash
72
- end
73
-
74
- # gets the current hash and calls to_query on it
75
- def to_query
76
- #We need to add the unescape because to_query breaks on nested arrays
77
- CGI.unescape(to_query_safe_hash(self.to_hash).to_query)
78
- end
79
-
80
- # unset all of our scope values and our internal object
81
- def reload
82
- (self.scopes.keys + [:internal_object]).each do |ivar|
83
- if instance_variable_defined?("@#{ivar}")
84
- remove_instance_variable("@#{ivar}")
85
- end
86
- end
87
- @loaded = false
88
- self
89
- end
90
-
91
- def to_s
92
- self.internal_object.to_s
93
- end
94
-
95
- def inspect
96
- self.internal_object.inspect
97
- end
98
-
99
- def blank?
100
- self.internal_object.blank?
101
- end
102
- alias_method :empty?, :blank?
103
-
104
- def present?
105
- self.internal_object.present?
106
- end
107
-
108
- def expires_in(ttl)
109
- ApiResource::Decorators::CachingDecorator.new(self, ttl)
110
- end
111
-
112
- protected
113
-
114
- # scope_name => e.g. paged
115
- # scope_definition => e.g. {:page => "req", "per_page" => "opt"}
116
-
117
- def define_subscope(scope_name, scope_definition)
118
-
119
- self.scopes[scope_name] = scope_definition
120
-
121
- self.class_eval do
122
-
123
- define_method(scope_name) do |*args|
124
-
125
- unless instance_variable_defined?("@#{scope_name}")
126
-
127
- arg_names = scope_definition.keys
128
- arg_types = scope_definition.values
129
-
130
- finder_opts = {
131
- scope_name => {}
132
- }
133
-
134
- arg_names.each_with_index do |arg_name, i|
135
-
136
- # If we are dealing with a scope with multiple args
137
- if arg_types[i] == :rest
138
- finder_opts[scope_name][arg_name] =
139
- args.slice(i, args.count)
140
- # Else we are only dealing with a single argument
141
- else
142
- if arg_types[i] == :req || args[i].present?
143
- finder_opts[scope_name][arg_name] = args[i]
144
- end
145
- end
146
- end
147
-
148
- # if we have nothing at this point we should just pass 'true'
149
- if finder_opts[scope_name] == {}
150
- finder_opts[scope_name] = true
151
- end
152
-
153
- instance_variable_set(
154
- "@#{scope_name}",
155
- self.get_subscope_instance(finder_opts)
156
- )
157
- end
158
- instance_variable_get("@#{scope_name}")
159
- end
160
- end
161
- end
162
-
163
- def get_subscope_instance(finder_opts)
164
- ApiResource::Associations::Scope.new(
165
- self, finder_opts.merge(:__parent => self)
166
- )
167
- end
168
-
169
- def method_missing(method, *args, &block)
170
- self.internal_object.send(method, *args, &block)
171
- end
172
-
173
- # querystring hash from parent
174
- def parent_hash
175
- @parent ? @parent.to_hash : {}
176
- end
177
-
178
- # require our subclasses to implement a way to find records
179
- def load
180
- raise NotImplementedError.new("#{self.class} must implement #load")
181
- end
182
-
183
- # make sure we have a valid scope
184
- def check_scope(scp)
185
- raise ArgumentError, "Unknown scope #{scp}" unless self.scope?(scp.to_s)
186
- end
187
- end
188
-
189
- end
190
-
191
- end
@@ -1,47 +0,0 @@
1
- module ApiResource
2
-
3
- module Associations
4
-
5
- class AssociationScope < AbstractScope
6
-
7
- class_attribute :remote_path_element
8
- self.remote_path_element = :service_uri
9
-
10
- attr_accessor :remote_path
11
- attr_reader :owner
12
-
13
- # TODO: added owner - moved it to the end because the tests don't use it - it's useful here though
14
- def initialize(klass, owner, opts = {})
15
- super(klass)
16
-
17
- @owner = owner
18
-
19
- self.internal_object = opts
20
- end
21
-
22
- def ==(other)
23
- raise "Not Implemented: This method must be implemented in a subclass"
24
- end
25
-
26
- def scopes
27
- self.klass.scopes
28
- end
29
-
30
- protected
31
-
32
- # get the remote URI based on our config and options
33
- def build_load_path(options)
34
- path = self.remote_path
35
- # add a format if it doesn't exist and there is no query string yet
36
- path += ".#{self.klass.format.extension}" unless path =~ /\./ || path =~/\?/
37
- # add the query string, allowing for other user-provided options in the remote_path if we have options
38
- unless options.blank?
39
- path += (path =~ /\?/ ? "&" : "?") + options.to_query
40
- end
41
- path
42
- end
43
- end
44
-
45
- end
46
-
47
- end
@@ -1,21 +0,0 @@
1
- module ApiResource
2
- module Associations
3
- class ResourceScope < AbstractScope
4
-
5
- include Enumerable
6
-
7
- alias_method :all, :internal_object
8
-
9
- def each(*args, &block)
10
- self.internal_object.each(*args, &block)
11
- end
12
-
13
- # perform a find with a given set of query params
14
- def load(opts = self.to_hash)
15
- ret = self.klass.all(:params => opts)
16
- @loaded = true
17
- ret
18
- end
19
- end
20
- end
21
- end
@@ -1,34 +0,0 @@
1
- module ApiResource
2
- module Associations
3
- class Scope < AbstractScope
4
-
5
- def initialize(klass, opts = {})
6
- # see if we have a hash of options and it has a parent in it
7
- unless opts[:__parent].respond_to?(:load)
8
- raise ArgumentError.new(
9
- "Scopes must have a parent object passed in that " +
10
- "responds to #load"
11
- )
12
- end
13
- super(klass, opts)
14
- end
15
-
16
- def load
17
- ret = self.klass.load(self.to_hash)
18
- @loaded = true
19
- ret
20
- end
21
-
22
- # we break this out here because Scope needs to pass self.klass to
23
- # any sub-scopes. This is because Scope does not have knowledge
24
- # of how to actually load data and delegates that to either
25
- # a ResourceScope or an AssociationScope
26
- def get_subscope_instance(finder_opts)
27
- ApiResource::Associations::Scope.new(
28
- self.klass, finder_opts.merge(:__parent => self)
29
- )
30
- end
31
-
32
- end
33
- end
34
- end
@@ -1,24 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ApiResource::Associations::ResourceScope do
4
-
5
-
6
- context "#load" do
7
-
8
- it "should perform the correct query" do
9
-
10
- TestResource.expects(:all)
11
- .with(:params => {
12
- "birthday" => {
13
- "date" => "2012-01-01"
14
- }
15
- })
16
- .returns([])
17
-
18
- TestResource.birthday("2012-01-01").first
19
-
20
- end
21
-
22
- end
23
-
24
- end
Binary file
@@ -1 +0,0 @@
1
-