spark_api 1.0.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 (257) hide show
  1. data/History.txt +139 -0
  2. data/LICENSE +14 -0
  3. data/README.md +153 -0
  4. data/Rakefile +18 -0
  5. data/VERSION +1 -0
  6. data/bin/spark_api +8 -0
  7. data/bin/spark_api~ +8 -0
  8. data/lib/spark_api.rb +46 -0
  9. data/lib/spark_api/authentication.rb +55 -0
  10. data/lib/spark_api/authentication/api_auth.rb +104 -0
  11. data/lib/spark_api/authentication/api_auth.rb~ +104 -0
  12. data/lib/spark_api/authentication/base_auth.rb +47 -0
  13. data/lib/spark_api/authentication/base_auth.rb~ +47 -0
  14. data/lib/spark_api/authentication/oauth2.rb +198 -0
  15. data/lib/spark_api/authentication/oauth2.rb~ +199 -0
  16. data/lib/spark_api/authentication/oauth2_impl/grant_type_base.rb +87 -0
  17. data/lib/spark_api/authentication/oauth2_impl/grant_type_base.rb~ +87 -0
  18. data/lib/spark_api/authentication/oauth2_impl/grant_type_code.rb +48 -0
  19. data/lib/spark_api/authentication/oauth2_impl/grant_type_code.rb~ +49 -0
  20. data/lib/spark_api/authentication/oauth2_impl/grant_type_password.rb +44 -0
  21. data/lib/spark_api/authentication/oauth2_impl/grant_type_password.rb~ +45 -0
  22. data/lib/spark_api/authentication/oauth2_impl/grant_type_refresh.rb +35 -0
  23. data/lib/spark_api/authentication/oauth2_impl/grant_type_refresh.rb~ +36 -0
  24. data/lib/spark_api/authentication/oauth2_impl/middleware.rb +38 -0
  25. data/lib/spark_api/authentication/oauth2_impl/middleware.rb~ +39 -0
  26. data/lib/spark_api/authentication/oauth2_impl/password_provider.rb +24 -0
  27. data/lib/spark_api/authentication/oauth2_impl/password_provider.rb~ +25 -0
  28. data/lib/spark_api/cli.rb +158 -0
  29. data/lib/spark_api/cli.rb~ +158 -0
  30. data/lib/spark_api/cli/api_auth.rb +8 -0
  31. data/lib/spark_api/cli/api_auth.rb~ +8 -0
  32. data/lib/spark_api/cli/oauth2.rb +14 -0
  33. data/lib/spark_api/cli/oauth2.rb~ +14 -0
  34. data/lib/spark_api/cli/setup.rb +47 -0
  35. data/lib/spark_api/cli/setup.rb~ +47 -0
  36. data/lib/spark_api/client.rb +27 -0
  37. data/lib/spark_api/configuration.rb +54 -0
  38. data/lib/spark_api/configuration.rb~ +54 -0
  39. data/lib/spark_api/configuration/yaml.rb +101 -0
  40. data/lib/spark_api/configuration/yaml.rb~ +101 -0
  41. data/lib/spark_api/connection.rb +42 -0
  42. data/lib/spark_api/faraday.rb +64 -0
  43. data/lib/spark_api/faraday.rb~ +64 -0
  44. data/lib/spark_api/models.rb +33 -0
  45. data/lib/spark_api/models.rb~ +33 -0
  46. data/lib/spark_api/models/account.rb +115 -0
  47. data/lib/spark_api/models/account.rb~ +115 -0
  48. data/lib/spark_api/models/base.rb +118 -0
  49. data/lib/spark_api/models/base.rb~ +118 -0
  50. data/lib/spark_api/models/connect_prefs.rb +10 -0
  51. data/lib/spark_api/models/connect_prefs.rb~ +10 -0
  52. data/lib/spark_api/models/constraint.rb +16 -0
  53. data/lib/spark_api/models/constraint.rb~ +16 -0
  54. data/lib/spark_api/models/contact.rb +49 -0
  55. data/lib/spark_api/models/contact.rb~ +49 -0
  56. data/lib/spark_api/models/custom_fields.rb +12 -0
  57. data/lib/spark_api/models/custom_fields.rb~ +12 -0
  58. data/lib/spark_api/models/document.rb +11 -0
  59. data/lib/spark_api/models/document.rb~ +11 -0
  60. data/lib/spark_api/models/finders.rb +45 -0
  61. data/lib/spark_api/models/finders.rb~ +45 -0
  62. data/lib/spark_api/models/idx_link.rb +47 -0
  63. data/lib/spark_api/models/idx_link.rb~ +47 -0
  64. data/lib/spark_api/models/listing.rb +197 -0
  65. data/lib/spark_api/models/listing.rb~ +197 -0
  66. data/lib/spark_api/models/listing_cart.rb +72 -0
  67. data/lib/spark_api/models/listing_cart.rb~ +72 -0
  68. data/lib/spark_api/models/market_statistics.rb +33 -0
  69. data/lib/spark_api/models/market_statistics.rb~ +33 -0
  70. data/lib/spark_api/models/message.rb +21 -0
  71. data/lib/spark_api/models/message.rb~ +21 -0
  72. data/lib/spark_api/models/note.rb +41 -0
  73. data/lib/spark_api/models/note.rb~ +41 -0
  74. data/lib/spark_api/models/notification.rb +42 -0
  75. data/lib/spark_api/models/notification.rb~ +42 -0
  76. data/lib/spark_api/models/open_house.rb +24 -0
  77. data/lib/spark_api/models/open_house.rb~ +24 -0
  78. data/lib/spark_api/models/photo.rb +70 -0
  79. data/lib/spark_api/models/photo.rb~ +70 -0
  80. data/lib/spark_api/models/property_types.rb +7 -0
  81. data/lib/spark_api/models/property_types.rb~ +7 -0
  82. data/lib/spark_api/models/saved_search.rb +16 -0
  83. data/lib/spark_api/models/saved_search.rb~ +16 -0
  84. data/lib/spark_api/models/shared_listing.rb +35 -0
  85. data/lib/spark_api/models/shared_listing.rb~ +35 -0
  86. data/lib/spark_api/models/standard_fields.rb +50 -0
  87. data/lib/spark_api/models/standard_fields.rb~ +50 -0
  88. data/lib/spark_api/models/subresource.rb +19 -0
  89. data/lib/spark_api/models/subresource.rb~ +19 -0
  90. data/lib/spark_api/models/system_info.rb +14 -0
  91. data/lib/spark_api/models/system_info.rb~ +14 -0
  92. data/lib/spark_api/models/tour_of_home.rb +24 -0
  93. data/lib/spark_api/models/tour_of_home.rb~ +24 -0
  94. data/lib/spark_api/models/video.rb +16 -0
  95. data/lib/spark_api/models/video.rb~ +16 -0
  96. data/lib/spark_api/models/virtual_tour.rb +18 -0
  97. data/lib/spark_api/models/virtual_tour.rb~ +18 -0
  98. data/lib/spark_api/multi_client.rb +59 -0
  99. data/lib/spark_api/multi_client.rb~ +59 -0
  100. data/lib/spark_api/paginate.rb +109 -0
  101. data/lib/spark_api/paginate.rb~ +109 -0
  102. data/lib/spark_api/primary_array.rb +29 -0
  103. data/lib/spark_api/primary_array.rb~ +29 -0
  104. data/lib/spark_api/request.rb +96 -0
  105. data/lib/spark_api/request.rb~ +96 -0
  106. data/lib/spark_api/response.rb +70 -0
  107. data/lib/spark_api/response.rb~ +70 -0
  108. data/lib/spark_api/version.rb +4 -0
  109. data/lib/spark_api/version.rb~ +4 -0
  110. data/script/console +6 -0
  111. data/script/console~ +6 -0
  112. data/script/example.rb +27 -0
  113. data/script/example.rb~ +27 -0
  114. data/spec/fixtures/accounts/all.json +160 -0
  115. data/spec/fixtures/accounts/my.json +74 -0
  116. data/spec/fixtures/accounts/my_portal.json +20 -0
  117. data/spec/fixtures/accounts/my_put.json +5 -0
  118. data/spec/fixtures/accounts/my_save.json +5 -0
  119. data/spec/fixtures/accounts/office.json +142 -0
  120. data/spec/fixtures/accounts/password_save.json +6 -0
  121. data/spec/fixtures/authentication_failure.json +7 -0
  122. data/spec/fixtures/base.json +13 -0
  123. data/spec/fixtures/contacts/contacts.json +28 -0
  124. data/spec/fixtures/contacts/my.json +19 -0
  125. data/spec/fixtures/contacts/new.json +11 -0
  126. data/spec/fixtures/contacts/new_empty.json +8 -0
  127. data/spec/fixtures/contacts/new_notify.json +11 -0
  128. data/spec/fixtures/contacts/post.json +10 -0
  129. data/spec/fixtures/contacts/tags.json +11 -0
  130. data/spec/fixtures/count.json +10 -0
  131. data/spec/fixtures/empty.json +3 -0
  132. data/spec/fixtures/errors/expired.json +7 -0
  133. data/spec/fixtures/errors/failure.json +5 -0
  134. data/spec/fixtures/errors/failure_with_constraint.json +17 -0
  135. data/spec/fixtures/errors/failure_with_msg.json +7 -0
  136. data/spec/fixtures/generic_delete.json +1 -0
  137. data/spec/fixtures/generic_failure.json +5 -0
  138. data/spec/fixtures/listing_carts/add_listing.json +13 -0
  139. data/spec/fixtures/listing_carts/add_listing_post.json +5 -0
  140. data/spec/fixtures/listing_carts/empty.json +5 -0
  141. data/spec/fixtures/listing_carts/listing_cart.json +19 -0
  142. data/spec/fixtures/listing_carts/new.json +12 -0
  143. data/spec/fixtures/listing_carts/post.json +10 -0
  144. data/spec/fixtures/listing_carts/remove_listing.json +13 -0
  145. data/spec/fixtures/listings/constraints.json +18 -0
  146. data/spec/fixtures/listings/constraints_with_pagination.json +24 -0
  147. data/spec/fixtures/listings/document_index.json +19 -0
  148. data/spec/fixtures/listings/multiple.json +69 -0
  149. data/spec/fixtures/listings/no_subresources.json +38 -0
  150. data/spec/fixtures/listings/open_houses.json +21 -0
  151. data/spec/fixtures/listings/photos/index.json +469 -0
  152. data/spec/fixtures/listings/photos/new.json +12 -0
  153. data/spec/fixtures/listings/photos/post.json +20 -0
  154. data/spec/fixtures/listings/put.json +5 -0
  155. data/spec/fixtures/listings/put_expiration_date.json +5 -0
  156. data/spec/fixtures/listings/saved_search.json +17 -0
  157. data/spec/fixtures/listings/shared_listing_get.json +14 -0
  158. data/spec/fixtures/listings/shared_listing_new.json +9 -0
  159. data/spec/fixtures/listings/shared_listing_post.json +10 -0
  160. data/spec/fixtures/listings/tour_of_homes.json +23 -0
  161. data/spec/fixtures/listings/videos_index.json +18 -0
  162. data/spec/fixtures/listings/virtual_tours_index.json +42 -0
  163. data/spec/fixtures/listings/with_documents.json +52 -0
  164. data/spec/fixtures/listings/with_permissions.json +44 -0
  165. data/spec/fixtures/listings/with_photos.json +110 -0
  166. data/spec/fixtures/listings/with_supplement.json +39 -0
  167. data/spec/fixtures/listings/with_videos.json +54 -0
  168. data/spec/fixtures/listings/with_vtour.json +48 -0
  169. data/spec/fixtures/logo_fbs.png +0 -0
  170. data/spec/fixtures/messages/new.json +14 -0
  171. data/spec/fixtures/messages/new_empty.json +7 -0
  172. data/spec/fixtures/messages/new_with_recipients.json +15 -0
  173. data/spec/fixtures/messages/post.json +5 -0
  174. data/spec/fixtures/notes/add.json +11 -0
  175. data/spec/fixtures/notes/agent_shared.json +11 -0
  176. data/spec/fixtures/notes/agent_shared_empty.json +7 -0
  177. data/spec/fixtures/notes/new.json +5 -0
  178. data/spec/fixtures/notifications/mark_read.json +1 -0
  179. data/spec/fixtures/notifications/new.json +8 -0
  180. data/spec/fixtures/notifications/new_empty.json +7 -0
  181. data/spec/fixtures/notifications/notifications.json +43 -0
  182. data/spec/fixtures/notifications/post.json +10 -0
  183. data/spec/fixtures/notifications/unread.json +10 -0
  184. data/spec/fixtures/oauth2/access.json +3 -0
  185. data/spec/fixtures/oauth2/access_with_old_refresh.json +5 -0
  186. data/spec/fixtures/oauth2/access_with_refresh.json +5 -0
  187. data/spec/fixtures/oauth2/authorization_code_body.json +7 -0
  188. data/spec/fixtures/oauth2/error.json +3 -0
  189. data/spec/fixtures/oauth2/password_body.json +7 -0
  190. data/spec/fixtures/oauth2/refresh_body.json +7 -0
  191. data/spec/fixtures/oauth2_error.json +3 -0
  192. data/spec/fixtures/property_types/property_types.json +31 -0
  193. data/spec/fixtures/session.json +10 -0
  194. data/spec/fixtures/standardfields/city.json +1031 -0
  195. data/spec/fixtures/standardfields/nearby.json +53 -0
  196. data/spec/fixtures/standardfields/standardfields.json +188 -0
  197. data/spec/fixtures/standardfields/stateorprovince.json +36 -0
  198. data/spec/fixtures/success.json +5 -0
  199. data/spec/json_helper.rb +76 -0
  200. data/spec/mock_helper.rb +124 -0
  201. data/spec/oauth2_helper.rb +68 -0
  202. data/spec/spec_helper.rb +48 -0
  203. data/spec/unit/flexmls_api_spec.rb~ +23 -0
  204. data/spec/unit/spark_api/authentication/api_auth_spec.rb +169 -0
  205. data/spec/unit/spark_api/authentication/api_auth_spec.rb~ +169 -0
  206. data/spec/unit/spark_api/authentication/base_auth_spec.rb +10 -0
  207. data/spec/unit/spark_api/authentication/base_auth_spec.rb~ +10 -0
  208. data/spec/unit/spark_api/authentication/oauth2_impl/grant_type_base_spec.rb +10 -0
  209. data/spec/unit/spark_api/authentication/oauth2_impl/grant_type_base_spec.rb~ +10 -0
  210. data/spec/unit/spark_api/authentication/oauth2_spec.rb +205 -0
  211. data/spec/unit/spark_api/authentication/oauth2_spec.rb~ +205 -0
  212. data/spec/unit/spark_api/authentication_spec.rb +38 -0
  213. data/spec/unit/spark_api/authentication_spec.rb~ +38 -0
  214. data/spec/unit/spark_api/configuration/yaml_spec.rb +72 -0
  215. data/spec/unit/spark_api/configuration/yaml_spec.rb~ +72 -0
  216. data/spec/unit/spark_api/configuration_spec.rb +122 -0
  217. data/spec/unit/spark_api/configuration_spec.rb~ +122 -0
  218. data/spec/unit/spark_api/faraday_spec.rb +90 -0
  219. data/spec/unit/spark_api/faraday_spec.rb~ +90 -0
  220. data/spec/unit/spark_api/models/account_spec.rb +176 -0
  221. data/spec/unit/spark_api/models/base_spec.rb +106 -0
  222. data/spec/unit/spark_api/models/connect_prefs_spec.rb +9 -0
  223. data/spec/unit/spark_api/models/constraint_spec.rb +19 -0
  224. data/spec/unit/spark_api/models/contact_spec.rb +108 -0
  225. data/spec/unit/spark_api/models/contact_spec.rb~ +108 -0
  226. data/spec/unit/spark_api/models/document_spec.rb +32 -0
  227. data/spec/unit/spark_api/models/listing_cart_spec.rb +127 -0
  228. data/spec/unit/spark_api/models/listing_cart_spec.rb~ +127 -0
  229. data/spec/unit/spark_api/models/listing_spec.rb +320 -0
  230. data/spec/unit/spark_api/models/listing_spec.rb~ +320 -0
  231. data/spec/unit/spark_api/models/message_spec.rb +47 -0
  232. data/spec/unit/spark_api/models/message_spec.rb~ +47 -0
  233. data/spec/unit/spark_api/models/note_spec.rb +63 -0
  234. data/spec/unit/spark_api/models/note_spec.rb~ +63 -0
  235. data/spec/unit/spark_api/models/notification_spec.rb +62 -0
  236. data/spec/unit/spark_api/models/notification_spec.rb~ +62 -0
  237. data/spec/unit/spark_api/models/open_house_spec.rb +39 -0
  238. data/spec/unit/spark_api/models/photo_spec.rb +92 -0
  239. data/spec/unit/spark_api/models/property_types_spec.rb +33 -0
  240. data/spec/unit/spark_api/models/saved_search_spec.rb +40 -0
  241. data/spec/unit/spark_api/models/shared_listing_spec.rb +45 -0
  242. data/spec/unit/spark_api/models/shared_listing_spec.rb~ +45 -0
  243. data/spec/unit/spark_api/models/standard_fields_spec.rb +60 -0
  244. data/spec/unit/spark_api/models/system_info_spec.rb +83 -0
  245. data/spec/unit/spark_api/models/tour_of_home_spec.rb +44 -0
  246. data/spec/unit/spark_api/models/video_spec.rb +36 -0
  247. data/spec/unit/spark_api/models/virtual_tour_spec.rb +44 -0
  248. data/spec/unit/spark_api/multi_client_spec.rb +56 -0
  249. data/spec/unit/spark_api/multi_client_spec.rb~ +56 -0
  250. data/spec/unit/spark_api/paginate_spec.rb +224 -0
  251. data/spec/unit/spark_api/paginate_spec.rb~ +224 -0
  252. data/spec/unit/spark_api/primary_array_spec.rb +41 -0
  253. data/spec/unit/spark_api/primary_array_spec.rb~ +41 -0
  254. data/spec/unit/spark_api/request_spec.rb +344 -0
  255. data/spec/unit/spark_api/request_spec.rb~ +344 -0
  256. data/spec/unit/spark_api_spec.rb +23 -0
  257. metadata +725 -0
@@ -0,0 +1,109 @@
1
+ require 'will_paginate/collection'
2
+
3
+ # =Pagination for api resource collections
4
+ # Will paginate adapter for the api client. Utilizes the same interface as will paginate and returns the
5
+ # same WillPaginate::Collection for finder results.
6
+ module SparkApi
7
+ module Paginate
8
+
9
+ DEFAULT_PAGE_SIZE = 25
10
+
11
+ # == Replacement hook for will_paginate's class method
12
+ # Does a best effort to mimic the will_paginate method of same name. All arguments are
13
+ # passed on to the finder method except the special keys for the options hash listed below.
14
+ #
15
+ # == Special parameters for paginating finders
16
+ # * <tt>:page</tt> -- REQUIRED, but defaults to 1 if false or nil
17
+ # * <tt>:per_page</tt> -- defaults to <tt>CurrentModel.per_page</tt> (which is 25 if not overridden)
18
+ # * <tt>:finder</tt> -- name of the finder used (default: "get"). This needs to be a class finder method on the class
19
+ def paginate(*args)
20
+ options = args.last.is_a?(::Hash) ? args.pop : {}
21
+ page = options.delete(:page) || 1
22
+ items_per_page = options.delete(:per_page) || self.per_page
23
+ finder = (options.delete(:finder) || 'get').to_s
24
+ page_options = {
25
+ "_pagination" => 1,
26
+ "_limit" => items_per_page,
27
+ "_page" => page
28
+ }
29
+ options.merge!(page_options)
30
+ args << options
31
+ collection = send(finder,*args)
32
+ end
33
+
34
+ # == Instanciate class instances from array of hash representations.
35
+ # Needs to be called by all finders that would like to support paging. Takes the hash result
36
+ # set from the request layer and instanciates instances of the class called for the finder.
37
+ #
38
+ # * result_array -- the results object returned from the api request layer. An array of hashes.
39
+ #
40
+ # :returns:
41
+ # An array of class instances for the Class of the calling finder
42
+ def collect(result_array)
43
+
44
+ # when conducting a count (pagination=count), the result_array is not an array
45
+ # in those cases, simply return the Fixnum
46
+ return result_array unless result_array.kind_of? Array
47
+
48
+ collection = result_array.collect { |item| new(item)}
49
+ result_array.replace(collection)
50
+ result_array
51
+ end
52
+
53
+ # Default per_page limit set on all models. Override this method in the model such ala the
54
+ # will_paginate gem to change
55
+ def per_page
56
+ DEFAULT_PAGE_SIZE
57
+ end
58
+
59
+
60
+ end
61
+
62
+ # ==Paginate Api Responses
63
+ # Module used by the request layer to decorate the response's results array with paging support.
64
+ # Pagination only happens if the response includes the pagination information as specified by the
65
+ # API.
66
+ module PaginateResponse
67
+ attr_accessor :results
68
+ def method_missing(method_symbol, *arguments)
69
+ if results.respond_to?(method_symbol)
70
+ arguments.empty? ? self.results.send(method_symbol) : self.results.send(method_symbol, arguments)
71
+ else
72
+ super
73
+ end
74
+ end
75
+ end
76
+
77
+ # ==Pagination Helpers
78
+ # Helpers to create the pagination collection
79
+ module PaginateHelper
80
+ # ==Enable pagination
81
+ # * results -- array of hashes representing api resources
82
+ # * paging_hash -- the pagination response information from the api representing paging state.
83
+ #
84
+ # :returns:
85
+ # The result set decorated as a WillPaginate::Collection
86
+ def paginate_response(results, paging_hash)
87
+ pager = Pagination.new(paging_hash)
88
+ paged_results = WillPaginate::Collection.create(pager.current_page, pager.page_size, pager.total_rows) do |p|
89
+ p.replace(results)
90
+ end
91
+ paged_results.extend PaginateResponse
92
+ paged_results.results = results
93
+ paged_results
94
+ end
95
+ end
96
+
97
+ # ==Pagination
98
+ # Simple class representing the API's pagination response object
99
+ class Pagination
100
+ attr_accessor :total_rows, :page_size, :total_pages, :current_page
101
+ def initialize(hash)
102
+ @total_rows = hash["TotalRows"]
103
+ @page_size = hash["PageSize"]
104
+ @total_pages = hash["TotalPages"]
105
+ @current_page = hash["CurrentPage"]
106
+ end
107
+ end
108
+
109
+ end
@@ -0,0 +1,109 @@
1
+ require 'will_paginate/collection'
2
+
3
+ # =Pagination for api resource collections
4
+ # Will paginate adapter for the api client. Utilizes the same interface as will paginate and returns the
5
+ # same WillPaginate::Collection for finder results.
6
+ module FlexmlsApi
7
+ module Paginate
8
+
9
+ DEFAULT_PAGE_SIZE = 25
10
+
11
+ # == Replacement hook for will_paginate's class method
12
+ # Does a best effort to mimic the will_paginate method of same name. All arguments are
13
+ # passed on to the finder method except the special keys for the options hash listed below.
14
+ #
15
+ # == Special parameters for paginating finders
16
+ # * <tt>:page</tt> -- REQUIRED, but defaults to 1 if false or nil
17
+ # * <tt>:per_page</tt> -- defaults to <tt>CurrentModel.per_page</tt> (which is 25 if not overridden)
18
+ # * <tt>:finder</tt> -- name of the finder used (default: "get"). This needs to be a class finder method on the class
19
+ def paginate(*args)
20
+ options = args.last.is_a?(::Hash) ? args.pop : {}
21
+ page = options.delete(:page) || 1
22
+ items_per_page = options.delete(:per_page) || self.per_page
23
+ finder = (options.delete(:finder) || 'get').to_s
24
+ page_options = {
25
+ "_pagination" => 1,
26
+ "_limit" => items_per_page,
27
+ "_page" => page
28
+ }
29
+ options.merge!(page_options)
30
+ args << options
31
+ collection = send(finder,*args)
32
+ end
33
+
34
+ # == Instanciate class instances from array of hash representations.
35
+ # Needs to be called by all finders that would like to support paging. Takes the hash result
36
+ # set from the request layer and instanciates instances of the class called for the finder.
37
+ #
38
+ # * result_array -- the results object returned from the api request layer. An array of hashes.
39
+ #
40
+ # :returns:
41
+ # An array of class instances for the Class of the calling finder
42
+ def collect(result_array)
43
+
44
+ # when conducting a count (pagination=count), the result_array is not an array
45
+ # in those cases, simply return the Fixnum
46
+ return result_array unless result_array.kind_of? Array
47
+
48
+ collection = result_array.collect { |item| new(item)}
49
+ result_array.replace(collection)
50
+ result_array
51
+ end
52
+
53
+ # Default per_page limit set on all models. Override this method in the model such ala the
54
+ # will_paginate gem to change
55
+ def per_page
56
+ DEFAULT_PAGE_SIZE
57
+ end
58
+
59
+
60
+ end
61
+
62
+ # ==Paginate Api Responses
63
+ # Module used by the request layer to decorate the response's results array with paging support.
64
+ # Pagination only happens if the response includes the pagination information as specified by the
65
+ # API.
66
+ module PaginateResponse
67
+ attr_accessor :results
68
+ def method_missing(method_symbol, *arguments)
69
+ if results.respond_to?(method_symbol)
70
+ arguments.empty? ? self.results.send(method_symbol) : self.results.send(method_symbol, arguments)
71
+ else
72
+ super
73
+ end
74
+ end
75
+ end
76
+
77
+ # ==Pagination Helpers
78
+ # Helpers to create the pagination collection
79
+ module PaginateHelper
80
+ # ==Enable pagination
81
+ # * results -- array of hashes representing api resources
82
+ # * paging_hash -- the pagination response information from the api representing paging state.
83
+ #
84
+ # :returns:
85
+ # The result set decorated as a WillPaginate::Collection
86
+ def paginate_response(results, paging_hash)
87
+ pager = Pagination.new(paging_hash)
88
+ paged_results = WillPaginate::Collection.create(pager.current_page, pager.page_size, pager.total_rows) do |p|
89
+ p.replace(results)
90
+ end
91
+ paged_results.extend PaginateResponse
92
+ paged_results.results = results
93
+ paged_results
94
+ end
95
+ end
96
+
97
+ # ==Pagination
98
+ # Simple class representing the API's pagination response object
99
+ class Pagination
100
+ attr_accessor :total_rows, :page_size, :total_pages, :current_page
101
+ def initialize(hash)
102
+ @total_rows = hash["TotalRows"]
103
+ @page_size = hash["PageSize"]
104
+ @total_pages = hash["TotalPages"]
105
+ @current_page = hash["CurrentPage"]
106
+ end
107
+ end
108
+
109
+ end
@@ -0,0 +1,29 @@
1
+ module SparkApi
2
+ class PrimaryArray < Array
3
+
4
+ def primary
5
+ find_primary
6
+ end
7
+
8
+ private
9
+
10
+ # This is a very simplistic but reliable implementation.
11
+ def find_primary
12
+ self.each do |arg|
13
+ if arg.primary?
14
+ return arg
15
+ end
16
+ end
17
+ nil
18
+ end
19
+ end
20
+
21
+ #=== Primary: interface to implement for elements that are added to a "PrimaryArray" collection
22
+ module Primary
23
+ # Return true if the element is the primary resource in a collection.
24
+ # Default implementation looks for a "Primary" attribute
25
+ def primary?
26
+ @attributes.key?("Primary") && self.Primary == true
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module FlexmlsApi
2
+ class PrimaryArray < Array
3
+
4
+ def primary
5
+ find_primary
6
+ end
7
+
8
+ private
9
+
10
+ # This is a very simplistic but reliable implementation.
11
+ def find_primary
12
+ self.each do |arg|
13
+ if arg.primary?
14
+ return arg
15
+ end
16
+ end
17
+ nil
18
+ end
19
+ end
20
+
21
+ #=== Primary: interface to implement for elements that are added to a "PrimaryArray" collection
22
+ module Primary
23
+ # Return true if the element is the primary resource in a collection.
24
+ # Default implementation looks for a "Primary" attribute
25
+ def primary?
26
+ @attributes.key?("Primary") && self.Primary == true
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,96 @@
1
+ require 'cgi'
2
+
3
+ module SparkApi
4
+ # HTTP request wrapper. Performs all the api session mumbo jumbo so that the models don't have to.
5
+ module Request
6
+ # Perform an HTTP GET request
7
+ #
8
+ # * path - Path of an api resource, excluding version and endpoint (domain) information
9
+ # * options - Resource request options as specified being supported via and api resource
10
+ # :returns:
11
+ # Hash of the json results as documented in the api.
12
+ # :raises:
13
+ # SparkApi::ClientError or subclass if the request failed.
14
+ def get(path, options={})
15
+ request(:get, path, nil, options)
16
+ end
17
+
18
+ # Perform an HTTP POST request
19
+ #
20
+ # * path - Path of an api resource, excluding version and endpoint (domain) information
21
+ # * body - Hash for post body data
22
+ # * options - Resource request options as specified being supported via and api resource
23
+ # :returns:
24
+ # Hash of the json results as documented in the api.
25
+ # :raises:
26
+ # SparkApi::ClientError or subclass if the request failed.
27
+ def post(path, body={}, options={})
28
+ request(:post, path, body, options)
29
+ end
30
+
31
+ # Perform an HTTP PUT request
32
+ #
33
+ # * path - Path of an api resource, excluding version and endpoint (domain) information
34
+ # * body - Hash for post body data
35
+ # * options - Resource request options as specified being supported via and api resource
36
+ # :returns:
37
+ # Hash of the json results as documented in the api.
38
+ # :raises:
39
+ # SparkApi::ClientError or subclass if the request failed.
40
+ def put(path, body={}, options={})
41
+ request(:put, path, body, options)
42
+ end
43
+
44
+ # Perform an HTTP DELETE request
45
+ #
46
+ # * path - Path of an api resource, excluding version and endpoint (domain) information
47
+ # * options - Resource request options as specified being supported via and api resource
48
+ # :returns:
49
+ # Hash of the json results as documented in the api.
50
+ # :raises:
51
+ # SparkApi::ClientError or subclass if the request failed.
52
+ def delete(path, options={})
53
+ request(:delete, path, nil, options)
54
+ end
55
+
56
+ private
57
+
58
+ # Perform an HTTP request (no data)
59
+ def request(method, path, body, options)
60
+ unless authenticated?
61
+ authenticate
62
+ end
63
+ attempts = 0
64
+ begin
65
+ request_opts = {}
66
+ request_opts.merge!(options)
67
+ post_data = body.nil? ? nil : {"D" => body }.to_json
68
+ request_path = "/#{version}#{path}"
69
+ start_time = Time.now
70
+ SparkApi.logger.debug("#{method.to_s.upcase} Request: #{request_path}")
71
+ if post_data.nil?
72
+ response = authenticator.request(method, request_path, nil, request_opts)
73
+ else
74
+ SparkApi.logger.debug("#{method.to_s.upcase} Data: #{post_data}")
75
+ response = authenticator.request(method, request_path, post_data, request_opts)
76
+ end
77
+ request_time = Time.now - start_time
78
+ SparkApi.logger.info("[#{(request_time * 1000).to_i}ms] Api: #{method.to_s.upcase} #{request_path}")
79
+ rescue PermissionDenied => e
80
+ if(ResponseCodes::SESSION_TOKEN_EXPIRED == e.code)
81
+ unless (attempts +=1) > 1
82
+ SparkApi.logger.debug("Retrying authentication")
83
+ authenticate
84
+ retry
85
+ end
86
+ end
87
+ # No luck authenticating... KABOOM!
88
+ SparkApi.logger.error("Authentication failed or server is sending us expired tokens, nothing we can do here.")
89
+ raise
90
+ end
91
+ response.body
92
+ end
93
+
94
+ end
95
+
96
+ end
@@ -0,0 +1,96 @@
1
+ require 'cgi'
2
+
3
+ module FlexmlsApi
4
+ # HTTP request wrapper. Performs all the api session mumbo jumbo so that the models don't have to.
5
+ module Request
6
+ # Perform an HTTP GET request
7
+ #
8
+ # * path - Path of an api resource, excluding version and endpoint (domain) information
9
+ # * options - Resource request options as specified being supported via and api resource
10
+ # :returns:
11
+ # Hash of the json results as documented in the api.
12
+ # :raises:
13
+ # FlexmlsApi::ClientError or subclass if the request failed.
14
+ def get(path, options={})
15
+ request(:get, path, nil, options)
16
+ end
17
+
18
+ # Perform an HTTP POST request
19
+ #
20
+ # * path - Path of an api resource, excluding version and endpoint (domain) information
21
+ # * body - Hash for post body data
22
+ # * options - Resource request options as specified being supported via and api resource
23
+ # :returns:
24
+ # Hash of the json results as documented in the api.
25
+ # :raises:
26
+ # FlexmlsApi::ClientError or subclass if the request failed.
27
+ def post(path, body={}, options={})
28
+ request(:post, path, body, options)
29
+ end
30
+
31
+ # Perform an HTTP PUT request
32
+ #
33
+ # * path - Path of an api resource, excluding version and endpoint (domain) information
34
+ # * body - Hash for post body data
35
+ # * options - Resource request options as specified being supported via and api resource
36
+ # :returns:
37
+ # Hash of the json results as documented in the api.
38
+ # :raises:
39
+ # FlexmlsApi::ClientError or subclass if the request failed.
40
+ def put(path, body={}, options={})
41
+ request(:put, path, body, options)
42
+ end
43
+
44
+ # Perform an HTTP DELETE request
45
+ #
46
+ # * path - Path of an api resource, excluding version and endpoint (domain) information
47
+ # * options - Resource request options as specified being supported via and api resource
48
+ # :returns:
49
+ # Hash of the json results as documented in the api.
50
+ # :raises:
51
+ # FlexmlsApi::ClientError or subclass if the request failed.
52
+ def delete(path, options={})
53
+ request(:delete, path, nil, options)
54
+ end
55
+
56
+ private
57
+
58
+ # Perform an HTTP request (no data)
59
+ def request(method, path, body, options)
60
+ unless authenticated?
61
+ authenticate
62
+ end
63
+ attempts = 0
64
+ begin
65
+ request_opts = {}
66
+ request_opts.merge!(options)
67
+ post_data = body.nil? ? nil : {"D" => body }.to_json
68
+ request_path = "/#{version}#{path}"
69
+ start_time = Time.now
70
+ FlexmlsApi.logger.debug("#{method.to_s.upcase} Request: #{request_path}")
71
+ if post_data.nil?
72
+ response = authenticator.request(method, request_path, nil, request_opts)
73
+ else
74
+ FlexmlsApi.logger.debug("#{method.to_s.upcase} Data: #{post_data}")
75
+ response = authenticator.request(method, request_path, post_data, request_opts)
76
+ end
77
+ request_time = Time.now - start_time
78
+ FlexmlsApi.logger.info("[#{(request_time * 1000).to_i}ms] Api: #{method.to_s.upcase} #{request_path}")
79
+ rescue PermissionDenied => e
80
+ if(ResponseCodes::SESSION_TOKEN_EXPIRED == e.code)
81
+ unless (attempts +=1) > 1
82
+ FlexmlsApi.logger.debug("Retrying authentication")
83
+ authenticate
84
+ retry
85
+ end
86
+ end
87
+ # No luck authenticating... KABOOM!
88
+ FlexmlsApi.logger.error("Authentication failed or server is sending us expired tokens, nothing we can do here.")
89
+ raise
90
+ end
91
+ response.body
92
+ end
93
+
94
+ end
95
+
96
+ end