active_cached_resource 0.0.1.pre

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 (340) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +22 -0
  4. data/.standard.yml +2 -0
  5. data/CHANGELOG.md +5 -0
  6. data/README.md +45 -0
  7. data/Rakefile +21 -0
  8. data/example/consumer/.dockerignore +41 -0
  9. data/example/consumer/.gitattributes +9 -0
  10. data/example/consumer/.gitignore +36 -0
  11. data/example/consumer/.kamal/hooks/docker-setup.sample +3 -0
  12. data/example/consumer/.kamal/hooks/post-deploy.sample +14 -0
  13. data/example/consumer/.kamal/hooks/post-proxy-reboot.sample +3 -0
  14. data/example/consumer/.kamal/hooks/pre-build.sample +51 -0
  15. data/example/consumer/.kamal/hooks/pre-connect.sample +47 -0
  16. data/example/consumer/.kamal/hooks/pre-deploy.sample +109 -0
  17. data/example/consumer/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  18. data/example/consumer/.kamal/secrets +17 -0
  19. data/example/consumer/Dockerfile +65 -0
  20. data/example/consumer/Gemfile +17 -0
  21. data/example/consumer/Rakefile +6 -0
  22. data/example/consumer/app/controllers/application_controller.rb +2 -0
  23. data/example/consumer/app/controllers/concerns/.keep +0 -0
  24. data/example/consumer/app/jobs/application_job.rb +7 -0
  25. data/example/consumer/app/mailers/application_mailer.rb +4 -0
  26. data/example/consumer/app/models/application_record.rb +3 -0
  27. data/example/consumer/app/models/concerns/.keep +0 -0
  28. data/example/consumer/app/models/person.rb +9 -0
  29. data/example/consumer/app/views/layouts/mailer.html.erb +13 -0
  30. data/example/consumer/app/views/layouts/mailer.text.erb +1 -0
  31. data/example/consumer/bin/brakeman +7 -0
  32. data/example/consumer/bin/bundle +109 -0
  33. data/example/consumer/bin/dev +2 -0
  34. data/example/consumer/bin/docker-entrypoint +14 -0
  35. data/example/consumer/bin/jobs +6 -0
  36. data/example/consumer/bin/kamal +27 -0
  37. data/example/consumer/bin/rails +4 -0
  38. data/example/consumer/bin/rake +4 -0
  39. data/example/consumer/bin/rubocop +8 -0
  40. data/example/consumer/bin/setup +34 -0
  41. data/example/consumer/bin/thrust +5 -0
  42. data/example/consumer/config/application.rb +20 -0
  43. data/example/consumer/config/boot.rb +3 -0
  44. data/example/consumer/config/cache.yml +16 -0
  45. data/example/consumer/config/credentials.yml.enc +1 -0
  46. data/example/consumer/config/database.yml +14 -0
  47. data/example/consumer/config/deploy.yml +116 -0
  48. data/example/consumer/config/environment.rb +5 -0
  49. data/example/consumer/config/environments/development.rb +64 -0
  50. data/example/consumer/config/environments/production.rb +85 -0
  51. data/example/consumer/config/environments/test.rb +50 -0
  52. data/example/consumer/config/initializers/cors.rb +16 -0
  53. data/example/consumer/config/initializers/filter_parameter_logging.rb +8 -0
  54. data/example/consumer/config/initializers/inflections.rb +16 -0
  55. data/example/consumer/config/locales/en.yml +31 -0
  56. data/example/consumer/config/puma.rb +41 -0
  57. data/example/consumer/config/queue.yml +18 -0
  58. data/example/consumer/config/recurring.yml +10 -0
  59. data/example/consumer/config/routes.rb +10 -0
  60. data/example/consumer/config.ru +6 -0
  61. data/example/consumer/db/cache_schema.rb +14 -0
  62. data/example/consumer/db/queue_schema.rb +129 -0
  63. data/example/consumer/db/seeds.rb +0 -0
  64. data/example/consumer/lib/tasks/.keep +0 -0
  65. data/example/consumer/log/.keep +0 -0
  66. data/example/consumer/public/robots.txt +1 -0
  67. data/example/consumer/script/.keep +0 -0
  68. data/example/consumer/storage/.keep +0 -0
  69. data/example/consumer/tmp/.keep +0 -0
  70. data/example/consumer/tmp/cache/.keep +0 -0
  71. data/example/consumer/tmp/pids/.keep +0 -0
  72. data/example/consumer/tmp/storage/.keep +0 -0
  73. data/example/consumer/vendor/.keep +0 -0
  74. data/example/provider/.dockerignore +41 -0
  75. data/example/provider/.gitattributes +9 -0
  76. data/example/provider/.gitignore +32 -0
  77. data/example/provider/.kamal/hooks/docker-setup.sample +3 -0
  78. data/example/provider/.kamal/hooks/post-deploy.sample +14 -0
  79. data/example/provider/.kamal/hooks/post-proxy-reboot.sample +3 -0
  80. data/example/provider/.kamal/hooks/pre-build.sample +51 -0
  81. data/example/provider/.kamal/hooks/pre-connect.sample +47 -0
  82. data/example/provider/.kamal/hooks/pre-deploy.sample +109 -0
  83. data/example/provider/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  84. data/example/provider/.kamal/secrets +17 -0
  85. data/example/provider/Dockerfile +65 -0
  86. data/example/provider/Gemfile +14 -0
  87. data/example/provider/Rakefile +6 -0
  88. data/example/provider/app/controllers/application_controller.rb +2 -0
  89. data/example/provider/app/controllers/concerns/.keep +0 -0
  90. data/example/provider/app/controllers/people_controller.rb +68 -0
  91. data/example/provider/app/jobs/application_job.rb +7 -0
  92. data/example/provider/app/mailers/application_mailer.rb +4 -0
  93. data/example/provider/app/models/address.rb +3 -0
  94. data/example/provider/app/models/application_record.rb +3 -0
  95. data/example/provider/app/models/company.rb +3 -0
  96. data/example/provider/app/models/concerns/.keep +0 -0
  97. data/example/provider/app/models/person.rb +6 -0
  98. data/example/provider/app/views/layouts/mailer.html.erb +13 -0
  99. data/example/provider/app/views/layouts/mailer.text.erb +1 -0
  100. data/example/provider/bin/brakeman +7 -0
  101. data/example/provider/bin/bundle +109 -0
  102. data/example/provider/bin/dev +2 -0
  103. data/example/provider/bin/docker-entrypoint +14 -0
  104. data/example/provider/bin/jobs +6 -0
  105. data/example/provider/bin/kamal +27 -0
  106. data/example/provider/bin/rails +4 -0
  107. data/example/provider/bin/rake +4 -0
  108. data/example/provider/bin/rubocop +8 -0
  109. data/example/provider/bin/setup +34 -0
  110. data/example/provider/bin/thrust +5 -0
  111. data/example/provider/config/application.rb +44 -0
  112. data/example/provider/config/boot.rb +3 -0
  113. data/example/provider/config/cache.yml +16 -0
  114. data/example/provider/config/credentials.yml.enc +1 -0
  115. data/example/provider/config/database.yml +20 -0
  116. data/example/provider/config/deploy.yml +116 -0
  117. data/example/provider/config/environment.rb +5 -0
  118. data/example/provider/config/environments/development.rb +64 -0
  119. data/example/provider/config/environments/production.rb +85 -0
  120. data/example/provider/config/environments/test.rb +50 -0
  121. data/example/provider/config/initializers/cors.rb +16 -0
  122. data/example/provider/config/initializers/filter_parameter_logging.rb +8 -0
  123. data/example/provider/config/initializers/inflections.rb +16 -0
  124. data/example/provider/config/locales/en.yml +31 -0
  125. data/example/provider/config/puma.rb +41 -0
  126. data/example/provider/config/queue.yml +18 -0
  127. data/example/provider/config/recurring.yml +10 -0
  128. data/example/provider/config/routes.rb +4 -0
  129. data/example/provider/config.ru +6 -0
  130. data/example/provider/db/cache_schema.rb +14 -0
  131. data/example/provider/db/migrate/20241202183937_create_people.rb +11 -0
  132. data/example/provider/db/migrate/20241202183955_create_addresses.rb +13 -0
  133. data/example/provider/db/migrate/20241202184017_create_companies.rb +14 -0
  134. data/example/provider/db/queue_schema.rb +129 -0
  135. data/example/provider/db/schema.rb +47 -0
  136. data/example/provider/db/seeds.rb +18 -0
  137. data/example/provider/lib/tasks/.keep +0 -0
  138. data/example/provider/log/.keep +0 -0
  139. data/example/provider/public/robots.txt +1 -0
  140. data/example/provider/script/.keep +0 -0
  141. data/example/provider/storage/.keep +0 -0
  142. data/example/provider/tmp/.keep +0 -0
  143. data/example/provider/tmp/pids/.keep +0 -0
  144. data/example/provider/tmp/storage/.keep +0 -0
  145. data/example/provider/vendor/.keep +0 -0
  146. data/lib/active_cached_resource/caching.rb +176 -0
  147. data/lib/active_cached_resource/caching_strategies/active_support_cache.rb +31 -0
  148. data/lib/active_cached_resource/caching_strategies/base.rb +114 -0
  149. data/lib/active_cached_resource/caching_strategies/sql_cache.rb +32 -0
  150. data/lib/active_cached_resource/configuration.rb +50 -0
  151. data/lib/active_cached_resource/logger.rb +22 -0
  152. data/lib/active_cached_resource/model.rb +33 -0
  153. data/lib/active_cached_resource/version.rb +12 -0
  154. data/lib/active_cached_resource.rb +64 -0
  155. data/lib/activeresource/.gitignore +15 -0
  156. data/lib/activeresource/README.md +283 -0
  157. data/lib/activeresource/examples/performance.rb +72 -0
  158. data/lib/activeresource/lib/active_resource/active_job_serializer.rb +26 -0
  159. data/lib/activeresource/lib/active_resource/associations/builder/association.rb +32 -0
  160. data/lib/activeresource/lib/active_resource/associations/builder/belongs_to.rb +16 -0
  161. data/lib/activeresource/lib/active_resource/associations/builder/has_many.rb +14 -0
  162. data/lib/activeresource/lib/active_resource/associations/builder/has_one.rb +14 -0
  163. data/lib/activeresource/lib/active_resource/associations.rb +175 -0
  164. data/lib/activeresource/lib/active_resource/base.rb +1741 -0
  165. data/lib/activeresource/lib/active_resource/callbacks.rb +22 -0
  166. data/lib/activeresource/lib/active_resource/collection.rb +214 -0
  167. data/lib/activeresource/lib/active_resource/connection.rb +298 -0
  168. data/lib/activeresource/lib/active_resource/custom_methods.rb +129 -0
  169. data/lib/activeresource/lib/active_resource/exceptions.rb +98 -0
  170. data/lib/activeresource/lib/active_resource/formats/json_format.rb +28 -0
  171. data/lib/activeresource/lib/active_resource/formats/xml_format.rb +27 -0
  172. data/lib/activeresource/lib/active_resource/formats.rb +24 -0
  173. data/lib/activeresource/lib/active_resource/http_mock.rb +386 -0
  174. data/lib/activeresource/lib/active_resource/inheriting_hash.rb +34 -0
  175. data/lib/activeresource/lib/active_resource/log_subscriber.rb +26 -0
  176. data/lib/activeresource/lib/active_resource/railtie.rb +31 -0
  177. data/lib/activeresource/lib/active_resource/reflection.rb +78 -0
  178. data/lib/activeresource/lib/active_resource/schema.rb +60 -0
  179. data/lib/activeresource/lib/active_resource/singleton.rb +111 -0
  180. data/lib/activeresource/lib/active_resource/threadsafe_attributes.rb +65 -0
  181. data/lib/activeresource/lib/active_resource/validations.rb +176 -0
  182. data/lib/activeresource/lib/active_resource.rb +49 -0
  183. data/lib/activeresource/lib/activeresource.rb +3 -0
  184. data/lib/activeresource/test/abstract_unit.rb +153 -0
  185. data/lib/activeresource/test/cases/active_job_serializer_test.rb +53 -0
  186. data/lib/activeresource/test/cases/association_test.rb +104 -0
  187. data/lib/activeresource/test/cases/associations/builder/belongs_to_test.rb +42 -0
  188. data/lib/activeresource/test/cases/associations/builder/has_many_test.rb +28 -0
  189. data/lib/activeresource/test/cases/associations/builder/has_one_test.rb +28 -0
  190. data/lib/activeresource/test/cases/authorization_test.rb +276 -0
  191. data/lib/activeresource/test/cases/base/custom_methods_test.rb +155 -0
  192. data/lib/activeresource/test/cases/base/equality_test.rb +53 -0
  193. data/lib/activeresource/test/cases/base/load_test.rb +249 -0
  194. data/lib/activeresource/test/cases/base/schema_test.rb +428 -0
  195. data/lib/activeresource/test/cases/base_errors_test.rb +129 -0
  196. data/lib/activeresource/test/cases/base_test.rb +1622 -0
  197. data/lib/activeresource/test/cases/callbacks_test.rb +155 -0
  198. data/lib/activeresource/test/cases/collection_test.rb +172 -0
  199. data/lib/activeresource/test/cases/connection_test.rb +357 -0
  200. data/lib/activeresource/test/cases/finder_test.rb +217 -0
  201. data/lib/activeresource/test/cases/format_test.rb +137 -0
  202. data/lib/activeresource/test/cases/http_mock_test.rb +213 -0
  203. data/lib/activeresource/test/cases/inheritence_test.rb +19 -0
  204. data/lib/activeresource/test/cases/inheriting_hash_test.rb +25 -0
  205. data/lib/activeresource/test/cases/log_subscriber_test.rb +63 -0
  206. data/lib/activeresource/test/cases/reflection_test.rb +65 -0
  207. data/lib/activeresource/test/cases/validations_test.rb +78 -0
  208. data/lib/activeresource/test/fixtures/address.rb +20 -0
  209. data/lib/activeresource/test/fixtures/beast.rb +16 -0
  210. data/lib/activeresource/test/fixtures/comment.rb +5 -0
  211. data/lib/activeresource/test/fixtures/customer.rb +5 -0
  212. data/lib/activeresource/test/fixtures/inventory.rb +14 -0
  213. data/lib/activeresource/test/fixtures/person.rb +15 -0
  214. data/lib/activeresource/test/fixtures/pet.rb +6 -0
  215. data/lib/activeresource/test/fixtures/post.rb +5 -0
  216. data/lib/activeresource/test/fixtures/product.rb +11 -0
  217. data/lib/activeresource/test/fixtures/project.rb +19 -0
  218. data/lib/activeresource/test/fixtures/proxy.rb +6 -0
  219. data/lib/activeresource/test/fixtures/sound.rb +11 -0
  220. data/lib/activeresource/test/fixtures/street_address.rb +6 -0
  221. data/lib/activeresource/test/fixtures/subscription_plan.rb +7 -0
  222. data/lib/activeresource/test/fixtures/weather.rb +21 -0
  223. data/lib/activeresource/test/setter_trap.rb +28 -0
  224. data/lib/activeresource/test/singleton_test.rb +138 -0
  225. data/lib/activeresource/test/threadsafe_attributes_test.rb +91 -0
  226. data/lib/generators/active_cached_resource/install_generator.rb +31 -0
  227. data/lib/generators/active_cached_resource/templates/migration.erb +16 -0
  228. data/sorbet/config +4 -0
  229. data/sorbet/rbi/annotations/.gitattributes +1 -0
  230. data/sorbet/rbi/annotations/activemodel.rbi +89 -0
  231. data/sorbet/rbi/annotations/activesupport.rbi +457 -0
  232. data/sorbet/rbi/annotations/minitest.rbi +119 -0
  233. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  234. data/sorbet/rbi/dsl/.gitattributes +1 -0
  235. data/sorbet/rbi/dsl/active_support/callbacks.rbi +21 -0
  236. data/sorbet/rbi/gems/.gitattributes +1 -0
  237. data/sorbet/rbi/gems/actioncable@8.0.0.rbi +252 -0
  238. data/sorbet/rbi/gems/actionmailbox@8.0.0.rbi +9 -0
  239. data/sorbet/rbi/gems/actionmailer@8.0.0.rbi +9 -0
  240. data/sorbet/rbi/gems/actionpack@8.0.0.rbi +20909 -0
  241. data/sorbet/rbi/gems/actiontext@8.0.0.rbi +9 -0
  242. data/sorbet/rbi/gems/actionview@8.0.0.rbi +16207 -0
  243. data/sorbet/rbi/gems/activejob@8.0.0.rbi +9 -0
  244. data/sorbet/rbi/gems/activemodel-serializers-xml@1.0.3.rbi +166 -0
  245. data/sorbet/rbi/gems/activemodel@8.0.0.rbi +6857 -0
  246. data/sorbet/rbi/gems/activerecord@8.0.0.rbi +42896 -0
  247. data/sorbet/rbi/gems/activeresource@6.1.4.rbi +3944 -0
  248. data/sorbet/rbi/gems/activestorage@8.0.0.rbi +9 -0
  249. data/sorbet/rbi/gems/activesupport@8.0.0.rbi +21251 -0
  250. data/sorbet/rbi/gems/ast@2.4.2.rbi +585 -0
  251. data/sorbet/rbi/gems/base64@0.2.0.rbi +509 -0
  252. data/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
  253. data/sorbet/rbi/gems/bigdecimal@3.1.8.rbi +78 -0
  254. data/sorbet/rbi/gems/builder@3.3.0.rbi +9 -0
  255. data/sorbet/rbi/gems/bump@0.10.0.rbi +169 -0
  256. data/sorbet/rbi/gems/byebug@11.1.3.rbi +3607 -0
  257. data/sorbet/rbi/gems/coderay@1.1.3.rbi +3427 -0
  258. data/sorbet/rbi/gems/concurrent-ruby@1.3.4.rbi +11645 -0
  259. data/sorbet/rbi/gems/connection_pool@2.4.1.rbi +9 -0
  260. data/sorbet/rbi/gems/crass@1.0.6.rbi +623 -0
  261. data/sorbet/rbi/gems/date@3.4.0.rbi +75 -0
  262. data/sorbet/rbi/gems/diff-lcs@1.5.1.rbi +1131 -0
  263. data/sorbet/rbi/gems/docile@1.4.1.rbi +377 -0
  264. data/sorbet/rbi/gems/drb@2.2.1.rbi +1347 -0
  265. data/sorbet/rbi/gems/erubi@1.13.0.rbi +150 -0
  266. data/sorbet/rbi/gems/globalid@1.2.1.rbi +9 -0
  267. data/sorbet/rbi/gems/i18n@1.14.6.rbi +2359 -0
  268. data/sorbet/rbi/gems/io-console@0.7.2.rbi +9 -0
  269. data/sorbet/rbi/gems/json@2.8.2.rbi +1901 -0
  270. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14238 -0
  271. data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +240 -0
  272. data/sorbet/rbi/gems/logger@1.6.1.rbi +920 -0
  273. data/sorbet/rbi/gems/loofah@2.23.1.rbi +1081 -0
  274. data/sorbet/rbi/gems/mail@2.8.1.rbi +9 -0
  275. data/sorbet/rbi/gems/marcel@1.0.4.rbi +9 -0
  276. data/sorbet/rbi/gems/method_source@1.1.0.rbi +304 -0
  277. data/sorbet/rbi/gems/mini_mime@1.1.5.rbi +9 -0
  278. data/sorbet/rbi/gems/minitest@5.25.2.rbi +1547 -0
  279. data/sorbet/rbi/gems/net-imap@0.5.1.rbi +9 -0
  280. data/sorbet/rbi/gems/net-pop@0.1.2.rbi +9 -0
  281. data/sorbet/rbi/gems/net-protocol@0.2.2.rbi +292 -0
  282. data/sorbet/rbi/gems/net-smtp@0.5.0.rbi +9 -0
  283. data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
  284. data/sorbet/rbi/gems/nio4r@2.7.4.rbi +9 -0
  285. data/sorbet/rbi/gems/nokogiri@1.16.7.rbi +7311 -0
  286. data/sorbet/rbi/gems/parallel@1.26.3.rbi +291 -0
  287. data/sorbet/rbi/gems/parser@3.3.6.0.rbi +5519 -0
  288. data/sorbet/rbi/gems/prism@1.2.0.rbi +39085 -0
  289. data/sorbet/rbi/gems/pry-byebug@3.10.1.rbi +1151 -0
  290. data/sorbet/rbi/gems/pry@0.14.2.rbi +10076 -0
  291. data/sorbet/rbi/gems/psych@5.2.0.rbi +1785 -0
  292. data/sorbet/rbi/gems/racc@1.8.1.rbi +162 -0
  293. data/sorbet/rbi/gems/rack-session@2.0.0.rbi +727 -0
  294. data/sorbet/rbi/gems/rack-test@2.1.0.rbi +747 -0
  295. data/sorbet/rbi/gems/rack@3.1.8.rbi +4905 -0
  296. data/sorbet/rbi/gems/rackup@2.2.1.rbi +230 -0
  297. data/sorbet/rbi/gems/rails-dom-testing@2.2.0.rbi +758 -0
  298. data/sorbet/rbi/gems/rails-html-sanitizer@1.6.0.rbi +785 -0
  299. data/sorbet/rbi/gems/rails@8.0.0.rbi +9 -0
  300. data/sorbet/rbi/gems/railties@8.0.0.rbi +6287 -0
  301. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +403 -0
  302. data/sorbet/rbi/gems/rake@13.2.1.rbi +3091 -0
  303. data/sorbet/rbi/gems/rbi@0.2.1.rbi +4535 -0
  304. data/sorbet/rbi/gems/rdoc@6.8.1.rbi +12572 -0
  305. data/sorbet/rbi/gems/regexp_parser@2.9.2.rbi +3772 -0
  306. data/sorbet/rbi/gems/reline@0.5.12.rbi +2416 -0
  307. data/sorbet/rbi/gems/rexml@3.3.9.rbi +4858 -0
  308. data/sorbet/rbi/gems/rspec-core@3.13.2.rbi +11287 -0
  309. data/sorbet/rbi/gems/rspec-expectations@3.13.3.rbi +8183 -0
  310. data/sorbet/rbi/gems/rspec-mocks@3.13.2.rbi +5341 -0
  311. data/sorbet/rbi/gems/rspec-support@3.13.1.rbi +1630 -0
  312. data/sorbet/rbi/gems/rspec@3.13.0.rbi +83 -0
  313. data/sorbet/rbi/gems/rubocop-ast@1.36.1.rbi +7303 -0
  314. data/sorbet/rbi/gems/rubocop-performance@1.21.1.rbi +9 -0
  315. data/sorbet/rbi/gems/rubocop@1.65.1.rbi +58170 -0
  316. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1318 -0
  317. data/sorbet/rbi/gems/securerandom@0.3.2.rbi +395 -0
  318. data/sorbet/rbi/gems/simplecov-html@0.13.1.rbi +225 -0
  319. data/sorbet/rbi/gems/simplecov@0.22.0.rbi +2149 -0
  320. data/sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi +9 -0
  321. data/sorbet/rbi/gems/spoom@1.5.0.rbi +4932 -0
  322. data/sorbet/rbi/gems/standard-custom@1.0.2.rbi +9 -0
  323. data/sorbet/rbi/gems/standard-performance@1.4.0.rbi +9 -0
  324. data/sorbet/rbi/gems/standard@1.40.0.rbi +929 -0
  325. data/sorbet/rbi/gems/stringio@3.1.2.rbi +9 -0
  326. data/sorbet/rbi/gems/tapioca@0.16.4.rbi +3597 -0
  327. data/sorbet/rbi/gems/thor@1.3.2.rbi +4378 -0
  328. data/sorbet/rbi/gems/timeout@0.4.2.rbi +151 -0
  329. data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5918 -0
  330. data/sorbet/rbi/gems/unicode-display_width@2.6.0.rbi +66 -0
  331. data/sorbet/rbi/gems/uri@1.0.2.rbi +2377 -0
  332. data/sorbet/rbi/gems/useragent@0.16.10.rbi +9 -0
  333. data/sorbet/rbi/gems/websocket-driver@0.7.6.rbi +9 -0
  334. data/sorbet/rbi/gems/websocket-extensions@0.1.5.rbi +9 -0
  335. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
  336. data/sorbet/rbi/gems/yard@0.9.37.rbi +18504 -0
  337. data/sorbet/rbi/gems/zeitwerk@2.7.1.rbi +9 -0
  338. data/sorbet/tapioca/config.yml +13 -0
  339. data/sorbet/tapioca/require.rb +12 -0
  340. metadata +543 -0
@@ -0,0 +1,1741 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/core_ext/class/attribute_accessors"
5
+ require "active_support/core_ext/class/attribute"
6
+ require "active_support/core_ext/hash/indifferent_access"
7
+ require "active_support/core_ext/kernel/reporting"
8
+ require "active_support/core_ext/module/delegation"
9
+ require "active_support/core_ext/module/aliasing"
10
+ require "active_support/core_ext/object/blank"
11
+ require "active_support/core_ext/object/to_query"
12
+ require "active_support/core_ext/object/duplicable"
13
+ require "set"
14
+
15
+ require_relative "connection"
16
+ require_relative "formats"
17
+ require_relative "schema"
18
+ require_relative "log_subscriber"
19
+ require_relative "associations"
20
+ require_relative "reflection"
21
+ require_relative "threadsafe_attributes"
22
+
23
+ require "active_model/serializers/xml"
24
+
25
+ module ActiveResource
26
+ # ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application.
27
+ #
28
+ # For an outline of what Active Resource is capable of, see its {README}[link:files/activeresource/README_rdoc.html].
29
+ #
30
+ # == Automated mapping
31
+ #
32
+ # Active Resource objects represent your RESTful resources as manipulatable Ruby objects. To map resources
33
+ # to Ruby objects, Active Resource only needs a class name that corresponds to the resource name (e.g., the class
34
+ # Person maps to the resources people, very similarly to Active Record) and a +site+ value, which holds the
35
+ # URI of the resources.
36
+ #
37
+ # class Person < ActiveResource::Base
38
+ # self.site = "https://api.people.com"
39
+ # end
40
+ #
41
+ # Now the Person class is mapped to RESTful resources located at <tt>https://api.people.com/people/</tt>, and
42
+ # you can now use Active Resource's life cycle methods to manipulate resources. In the case where you already have
43
+ # an existing model with the same name as the desired RESTful resource you can set the +element_name+ value.
44
+ #
45
+ # class PersonResource < ActiveResource::Base
46
+ # self.site = "https://api.people.com"
47
+ # self.element_name = "person"
48
+ # end
49
+ #
50
+ # If your Active Resource object is required to use an HTTP proxy you can set the +proxy+ value which holds a URI.
51
+ #
52
+ # class PersonResource < ActiveResource::Base
53
+ # self.site = "https://api.people.com"
54
+ # self.proxy = "https://user:password@proxy.people.com:8080"
55
+ # end
56
+ #
57
+ #
58
+ # == Life cycle methods
59
+ #
60
+ # Active Resource exposes methods for creating, finding, updating, and deleting resources
61
+ # from REST web services.
62
+ #
63
+ # ryan = Person.new(:first => 'Ryan', :last => 'Daigle')
64
+ # ryan.save # => true
65
+ # ryan.id # => 2
66
+ # Person.exists?(ryan.id) # => true
67
+ # ryan.exists? # => true
68
+ #
69
+ # ryan = Person.find(1)
70
+ # # Resource holding our newly created Person object
71
+ #
72
+ # ryan.first = 'Rizzle'
73
+ # ryan.save # => true
74
+ #
75
+ # ryan.destroy # => true
76
+ #
77
+ # As you can see, these are very similar to Active Record's life cycle methods for database records.
78
+ # You can read more about each of these methods in their respective documentation.
79
+ #
80
+ # === Custom REST methods
81
+ #
82
+ # Since simple CRUD/life cycle methods can't accomplish every task, Active Resource also supports
83
+ # defining your own custom REST methods. To invoke them, Active Resource provides the <tt>get</tt>,
84
+ # <tt>post</tt>, <tt>put</tt> and <tt>delete</tt> methods where you can specify a custom REST method
85
+ # name to invoke.
86
+ #
87
+ # # POST to the custom 'register' REST method, i.e. POST /people/new/register.json.
88
+ # Person.new(:name => 'Ryan').post(:register)
89
+ # # => { :id => 1, :name => 'Ryan', :position => 'Clerk' }
90
+ #
91
+ # # PUT an update by invoking the 'promote' REST method, i.e. PUT /people/1/promote.json?position=Manager.
92
+ # Person.find(1).put(:promote, :position => 'Manager')
93
+ # # => { :id => 1, :name => 'Ryan', :position => 'Manager' }
94
+ #
95
+ # # GET all the positions available, i.e. GET /people/positions.json.
96
+ # Person.get(:positions)
97
+ # # => [{:name => 'Manager'}, {:name => 'Clerk'}]
98
+ #
99
+ # # DELETE to 'fire' a person, i.e. DELETE /people/1/fire.json.
100
+ # Person.find(1).delete(:fire)
101
+ #
102
+ # For more information on using custom REST methods, see the
103
+ # ActiveResource::CustomMethods documentation.
104
+ #
105
+ # == Validations
106
+ #
107
+ # You can validate resources client side by overriding validation methods in the base class.
108
+ #
109
+ # class Person < ActiveResource::Base
110
+ # self.site = "https://api.people.com"
111
+ # protected
112
+ # def validate
113
+ # errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/
114
+ # end
115
+ # end
116
+ #
117
+ # See the ActiveResource::Validations documentation for more information.
118
+ #
119
+ # == Authentication
120
+ #
121
+ # Many REST APIs require authentication. The HTTP spec describes two ways to
122
+ # make requests with a username and password (see RFC 2617).
123
+ #
124
+ # Basic authentication simply sends a username and password along with HTTP
125
+ # requests. These sensitive credentials are sent unencrypted, visible to
126
+ # any onlooker, so this scheme should only be used with SSL.
127
+ #
128
+ # Digest authentication sends a cryptographic hash of the username, password,
129
+ # HTTP method, URI, and a single-use secret key provided by the server.
130
+ # Sensitive credentials aren't visible to onlookers, so digest authentication
131
+ # doesn't require SSL. However, this doesn't mean the connection is secure!
132
+ # Just the username and password.
133
+ #
134
+ # Another common way to authenticate requests is via bearer tokens, a scheme
135
+ # originally created as part of the OAuth 2.0 protocol (see RFC 6750).
136
+ #
137
+ # Bearer authentication sends a token, that can maybe only be a short string
138
+ # of hexadecimal characters or even a JWT Token. Similarly to the Basic
139
+ # authentication, this scheme should only be used with SSL.
140
+ #
141
+ # (You really, really want to use SSL. There's little reason not to.)
142
+ #
143
+ # === Picking an authentication scheme
144
+ #
145
+ # Basic authentication is the default. To switch to digest or bearer token authentication,
146
+ # set +auth_type+ to +:digest+ or +:bearer+:
147
+ #
148
+ # class Person < ActiveResource::Base
149
+ # self.auth_type = :digest
150
+ # end
151
+ #
152
+ # === Setting the username and password
153
+ #
154
+ # Set +user+ and +password+ on the class, or include them in the +site+ URL.
155
+ #
156
+ # class Person < ActiveResource::Base
157
+ # # Set user and password directly:
158
+ # self.user = "ryan"
159
+ # self.password = "password"
160
+ #
161
+ # # Or include them in the site:
162
+ # self.site = "https://ryan:password@api.people.com"
163
+ # end
164
+ #
165
+ # === Setting the bearer token
166
+ #
167
+ # Set +bearer_token+ on the class:
168
+ #
169
+ # class Person < ActiveResource::Base
170
+ # # Set bearer token directly:
171
+ # self.auth_type = :bearer
172
+ # self.bearer_token = "my-bearer-token"
173
+ # end
174
+ #
175
+ # === Certificate Authentication
176
+ #
177
+ # You can also authenticate using an X509 certificate. <tt>See ssl_options=</tt> for all options.
178
+ #
179
+ # class Person < ActiveResource::Base
180
+ # self.site = "https://secure.api.people.com/"
181
+ #
182
+ # File.open(pem_file_path, 'rb') do |pem_file|
183
+ # self.ssl_options = {
184
+ # cert: OpenSSL::X509::Certificate.new(pem_file),
185
+ # key: OpenSSL::PKey::RSA.new(pem_file),
186
+ # ca_path: "/path/to/OpenSSL/formatted/CA_Certs",
187
+ # verify_mode: OpenSSL::SSL::VERIFY_PEER }
188
+ # end
189
+ # end
190
+ #
191
+ #
192
+ # == Errors & Validation
193
+ #
194
+ # Error handling and validation is handled in much the same manner as you're used to seeing in
195
+ # Active Record. Both the response code in the HTTP response and the body of the response are used to
196
+ # indicate that an error occurred.
197
+ #
198
+ # === Resource errors
199
+ #
200
+ # When a GET is requested for a resource that does not exist, the HTTP <tt>404</tt> (Resource Not Found)
201
+ # response code will be returned from the server which will raise an ActiveResource::ResourceNotFound
202
+ # exception.
203
+ #
204
+ # # GET https://api.people.com/people/999.json
205
+ # ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound
206
+ #
207
+ #
208
+ # <tt>404</tt> is just one of the HTTP error response codes that Active Resource will handle with its own exception. The
209
+ # following HTTP response codes will also result in these exceptions:
210
+ #
211
+ # * 200..399 - Valid response. No exceptions, other than these redirects:
212
+ # * 301, 302, 303, 307 - ActiveResource::Redirection
213
+ # * 400 - ActiveResource::BadRequest
214
+ # * 401 - ActiveResource::UnauthorizedAccess
215
+ # * 402 - ActiveResource::PaymentRequired
216
+ # * 403 - ActiveResource::ForbiddenAccess
217
+ # * 404 - ActiveResource::ResourceNotFound
218
+ # * 405 - ActiveResource::MethodNotAllowed
219
+ # * 409 - ActiveResource::ResourceConflict
220
+ # * 410 - ActiveResource::ResourceGone
221
+ # * 412 - ActiveResource::PreconditionFailed
222
+ # * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
223
+ # * 429 - ActiveResource::TooManyRequests
224
+ # * 401..499 - ActiveResource::ClientError
225
+ # * 500..599 - ActiveResource::ServerError
226
+ # * Other - ActiveResource::ConnectionError
227
+ #
228
+ # These custom exceptions allow you to deal with resource errors more naturally and with more precision
229
+ # rather than returning a general HTTP error. For example:
230
+ #
231
+ # begin
232
+ # ryan = Person.find(my_id)
233
+ # rescue ActiveResource::ResourceNotFound
234
+ # redirect_to :action => 'not_found'
235
+ # rescue ActiveResource::ResourceConflict, ActiveResource::ResourceInvalid
236
+ # redirect_to :action => 'new'
237
+ # end
238
+ #
239
+ # When a GET is requested for a nested resource and you don't provide the prefix_param
240
+ # an ActiveResource::MissingPrefixParam will be raised.
241
+ #
242
+ # class Comment < ActiveResource::Base
243
+ # self.site = "https://someip.com/posts/:post_id"
244
+ # end
245
+ #
246
+ # Comment.find(1)
247
+ # # => ActiveResource::MissingPrefixParam: post_id prefix_option is missing
248
+ #
249
+ # === Validation errors
250
+ #
251
+ # Active Resource supports validations on resources and will return errors if any of these validations fail
252
+ # (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
253
+ # a response code of <tt>422</tt> and an JSON or XML representation of the validation errors. The save operation will
254
+ # then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
255
+ #
256
+ # ryan = Person.find(1)
257
+ # ryan.first # => ''
258
+ # ryan.save # => false
259
+ #
260
+ # # When
261
+ # # PUT https://api.people.com/people/1.xml
262
+ # # or
263
+ # # PUT https://api.people.com/people/1.json
264
+ # # is requested with invalid values, the response is:
265
+ # #
266
+ # # Response (422):
267
+ # # <errors><error>First cannot be empty</error></errors>
268
+ # # or
269
+ # # {"errors":{"first":["cannot be empty"]}}
270
+ # #
271
+ #
272
+ # ryan.errors.invalid?(:first) # => true
273
+ # ryan.errors.full_messages # => ['First cannot be empty']
274
+ #
275
+ # For backwards-compatibility with older endpoints, the following formats are also supported in JSON responses:
276
+ #
277
+ # # {"errors":['First cannot be empty']}
278
+ # # This was the required format for previous versions of ActiveResource
279
+ # # {"first":["cannot be empty"]}
280
+ # # This was the default format produced by respond_with in ActionController <3.2.1
281
+ #
282
+ # Parsing either of these formats will result in a deprecation warning.
283
+ #
284
+ # Learn more about Active Resource's validation features in the ActiveResource::Validations documentation.
285
+ #
286
+ # === Timeouts
287
+ #
288
+ # Active Resource relies on HTTP to access RESTful APIs and as such is inherently susceptible to slow or
289
+ # unresponsive servers. In such cases, your Active Resource method calls could \timeout. You can control the
290
+ # amount of time before Active Resource times out with the +timeout+ variable.
291
+ #
292
+ # class Person < ActiveResource::Base
293
+ # self.site = "https://api.people.com"
294
+ # self.timeout = 5
295
+ # end
296
+ #
297
+ # This sets the +timeout+ to 5 seconds. You can adjust the +timeout+ to a value suitable for the RESTful API
298
+ # you are accessing. It is recommended to set this to a reasonably low value to allow your Active Resource
299
+ # clients (especially if you are using Active Resource in a Rails application) to fail-fast (see
300
+ # http://en.wikipedia.org/wiki/Fail-fast) rather than cause cascading failures that could incapacitate your
301
+ # server.
302
+ #
303
+ # When a \timeout occurs, an ActiveResource::TimeoutError is raised. You should rescue from
304
+ # ActiveResource::TimeoutError in your Active Resource method calls.
305
+ #
306
+ # Internally, Active Resource relies on Ruby's Net::HTTP library to make HTTP requests. Setting +timeout+
307
+ # sets the <tt>read_timeout</tt> of the internal Net::HTTP instance to the same value. The default
308
+ # <tt>read_timeout</tt> is 60 seconds on most Ruby implementations.
309
+ #
310
+ # Active Resource also supports distinct +open_timeout+ (time to connect) and +read_timeout+ (how long to
311
+ # wait for an upstream response). This is inline with supported +Net::HTTP+ timeout configuration and allows
312
+ # for finer control of client timeouts depending on context.
313
+ #
314
+ # class Person < ActiveResource::Base
315
+ # self.site = "https://api.people.com"
316
+ # self.open_timeout = 2
317
+ # self.read_timeout = 10
318
+ # end
319
+ class Base
320
+ ##
321
+ # :singleton-method:
322
+ # The logger for diagnosing and tracing Active Resource calls.
323
+ cattr_reader :logger
324
+
325
+ def self.logger=(logger)
326
+ self._connection = nil
327
+ @@logger = logger
328
+ end
329
+
330
+ class_attribute :_format
331
+ class_attribute :_collection_parser
332
+ class_attribute :include_format_in_path
333
+ self.include_format_in_path = true
334
+
335
+ class_attribute :connection_class
336
+ self.connection_class = Connection
337
+
338
+ class << self
339
+ include ThreadsafeAttributes
340
+ threadsafe_attribute :_headers, :_connection, :_user, :_password, :_bearer_token, :_site, :_proxy
341
+
342
+ # Creates a schema for this resource - setting the attributes that are
343
+ # known prior to fetching an instance from the remote system.
344
+ #
345
+ # The schema helps define the set of <tt>known_attributes</tt> of the
346
+ # current resource.
347
+ #
348
+ # There is no need to specify a schema for your Active Resource. If
349
+ # you do not, the <tt>known_attributes</tt> will be guessed from the
350
+ # instance attributes returned when an instance is fetched from the
351
+ # remote system.
352
+ #
353
+ # example:
354
+ # class Person < ActiveResource::Base
355
+ # schema do
356
+ # # define each attribute separately
357
+ # attribute 'name', :string
358
+ #
359
+ # # or use the convenience methods and pass >=1 attribute names
360
+ # string 'eye_color', 'hair_color'
361
+ # integer 'age'
362
+ # float 'height', 'weight'
363
+ #
364
+ # # unsupported types should be left as strings
365
+ # # overload the accessor methods if you need to convert them
366
+ # attribute 'created_at', 'string'
367
+ # end
368
+ # end
369
+ #
370
+ # p = Person.new
371
+ # p.respond_to? :name # => true
372
+ # p.respond_to? :age # => true
373
+ # p.name # => nil
374
+ # p.age # => nil
375
+ #
376
+ # j = Person.find_by_name('John')
377
+ # <person><name>John</name><age>34</age><num_children>3</num_children></person>
378
+ # j.respond_to? :name # => true
379
+ # j.respond_to? :age # => true
380
+ # j.name # => 'John'
381
+ # j.age # => '34' # note this is a string!
382
+ # j.num_children # => '3' # note this is a string!
383
+ #
384
+ # p.num_children # => NoMethodError
385
+ #
386
+ # Attribute-types must be one of: <tt>string, text, integer, float, decimal, datetime, timestamp, time, date, binary, boolean</tt>
387
+ #
388
+ # Note: at present the attribute-type doesn't do anything, but stay
389
+ # tuned...
390
+ # Shortly it will also *cast* the value of the returned attribute.
391
+ # ie:
392
+ # j.age # => 34 # cast to an integer
393
+ # j.weight # => '65' # still a string!
394
+ #
395
+ def schema(&block)
396
+ if block_given?
397
+ schema_definition = Schema.new
398
+ schema_definition.instance_eval(&block)
399
+
400
+ # skip out if we didn't define anything
401
+ return unless schema_definition.attrs.present?
402
+
403
+ @schema ||= {}.with_indifferent_access
404
+ @known_attributes ||= []
405
+
406
+ schema_definition.attrs.each do |k, v|
407
+ @schema[k] = v
408
+ @known_attributes << k
409
+ end
410
+
411
+ @schema
412
+ else
413
+ @schema ||= nil
414
+ end
415
+ end
416
+
417
+ # Alternative, direct way to specify a <tt>schema</tt> for this
418
+ # Resource. <tt>schema</tt> is more flexible, but this is quick
419
+ # for a very simple schema.
420
+ #
421
+ # Pass the schema as a hash with the keys being the attribute-names
422
+ # and the value being one of the accepted attribute types (as defined
423
+ # in <tt>schema</tt>)
424
+ #
425
+ # example:
426
+ #
427
+ # class Person < ActiveResource::Base
428
+ # self.schema = {'name' => :string, 'age' => :integer }
429
+ # end
430
+ #
431
+ # The keys/values can be strings or symbols. They will be converted to
432
+ # strings.
433
+ #
434
+ def schema=(the_schema)
435
+ unless the_schema.present?
436
+ # purposefully nulling out the schema
437
+ @schema = nil
438
+ @known_attributes = []
439
+ return
440
+ end
441
+
442
+ raise ArgumentError, "Expected a hash" unless the_schema.kind_of? Hash
443
+
444
+ schema do
445
+ the_schema.each { |k, v| attribute(k, v) }
446
+ end
447
+ end
448
+
449
+ # Returns the list of known attributes for this resource, gathered
450
+ # from the provided <tt>schema</tt>
451
+ # Attributes that are known will cause your resource to return 'true'
452
+ # when <tt>respond_to?</tt> is called on them. A known attribute will
453
+ # return nil if not set (rather than <tt>MethodNotFound</tt>); thus
454
+ # known attributes can be used with <tt>validates_presence_of</tt>
455
+ # without a getter-method.
456
+ def known_attributes
457
+ @known_attributes ||= []
458
+ end
459
+
460
+ # Gets the URI of the REST resources to map for this class. The site variable is required for
461
+ # Active Resource's mapping to work.
462
+ def site
463
+ # Not using superclass_delegating_reader because don't want subclasses to modify superclass instance
464
+ #
465
+ # With superclass_delegating_reader
466
+ #
467
+ # Parent.site = 'https://anonymous@test.com'
468
+ # Subclass.site # => 'https://anonymous@test.com'
469
+ # Subclass.site.user = 'david'
470
+ # Parent.site # => 'https://david@test.com'
471
+ #
472
+ # Without superclass_delegating_reader (expected behavior)
473
+ #
474
+ # Parent.site = 'https://anonymous@test.com'
475
+ # Subclass.site # => 'https://anonymous@test.com'
476
+ # Subclass.site.user = 'david' # => TypeError: can't modify frozen object
477
+ #
478
+ if _site_defined?
479
+ _site
480
+ elsif superclass != Object && superclass.site
481
+ superclass.site.dup.freeze
482
+ end
483
+ end
484
+
485
+ # Sets the URI of the REST resources to map for this class to the value in the +site+ argument.
486
+ # The site variable is required for Active Resource's mapping to work.
487
+ def site=(site)
488
+ self._connection = nil
489
+ if site.nil?
490
+ self._site = nil
491
+ else
492
+ self._site = create_site_uri_from(site)
493
+ self._user = URI_PARSER.unescape(_site.user) if _site.user
494
+ self._password = URI_PARSER.unescape(_site.password) if _site.password
495
+ end
496
+ end
497
+
498
+ # Gets the \proxy variable if a proxy is required
499
+ def proxy
500
+ # Not using superclass_delegating_reader. See +site+ for explanation
501
+ if _proxy_defined?
502
+ _proxy
503
+ elsif superclass != Object && superclass.proxy
504
+ superclass.proxy.dup.freeze
505
+ end
506
+ end
507
+
508
+ # Sets the URI of the http proxy to the value in the +proxy+ argument.
509
+ def proxy=(proxy)
510
+ self._connection = nil
511
+ self._proxy = proxy.nil? ? nil : create_proxy_uri_from(proxy)
512
+ end
513
+
514
+ # Gets the \user for REST HTTP authentication.
515
+ def user
516
+ # Not using superclass_delegating_reader. See +site+ for explanation
517
+ if _user_defined?
518
+ _user
519
+ elsif superclass != Object && superclass.user
520
+ superclass.user.dup.freeze
521
+ end
522
+ end
523
+
524
+ # Sets the \user for REST HTTP authentication.
525
+ def user=(user)
526
+ self._connection = nil
527
+ self._user = user
528
+ end
529
+
530
+ # Gets the \password for REST HTTP authentication.
531
+ def password
532
+ # Not using superclass_delegating_reader. See +site+ for explanation
533
+ if _password_defined?
534
+ _password
535
+ elsif superclass != Object && superclass.password
536
+ superclass.password.dup.freeze
537
+ end
538
+ end
539
+
540
+ # Sets the \password for REST HTTP authentication.
541
+ def password=(password)
542
+ self._connection = nil
543
+ self._password = password
544
+ end
545
+
546
+ # Gets the \bearer_token for REST HTTP authentication.
547
+ def bearer_token
548
+ # Not using superclass_delegating_reader. See +site+ for explanation
549
+ if _bearer_token_defined?
550
+ _bearer_token
551
+ elsif superclass != Object && superclass.bearer_token
552
+ superclass.bearer_token.dup.freeze
553
+ end
554
+ end
555
+
556
+ # Sets the \bearer_token for REST HTTP authentication.
557
+ def bearer_token=(bearer_token)
558
+ self._connection = nil
559
+ self._bearer_token = bearer_token
560
+ end
561
+
562
+ def auth_type
563
+ if defined?(@auth_type)
564
+ @auth_type
565
+ end
566
+ end
567
+
568
+ def auth_type=(auth_type)
569
+ self._connection = nil
570
+ @auth_type = auth_type
571
+ end
572
+
573
+ # Sets the format that attributes are sent and received in from a mime type reference:
574
+ #
575
+ # Person.format = :json
576
+ # Person.find(1) # => GET /people/1.json
577
+ #
578
+ # Person.format = ActiveResource::Formats::XmlFormat
579
+ # Person.find(1) # => GET /people/1.xml
580
+ #
581
+ # Default format is <tt>:json</tt>.
582
+ def format=(mime_type_reference_or_format)
583
+ format = mime_type_reference_or_format.is_a?(Symbol) ?
584
+ ActiveResource::Formats[mime_type_reference_or_format] : mime_type_reference_or_format
585
+
586
+ self._format = format
587
+ connection.format = format if site
588
+ end
589
+
590
+ # Returns the current format, default is ActiveResource::Formats::JsonFormat.
591
+ def format
592
+ self._format || ActiveResource::Formats::JsonFormat
593
+ end
594
+
595
+ # Sets the parser to use when a collection is returned. The parser must be Enumerable.
596
+ def collection_parser=(parser_instance)
597
+ parser_instance = parser_instance.constantize if parser_instance.is_a?(String)
598
+ self._collection_parser = parser_instance
599
+ end
600
+
601
+ def collection_parser
602
+ self._collection_parser || ActiveResource::Collection
603
+ end
604
+
605
+ # Sets the number of seconds after which requests to the REST API should time out.
606
+ def timeout=(timeout)
607
+ self._connection = nil
608
+ @timeout = timeout
609
+ end
610
+
611
+ # Sets the number of seconds after which connection attempts to the REST API should time out.
612
+ def open_timeout=(timeout)
613
+ self._connection = nil
614
+ @open_timeout = timeout
615
+ end
616
+
617
+ # Sets the number of seconds after which reads to the REST API should time out.
618
+ def read_timeout=(timeout)
619
+ self._connection = nil
620
+ @read_timeout = timeout
621
+ end
622
+
623
+ # Gets the number of seconds after which requests to the REST API should time out.
624
+ def timeout
625
+ if defined?(@timeout)
626
+ @timeout
627
+ elsif superclass != Object && superclass.timeout
628
+ superclass.timeout
629
+ end
630
+ end
631
+
632
+ # Gets the number of seconds after which connection attempts to the REST API should time out.
633
+ def open_timeout
634
+ if defined?(@open_timeout)
635
+ @open_timeout
636
+ elsif superclass != Object && superclass.open_timeout
637
+ superclass.open_timeout
638
+ end
639
+ end
640
+
641
+ # Gets the number of seconds after which reads to the REST API should time out.
642
+ def read_timeout
643
+ if defined?(@read_timeout)
644
+ @read_timeout
645
+ elsif superclass != Object && superclass.read_timeout
646
+ superclass.read_timeout
647
+ end
648
+ end
649
+
650
+ # Options that will get applied to an SSL connection.
651
+ #
652
+ # * <tt>:key</tt> - An OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
653
+ # * <tt>:cert</tt> - An OpenSSL::X509::Certificate object as client certificate
654
+ # * <tt>:ca_file</tt> - Path to a CA certification file in PEM format. The file can contain several CA certificates.
655
+ # * <tt>:ca_path</tt> - Path of a CA certification directory containing certifications in PEM format.
656
+ # * <tt>:verify_mode</tt> - Flags for server the certification verification at beginning of SSL/TLS session. (OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable)
657
+ # * <tt>:verify_callback</tt> - The verify callback for the server certification verification.
658
+ # * <tt>:verify_depth</tt> - The maximum depth for the certificate chain verification.
659
+ # * <tt>:cert_store</tt> - OpenSSL::X509::Store to verify peer certificate.
660
+ # * <tt>:ssl_timeout</tt> -The SSL timeout in seconds.
661
+ def ssl_options=(options)
662
+ self._connection = nil
663
+ @ssl_options = options
664
+ end
665
+
666
+ # Returns the SSL options hash.
667
+ def ssl_options
668
+ if defined?(@ssl_options)
669
+ @ssl_options
670
+ elsif superclass != Object && superclass.ssl_options
671
+ superclass.ssl_options
672
+ end
673
+ end
674
+
675
+ # An instance of ActiveResource::Connection that is the base \connection to the remote service.
676
+ # The +refresh+ parameter toggles whether or not the \connection is refreshed at every request
677
+ # or not (defaults to <tt>false</tt>).
678
+ def connection(refresh = false)
679
+ if _connection_defined? || superclass == Object
680
+ self._connection = connection_class.new(
681
+ site, format, logger: logger
682
+ ) if refresh || _connection.nil?
683
+ _connection.proxy = proxy if proxy
684
+ _connection.user = user if user
685
+ _connection.password = password if password
686
+ _connection.bearer_token = bearer_token if bearer_token
687
+ _connection.auth_type = auth_type if auth_type
688
+ _connection.timeout = timeout if timeout
689
+ _connection.open_timeout = open_timeout if open_timeout
690
+ _connection.read_timeout = read_timeout if read_timeout
691
+ _connection.ssl_options = ssl_options if ssl_options
692
+ _connection
693
+ else
694
+ superclass.connection
695
+ end
696
+ end
697
+
698
+ def headers
699
+ self._headers ||= if superclass != Object
700
+ InheritingHash.new(superclass.headers)
701
+ else
702
+ {}
703
+ end
704
+ end
705
+
706
+ attr_writer :element_name
707
+
708
+ def element_name
709
+ @element_name ||= model_name.element
710
+ end
711
+
712
+ attr_writer :collection_name
713
+
714
+ def collection_name
715
+ @collection_name ||= ActiveSupport::Inflector.pluralize(element_name)
716
+ end
717
+
718
+ attr_writer :primary_key
719
+
720
+ def primary_key
721
+ if defined?(@primary_key)
722
+ @primary_key
723
+ elsif superclass != Object && superclass.primary_key
724
+ primary_key = superclass.primary_key
725
+ return primary_key if primary_key.is_a?(Symbol)
726
+ primary_key.dup.freeze
727
+ else
728
+ "id"
729
+ end
730
+ end
731
+
732
+ # Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>)
733
+ # This method is regenerated at runtime based on what the \prefix is set to.
734
+ def prefix(options = {})
735
+ default = site.path
736
+ default << "/" unless default[-1..-1] == "/"
737
+ # generate the actual method based on the current site path
738
+ self.prefix = default
739
+ prefix(options)
740
+ end
741
+
742
+ # An attribute reader for the source string for the resource path \prefix. This
743
+ # method is regenerated at runtime based on what the \prefix is set to.
744
+ def prefix_source
745
+ prefix # generate #prefix and #prefix_source methods first
746
+ prefix_source
747
+ end
748
+
749
+ # Sets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>).
750
+ # Default value is <tt>site.path</tt>.
751
+ def prefix=(value = "/")
752
+ # Replace :placeholders with '#{embedded options[:lookups]}'
753
+ prefix_call = value.gsub(/:\w+/) { |key| "\#{URI_PARSER.escape options[#{key}].to_s}" }
754
+
755
+ # Clear prefix parameters in case they have been cached
756
+ @prefix_parameters = nil
757
+
758
+ silence_warnings do
759
+ # Redefine the new methods.
760
+ instance_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
761
+ def prefix_source() "#{value}" end
762
+ def prefix(options={}) "#{prefix_call}" end
763
+ RUBY_EVAL
764
+ end
765
+ rescue Exception => e
766
+ logger.error "Couldn't set prefix: #{e}\n #{code}" if logger
767
+ raise
768
+ end
769
+
770
+ alias_method :set_prefix, :prefix= # :nodoc:
771
+
772
+ alias_method :set_element_name, :element_name= # :nodoc:
773
+ alias_method :set_collection_name, :collection_name= # :nodoc:
774
+
775
+ def format_extension
776
+ include_format_in_path ? ".#{format.extension}" : ""
777
+ end
778
+
779
+ # Instantiates a new record with the given options.
780
+ #
781
+ # This method creates a new instance of the class with the provided record and sets its `prefix_options` attribute.
782
+ #
783
+ # ==== Options
784
+ #
785
+ # +record+ [Object] The record to be instantiated.
786
+ # +prefix_options+ [Hash, nil] Optional hash containing prefix options for the resource. Defaults to an empty hash.
787
+ #
788
+ # ==== Returns
789
+ #
790
+ # [Object] The newly instantiated resource.
791
+ #
792
+ # ==== Examples
793
+ #
794
+ # MyResource.instantiate_record(record)
795
+ # # Creates a new MyResource instance with default prefix options.
796
+ #
797
+ # MyResource.instantiate_record(record, { prefix: "admin" })
798
+ # # Creates a new MyResource instance with prefix set to "admin".
799
+ #
800
+ def instantiate_record(record, prefix_options = {})
801
+ new(record, true).tap do |resource|
802
+ resource.prefix_options = prefix_options
803
+ end
804
+ end
805
+
806
+ # Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
807
+ # will split from the \prefix options.
808
+ #
809
+ # ==== Options
810
+ # +prefix_options+ - A \hash to add a \prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
811
+ # would yield a URL like <tt>/accounts/19/purchases.json</tt>).
812
+ #
813
+ # +query_options+ - A \hash to add items to the query string for the request.
814
+ #
815
+ # ==== Examples
816
+ # Post.element_path(1)
817
+ # # => /posts/1.json
818
+ #
819
+ # class Comment < ActiveResource::Base
820
+ # self.site = "https://37s.sunrise.com/posts/:post_id"
821
+ # end
822
+ #
823
+ # Comment.element_path(1, :post_id => 5)
824
+ # # => /posts/5/comments/1.json
825
+ #
826
+ # Comment.element_path(1, :post_id => 5, :active => 1)
827
+ # # => /posts/5/comments/1.json?active=1
828
+ #
829
+ # Comment.element_path(1, {:post_id => 5}, {:active => 1})
830
+ # # => /posts/5/comments/1.json?active=1
831
+ #
832
+ def element_path(id, prefix_options = {}, query_options = nil)
833
+ check_prefix_options(prefix_options)
834
+
835
+ prefix_options, query_options = split_options(prefix_options) if query_options.nil?
836
+ "#{prefix(prefix_options)}#{collection_name}/#{URI.encode_www_form_component(id.to_s)}#{format_extension}#{query_string(query_options)}"
837
+ end
838
+
839
+ # Gets the element url for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
840
+ # will split from the \prefix options.
841
+ #
842
+ # ==== Options
843
+ # +prefix_options+ - A \hash to add a \prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
844
+ # would yield a URL like <tt>https://37s.sunrise.com/accounts/19/purchases.json</tt>).
845
+ #
846
+ # +query_options+ - A \hash to add items to the query string for the request.
847
+ #
848
+ # ==== Examples
849
+ # Post.element_url(1)
850
+ # # => https://37s.sunrise.com/posts/1.json
851
+ #
852
+ # class Comment < ActiveResource::Base
853
+ # self.site = "https://37s.sunrise.com/posts/:post_id"
854
+ # end
855
+ #
856
+ # Comment.element_url(1, :post_id => 5)
857
+ # # => https://37s.sunrise.com/posts/5/comments/1.json
858
+ #
859
+ # Comment.element_url(1, :post_id => 5, :active => 1)
860
+ # # => https://37s.sunrise.com/posts/5/comments/1.json?active=1
861
+ #
862
+ # Comment.element_url(1, {:post_id => 5}, {:active => 1})
863
+ # # => https://37s.sunrise.com/posts/5/comments/1.json?active=1
864
+ #
865
+ def element_url(id, prefix_options = {}, query_options = nil)
866
+ URI.join(site, element_path(id, prefix_options, query_options)).to_s
867
+ end
868
+
869
+ # Gets the new element path for REST resources.
870
+ #
871
+ # ==== Options
872
+ # * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
873
+ # would yield a URL like <tt>/accounts/19/purchases/new.json</tt>).
874
+ #
875
+ # ==== Examples
876
+ # Post.new_element_path
877
+ # # => /posts/new.json
878
+ #
879
+ # class Comment < ActiveResource::Base
880
+ # self.site = "https://37s.sunrise.com/posts/:post_id"
881
+ # end
882
+ #
883
+ # Comment.collection_path(:post_id => 5)
884
+ # # => /posts/5/comments/new.json
885
+ def new_element_path(prefix_options = {})
886
+ "#{prefix(prefix_options)}#{collection_name}/new#{format_extension}"
887
+ end
888
+
889
+ # Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails
890
+ # will split from the +prefix_options+.
891
+ #
892
+ # ==== Options
893
+ # * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
894
+ # would yield a URL like <tt>/accounts/19/purchases.json</tt>).
895
+ # * +query_options+ - A hash to add items to the query string for the request.
896
+ #
897
+ # ==== Examples
898
+ # Post.collection_path
899
+ # # => /posts.json
900
+ #
901
+ # Comment.collection_path(:post_id => 5)
902
+ # # => /posts/5/comments.json
903
+ #
904
+ # Comment.collection_path(:post_id => 5, :active => 1)
905
+ # # => /posts/5/comments.json?active=1
906
+ #
907
+ # Comment.collection_path({:post_id => 5}, {:active => 1})
908
+ # # => /posts/5/comments.json?active=1
909
+ #
910
+ def collection_path(prefix_options = {}, query_options = nil)
911
+ check_prefix_options(prefix_options)
912
+ prefix_options, query_options = split_options(prefix_options) if query_options.nil?
913
+ "#{prefix(prefix_options)}#{collection_name}#{format_extension}#{query_string(query_options)}"
914
+ end
915
+
916
+ alias_method :set_primary_key, :primary_key= # :nodoc:
917
+
918
+ # Builds a new, unsaved record using the default values from the remote server so
919
+ # that it can be used with RESTful forms.
920
+ #
921
+ # ==== Options
922
+ # * +attributes+ - A hash that overrides the default values from the server.
923
+ #
924
+ # Returns the new resource instance.
925
+ #
926
+ def build(attributes = {})
927
+ attrs = self.format.decode(connection.get("#{new_element_path(attributes)}", headers).body).merge(attributes)
928
+ self.new(attrs)
929
+ end
930
+
931
+ # Creates a new resource instance and makes a request to the remote service
932
+ # that it be saved, making it equivalent to the following simultaneous calls:
933
+ #
934
+ # ryan = Person.new(:first => 'ryan')
935
+ # ryan.save
936
+ #
937
+ # Returns the newly created resource. If a failure has occurred an
938
+ # exception will be raised (see <tt>save</tt>). If the resource is invalid and
939
+ # has not been saved then <tt>valid?</tt> will return <tt>false</tt>,
940
+ # while <tt>new?</tt> will still return <tt>true</tt>.
941
+ #
942
+ # ==== Examples
943
+ # Person.create(:name => 'Jeremy', :email => 'myname@nospam.com', :enabled => true)
944
+ # my_person = Person.find(:first)
945
+ # my_person.email # => myname@nospam.com
946
+ #
947
+ # dhh = Person.create(:name => 'David', :email => 'dhh@nospam.com', :enabled => true)
948
+ # dhh.valid? # => true
949
+ # dhh.new? # => false
950
+ #
951
+ # # We'll assume that there's a validation that requires the name attribute
952
+ # that_guy = Person.create(:name => '', :email => 'thatguy@nospam.com', :enabled => true)
953
+ # that_guy.valid? # => false
954
+ # that_guy.new? # => true
955
+ def create(attributes = {})
956
+ self.new(attributes).tap { |resource| resource.save }
957
+ end
958
+
959
+ # Creates a new resource (just like <tt>create</tt>) and makes a request to the
960
+ # remote service that it be saved, but runs validations and raises
961
+ # <tt>ActiveResource::ResourceInvalid</tt>, making it equivalent to the following
962
+ # simultaneous calls:
963
+ #
964
+ # ryan = Person.new(:first => 'ryan')
965
+ # ryan.save!
966
+ def create!(attributes = {})
967
+ self.new(attributes).tap { |resource| resource.save! }
968
+ end
969
+
970
+ # Core method for finding resources. Used similarly to Active Record's +find+ method.
971
+ #
972
+ # ==== Arguments
973
+ # The first argument is considered to be the scope of the query. That is, how many
974
+ # resources are returned from the request. It can be one of the following.
975
+ #
976
+ # * <tt>:one</tt> - Returns a single resource.
977
+ # * <tt>:first</tt> - Returns the first resource found.
978
+ # * <tt>:last</tt> - Returns the last resource found.
979
+ # * <tt>:all</tt> - Returns every resource that matches the request.
980
+ #
981
+ # ==== Options
982
+ #
983
+ # * <tt>:from</tt> - Sets the path or custom method that resources will be fetched from.
984
+ # * <tt>:params</tt> - Sets query and \prefix (nested URL) parameters.
985
+ #
986
+ # ==== Examples
987
+ # Person.find(1)
988
+ # # => GET /people/1.json
989
+ #
990
+ # Person.find(:all)
991
+ # # => GET /people.json
992
+ #
993
+ # Person.find(:all, :params => { :title => "CEO" })
994
+ # # => GET /people.json?title=CEO
995
+ #
996
+ # Person.find(:first, :from => :managers)
997
+ # # => GET /people/managers.json
998
+ #
999
+ # Person.find(:last, :from => :managers)
1000
+ # # => GET /people/managers.json
1001
+ #
1002
+ # Person.find(:all, :from => "/companies/1/people.json")
1003
+ # # => GET /companies/1/people.json
1004
+ #
1005
+ # Person.find(:one, :from => :leader)
1006
+ # # => GET /people/leader.json
1007
+ #
1008
+ # Person.find(:all, :from => :developers, :params => { :language => 'ruby' })
1009
+ # # => GET /people/developers.json?language=ruby
1010
+ #
1011
+ # Person.find(:one, :from => "/companies/1/manager.json")
1012
+ # # => GET /companies/1/manager.json
1013
+ #
1014
+ # StreetAddress.find(1, :params => { :person_id => 1 })
1015
+ # # => GET /people/1/street_addresses/1.json
1016
+ #
1017
+ # == Failure or missing data
1018
+ # A failure to find the requested object raises a ResourceNotFound
1019
+ # exception if the find was called with an id.
1020
+ # With any other scope, find returns nil when no data is returned.
1021
+ #
1022
+ # Person.find(1)
1023
+ # # => raises ResourceNotFound
1024
+ #
1025
+ # Person.find(:all)
1026
+ # Person.find(:first)
1027
+ # Person.find(:last)
1028
+ # # => nil
1029
+ def find(*arguments)
1030
+ scope = arguments.slice!(0)
1031
+ options = arguments.slice!(0) || {}
1032
+
1033
+ case scope
1034
+ when :all
1035
+ find_every(options)
1036
+ when :first
1037
+ collection = find_every(options)
1038
+ collection && collection.first
1039
+ when :last
1040
+ collection = find_every(options)
1041
+ collection && collection.last
1042
+ when :one
1043
+ find_one(options)
1044
+ else
1045
+ find_single(scope, options)
1046
+ end
1047
+ end
1048
+
1049
+
1050
+ # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass
1051
+ # in all the same arguments to this method as you can to
1052
+ # <tt>find(:first)</tt>.
1053
+ def first(*args)
1054
+ find(:first, *args)
1055
+ end
1056
+
1057
+ # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass
1058
+ # in all the same arguments to this method as you can to
1059
+ # <tt>find(:last)</tt>.
1060
+ def last(*args)
1061
+ find(:last, *args)
1062
+ end
1063
+
1064
+ # This is an alias for find(:all). You can pass in all the same
1065
+ # arguments to this method as you can to <tt>find(:all)</tt>
1066
+ def all(*args)
1067
+ find(:all, *args)
1068
+ end
1069
+
1070
+ def where(clauses = {})
1071
+ raise ArgumentError, "expected a clauses Hash, got #{clauses.inspect}" unless clauses.is_a? Hash
1072
+ find(:all, params: clauses)
1073
+ end
1074
+
1075
+
1076
+ # Deletes the resources with the ID in the +id+ parameter.
1077
+ #
1078
+ # ==== Options
1079
+ # All options specify \prefix and query parameters.
1080
+ #
1081
+ # ==== Examples
1082
+ # Event.delete(2) # sends DELETE /events/2
1083
+ #
1084
+ # Event.create(:name => 'Free Concert', :location => 'Community Center')
1085
+ # my_event = Event.find(:first) # let's assume this is event with ID 7
1086
+ # Event.delete(my_event.id) # sends DELETE /events/7
1087
+ #
1088
+ # # Let's assume a request to events/5/cancel.json
1089
+ # Event.delete(params[:id]) # sends DELETE /events/5
1090
+ def delete(id, options = {})
1091
+ connection.delete(element_path(id, options), headers)
1092
+ end
1093
+
1094
+ # Asserts the existence of a resource, returning <tt>true</tt> if the resource is found.
1095
+ #
1096
+ # ==== Examples
1097
+ # Note.create(:title => 'Hello, world.', :body => 'Nothing more for now...')
1098
+ # Note.exists?(1) # => true
1099
+ #
1100
+ # Note.exists(1349) # => false
1101
+ def exists?(id, options = {})
1102
+ return false unless id
1103
+
1104
+ prefix_options, query_options = split_options(options[:params])
1105
+ path = element_path(id, prefix_options, query_options)
1106
+ response = connection.head(path, headers)
1107
+
1108
+ (200..206).include?(response.code.to_i)
1109
+ rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone
1110
+ false
1111
+ end
1112
+
1113
+ private
1114
+ def check_prefix_options(prefix_options)
1115
+ p_options = HashWithIndifferentAccess.new(prefix_options)
1116
+ prefix_parameters.each do |p|
1117
+ raise(MissingPrefixParam, "#{p} prefix_option is missing") if p_options[p].blank?
1118
+ end
1119
+ end
1120
+
1121
+ # Find every resource
1122
+ def find_every(options)
1123
+ params = options[:params]
1124
+ prefix_options, query_options = split_options(params)
1125
+
1126
+ collection_parser.new([], options[:from]).tap do |parser|
1127
+ parser.resource_class = self
1128
+ parser.query_params = query_options
1129
+ parser.prefix_options = prefix_options
1130
+ parser.path_params = params
1131
+ end
1132
+ end
1133
+
1134
+ # Find a single resource from a one-off URL
1135
+ def find_one(options)
1136
+ case from = options[:from]
1137
+ when Symbol
1138
+ instantiate_record(get(from, options[:params]))
1139
+ when String
1140
+ path = "#{from}#{query_string(options[:params])}"
1141
+ instantiate_record(format.decode(connection.get(path, headers).body))
1142
+ end
1143
+ end
1144
+
1145
+ # Find a single resource from the default URL
1146
+ def find_single(scope, options)
1147
+ prefix_options, query_options = split_options(options[:params])
1148
+ path = element_path(scope, prefix_options, query_options)
1149
+ instantiate_record(format.decode(connection.get(path, headers).body), prefix_options)
1150
+ end
1151
+
1152
+ def instantiate_collection(collection, original_params = {}, prefix_options = {})
1153
+ collection_parser.new(collection).tap do |parser|
1154
+ parser.resource_class = self
1155
+ parser.original_params = original_params
1156
+ end.collect! { |record| instantiate_record(record, prefix_options) }
1157
+ end
1158
+
1159
+ # Accepts a URI and creates the site URI from that.
1160
+ def create_site_uri_from(site)
1161
+ site.is_a?(URI) ? site.dup : URI.parse(site)
1162
+ end
1163
+
1164
+ # Accepts a URI and creates the proxy URI from that.
1165
+ def create_proxy_uri_from(proxy)
1166
+ proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy)
1167
+ end
1168
+
1169
+ # contains a set of the current prefix parameters.
1170
+ def prefix_parameters
1171
+ @prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set
1172
+ end
1173
+
1174
+ # Builds the query string for the request.
1175
+ def query_string(options)
1176
+ "?#{options.to_query}" unless options.nil? || options.empty?
1177
+ end
1178
+
1179
+ # split an option hash into two hashes, one containing the prefix options,
1180
+ # and the other containing the leftovers.
1181
+ def split_options(options = {})
1182
+ prefix_options, query_options = {}, {}
1183
+
1184
+ (options || {}).each do |key, value|
1185
+ next if key.blank?
1186
+ (prefix_parameters.include?(key.to_s.to_sym) ? prefix_options : query_options)[key.to_s.to_sym] = value
1187
+ end
1188
+
1189
+ [ prefix_options, query_options ]
1190
+ end
1191
+ end
1192
+
1193
+ attr_accessor :attributes # :nodoc:
1194
+ attr_accessor :prefix_options # :nodoc:
1195
+
1196
+ # If no schema has been defined for the class (see
1197
+ # <tt>ActiveResource::schema=</tt>), the default automatic schema is
1198
+ # generated from the current instance's attributes
1199
+ def schema
1200
+ self.class.schema || self.attributes
1201
+ end
1202
+
1203
+ # This is a list of known attributes for this resource. Either
1204
+ # gathered from the provided <tt>schema</tt>, or from the attributes
1205
+ # set on this instance after it has been fetched from the remote system.
1206
+ def known_attributes
1207
+ (self.class.known_attributes + self.attributes.keys.map(&:to_s)).uniq
1208
+ end
1209
+
1210
+
1211
+ # Constructor method for \new resources; the optional +attributes+ parameter takes a \hash
1212
+ # of attributes for the \new resource.
1213
+ #
1214
+ # ==== Examples
1215
+ # my_course = Course.new
1216
+ # my_course.name = "Western Civilization"
1217
+ # my_course.lecturer = "Don Trotter"
1218
+ # my_course.save
1219
+ #
1220
+ # my_other_course = Course.new(:name => "Philosophy: Reason and Being", :lecturer => "Ralph Cling")
1221
+ # my_other_course.save
1222
+ def initialize(attributes = {}, persisted = false)
1223
+ @attributes = {}.with_indifferent_access
1224
+ @prefix_options = {}
1225
+ @persisted = persisted
1226
+ load(attributes, false, persisted)
1227
+ end
1228
+
1229
+ # Returns a \clone of the resource that hasn't been assigned an +id+ yet and
1230
+ # is treated as a \new resource.
1231
+ #
1232
+ # ryan = Person.find(1)
1233
+ # not_ryan = ryan.clone
1234
+ # not_ryan.new? # => true
1235
+ #
1236
+ # Any active resource member attributes will NOT be cloned, though all other
1237
+ # attributes are. This is to prevent the conflict between any +prefix_options+
1238
+ # that refer to the original parent resource and the newly cloned parent
1239
+ # resource that does not exist.
1240
+ #
1241
+ # ryan = Person.find(1)
1242
+ # ryan.address = StreetAddress.find(1, :person_id => ryan.id)
1243
+ # ryan.hash = {:not => "an ARes instance"}
1244
+ #
1245
+ # not_ryan = ryan.clone
1246
+ # not_ryan.new? # => true
1247
+ # not_ryan.address # => NoMethodError
1248
+ # not_ryan.hash # => {:not => "an ARes instance"}
1249
+ def clone
1250
+ # Clone all attributes except the pk and any nested ARes
1251
+ cloned = attributes.reject { |k, v| k == self.class.primary_key || v.is_a?(ActiveResource::Base) }.transform_values { |v| v.clone }
1252
+ # Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which
1253
+ # attempts to convert hashes into member objects and arrays into collections of objects. We want
1254
+ # the raw objects to be cloned so we bypass load by directly setting the attributes hash.
1255
+ resource = self.class.new({})
1256
+ resource.prefix_options = self.prefix_options
1257
+ resource.send :instance_variable_set, "@attributes", cloned
1258
+ resource
1259
+ end
1260
+
1261
+
1262
+ # Returns +true+ if this object hasn't yet been saved, otherwise, returns +false+.
1263
+ #
1264
+ # ==== Examples
1265
+ # not_new = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall')
1266
+ # not_new.new? # => false
1267
+ #
1268
+ # is_new = Computer.new(:brand => 'IBM', :make => 'Thinkpad', :vendor => 'IBM')
1269
+ # is_new.new? # => true
1270
+ #
1271
+ # is_new.save
1272
+ # is_new.new? # => false
1273
+ #
1274
+ def new?
1275
+ !persisted?
1276
+ end
1277
+ alias :new_record? :new?
1278
+
1279
+ # Returns +true+ if this object has been saved, otherwise returns +false+.
1280
+ #
1281
+ # ==== Examples
1282
+ # persisted = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall')
1283
+ # persisted.persisted? # => true
1284
+ #
1285
+ # not_persisted = Computer.new(:brand => 'IBM', :make => 'Thinkpad', :vendor => 'IBM')
1286
+ # not_persisted.persisted? # => false
1287
+ #
1288
+ # not_persisted.save
1289
+ # not_persisted.persisted? # => true
1290
+ #
1291
+ def persisted?
1292
+ @persisted
1293
+ end
1294
+
1295
+ # Gets the <tt>\id</tt> attribute of the resource.
1296
+ def id
1297
+ attributes[self.class.primary_key]
1298
+ end
1299
+
1300
+ # Sets the <tt>\id</tt> attribute of the resource.
1301
+ def id=(id)
1302
+ attributes[self.class.primary_key] = id
1303
+ end
1304
+
1305
+ # Test for equality. Resource are equal if and only if +other+ is the same object or
1306
+ # is an instance of the same class, is not <tt>new?</tt>, and has the same +id+.
1307
+ #
1308
+ # ==== Examples
1309
+ # ryan = Person.create(:name => 'Ryan')
1310
+ # jamie = Person.create(:name => 'Jamie')
1311
+ #
1312
+ # ryan == jamie
1313
+ # # => false (Different name attribute and id)
1314
+ #
1315
+ # ryan_again = Person.new(:name => 'Ryan')
1316
+ # ryan == ryan_again
1317
+ # # => false (ryan_again is new?)
1318
+ #
1319
+ # ryans_clone = Person.create(:name => 'Ryan')
1320
+ # ryan == ryans_clone
1321
+ # # => false (Different id attributes)
1322
+ #
1323
+ # ryans_twin = Person.find(ryan.id)
1324
+ # ryan == ryans_twin
1325
+ # # => true
1326
+ #
1327
+ def ==(other)
1328
+ other.equal?(self) || (other.instance_of?(self.class) && other.id == id && other.prefix_options == prefix_options)
1329
+ end
1330
+
1331
+ # Tests for equality (delegates to ==).
1332
+ def eql?(other)
1333
+ self == other
1334
+ end
1335
+
1336
+ # Delegates to id in order to allow two resources of the same type and \id to work with something like:
1337
+ # [(a = Person.find 1), (b = Person.find 2)] & [(c = Person.find 1), (d = Person.find 4)] # => [a]
1338
+ def hash
1339
+ id.hash
1340
+ end
1341
+
1342
+ # Duplicates the current resource without saving it.
1343
+ #
1344
+ # ==== Examples
1345
+ # my_invoice = Invoice.create(:customer => 'That Company')
1346
+ # next_invoice = my_invoice.dup
1347
+ # next_invoice.new? # => true
1348
+ #
1349
+ # next_invoice.save
1350
+ # next_invoice == my_invoice # => false (different id attributes)
1351
+ #
1352
+ # my_invoice.customer # => That Company
1353
+ # next_invoice.customer # => That Company
1354
+ def dup
1355
+ self.class.new.tap do |resource|
1356
+ resource.attributes = @attributes
1357
+ resource.prefix_options = @prefix_options
1358
+ end
1359
+ end
1360
+
1361
+ # Saves (+POST+) or \updates (+PUT+) a resource. Delegates to +create+ if the object is \new,
1362
+ # +update+ if it exists. If the response to the \save includes a body, it will be assumed that this body
1363
+ # is Json for the final object as it looked after the \save (which would include attributes like +created_at+
1364
+ # that weren't part of the original submit).
1365
+ #
1366
+ # ==== Examples
1367
+ # my_company = Company.new(:name => 'RoleModel Software', :owner => 'Ken Auer', :size => 2)
1368
+ # my_company.new? # => true
1369
+ # my_company.save # sends POST /companies/ (create)
1370
+ #
1371
+ # my_company.new? # => false
1372
+ # my_company.size = 10
1373
+ # my_company.save # sends PUT /companies/1 (update)
1374
+ def save
1375
+ run_callbacks :save do
1376
+ new? ? create : update
1377
+ end
1378
+ end
1379
+
1380
+ # Saves the resource.
1381
+ #
1382
+ # If the resource is new, it is created via +POST+, otherwise the
1383
+ # existing resource is updated via +PUT+.
1384
+ #
1385
+ # With <tt>save!</tt> validations always run. If any of them fail
1386
+ # ActiveResource::ResourceInvalid gets raised, and nothing is POSTed to
1387
+ # the remote system.
1388
+ # See ActiveResource::Validations for more information.
1389
+ #
1390
+ # There's a series of callbacks associated with <tt>save!</tt>. If any
1391
+ # of the <tt>before_*</tt> callbacks return +false+ the action is
1392
+ # cancelled and <tt>save!</tt> raises ActiveResource::ResourceInvalid.
1393
+ def save!
1394
+ save || raise(ResourceInvalid.new(self))
1395
+ end
1396
+
1397
+ # Deletes the resource from the remote service.
1398
+ #
1399
+ # ==== Examples
1400
+ # my_id = 3
1401
+ # my_person = Person.find(my_id)
1402
+ # my_person.destroy
1403
+ # Person.find(my_id) # 404 (Resource Not Found)
1404
+ #
1405
+ # new_person = Person.create(:name => 'James')
1406
+ # new_id = new_person.id # => 7
1407
+ # new_person.destroy
1408
+ # Person.find(new_id) # 404 (Resource Not Found)
1409
+ def destroy
1410
+ run_callbacks :destroy do
1411
+ connection.delete(element_path, self.class.headers)
1412
+ end
1413
+ end
1414
+
1415
+ # Evaluates to <tt>true</tt> if this resource is not <tt>new?</tt> and is
1416
+ # found on the remote service. Using this method, you can check for
1417
+ # resources that may have been deleted between the object's instantiation
1418
+ # and actions on it.
1419
+ #
1420
+ # ==== Examples
1421
+ # Person.create(:name => 'Theodore Roosevelt')
1422
+ # that_guy = Person.find(:first)
1423
+ # that_guy.exists? # => true
1424
+ #
1425
+ # that_lady = Person.new(:name => 'Paul Bean')
1426
+ # that_lady.exists? # => false
1427
+ #
1428
+ # guys_id = that_guy.id
1429
+ # Person.delete(guys_id)
1430
+ # that_guy.exists? # => false
1431
+ def exists?
1432
+ !new? && self.class.exists?(to_param, params: prefix_options)
1433
+ end
1434
+
1435
+ # Returns the serialized string representation of the resource in the configured
1436
+ # serialization format specified in ActiveResource::Base.format. The options
1437
+ # applicable depend on the configured encoding format.
1438
+ def encode(options = {})
1439
+ send("to_#{self.class.format.extension}", options)
1440
+ end
1441
+
1442
+ # A method to \reload the attributes of this object from the remote web service.
1443
+ #
1444
+ # ==== Examples
1445
+ # my_branch = Branch.find(:first)
1446
+ # my_branch.name # => "Wislon Raod"
1447
+ #
1448
+ # # Another client fixes the typo...
1449
+ #
1450
+ # my_branch.name # => "Wislon Raod"
1451
+ # my_branch.reload
1452
+ # my_branch.name # => "Wilson Road"
1453
+ def reload
1454
+ self.load(self.class.find(to_param, params: @prefix_options).attributes, false, true)
1455
+ end
1456
+
1457
+ # A method to manually load attributes from a \hash. Recursively loads collections of
1458
+ # resources. This method is called in +initialize+ and +create+ when a \hash of attributes
1459
+ # is provided.
1460
+ #
1461
+ # ==== Examples
1462
+ # my_attrs = {:name => 'J&J Textiles', :industry => 'Cloth and textiles'}
1463
+ # my_attrs = {:name => 'Marty', :colors => ["red", "green", "blue"]}
1464
+ #
1465
+ # the_supplier = Supplier.find(:first)
1466
+ # the_supplier.name # => 'J&M Textiles'
1467
+ # the_supplier.load(my_attrs)
1468
+ # the_supplier.name('J&J Textiles')
1469
+ #
1470
+ # # These two calls are the same as Supplier.new(my_attrs)
1471
+ # my_supplier = Supplier.new
1472
+ # my_supplier.load(my_attrs)
1473
+ #
1474
+ # # These three calls are the same as Supplier.create(my_attrs)
1475
+ # your_supplier = Supplier.new
1476
+ # your_supplier.load(my_attrs)
1477
+ # your_supplier.save
1478
+ def load(attributes, remove_root = false, persisted = false)
1479
+ unless attributes.respond_to?(:to_hash)
1480
+ raise ArgumentError, "expected attributes to be able to convert to Hash, got #{attributes.inspect}"
1481
+ end
1482
+
1483
+ attributes = attributes.to_hash
1484
+ @prefix_options, attributes = split_options(attributes)
1485
+
1486
+ if attributes.keys.size == 1
1487
+ remove_root = self.class.element_name == attributes.keys.first.to_s
1488
+ end
1489
+
1490
+ attributes = Formats.remove_root(attributes) if remove_root
1491
+
1492
+ attributes.each do |key, value|
1493
+ @attributes[key.to_s] =
1494
+ case value
1495
+ when Array
1496
+ resource = nil
1497
+ value.map do |attrs|
1498
+ if attrs.is_a?(Hash)
1499
+ resource ||= find_or_create_resource_for_collection(key)
1500
+ resource.new(attrs, persisted)
1501
+ else
1502
+ attrs.duplicable? ? attrs.dup : attrs
1503
+ end
1504
+ end
1505
+ when Hash
1506
+ resource = find_or_create_resource_for(key)
1507
+ resource.new(value, persisted)
1508
+ else
1509
+ value.duplicable? ? value.dup : value
1510
+ end
1511
+ end
1512
+ self
1513
+ end
1514
+
1515
+ # Updates a single attribute and then saves the object.
1516
+ #
1517
+ # Note: <tt>Unlike ActiveRecord::Base.update_attribute</tt>, this method <b>is</b>
1518
+ # subject to normal validation routines as an update sends the whole body
1519
+ # of the resource in the request. (See Validations).
1520
+ #
1521
+ # As such, this method is equivalent to calling update_attributes with a single attribute/value pair.
1522
+ #
1523
+ # If the saving fails because of a connection or remote service error, an
1524
+ # exception will be raised. If saving fails because the resource is
1525
+ # invalid then <tt>false</tt> will be returned.
1526
+ def update_attribute(name, value)
1527
+ self.send("#{name}=".to_sym, value)
1528
+ self.save
1529
+ end
1530
+
1531
+ # Updates this resource with all the attributes from the passed-in Hash
1532
+ # and requests that the record be saved.
1533
+ #
1534
+ # If the saving fails because of a connection or remote service error, an
1535
+ # exception will be raised. If saving fails because the resource is
1536
+ # invalid then <tt>false</tt> will be returned.
1537
+ #
1538
+ # Note: Though this request can be made with a partial set of the
1539
+ # resource's attributes, the full body of the request will still be sent
1540
+ # in the save request to the remote service.
1541
+ def update_attributes(attributes)
1542
+ load(attributes, false) && save
1543
+ end
1544
+
1545
+ # For checking <tt>respond_to?</tt> without searching the attributes (which is faster).
1546
+ alias_method :respond_to_without_attributes?, :respond_to?
1547
+
1548
+ # A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a Person object with a
1549
+ # +name+ attribute can answer <tt>true</tt> to <tt>my_person.respond_to?(:name)</tt>, <tt>my_person.respond_to?(:name=)</tt>, and
1550
+ # <tt>my_person.respond_to?(:name?)</tt>.
1551
+ def respond_to_missing?(method, include_priv = false)
1552
+ method_name = method.to_s
1553
+ if attributes.nil?
1554
+ super
1555
+ elsif known_attributes.include?(method_name)
1556
+ true
1557
+ elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`)
1558
+ true
1559
+ else
1560
+ # super must be called at the end of the method, because the inherited respond_to?
1561
+ # would return true for generated readers, even if the attribute wasn't present
1562
+ super
1563
+ end
1564
+ end
1565
+
1566
+ def to_json(options = {})
1567
+ super(include_root_in_json ? { root: self.class.element_name }.merge(options) : options)
1568
+ end
1569
+
1570
+ def to_xml(options = {})
1571
+ super({ root: self.class.element_name }.merge(options))
1572
+ end
1573
+
1574
+ def read_attribute_for_serialization(n)
1575
+ if !attributes[n].nil?
1576
+ attributes[n]
1577
+ elsif respond_to?(n)
1578
+ send(n)
1579
+ end
1580
+ end
1581
+
1582
+ protected
1583
+ def connection(refresh = false)
1584
+ self.class.connection(refresh)
1585
+ end
1586
+
1587
+ # Update the resource on the remote service.
1588
+ def update
1589
+ run_callbacks :update do
1590
+ connection.put(element_path(prefix_options), encode, self.class.headers).tap do |response|
1591
+ load_attributes_from_response(response)
1592
+ end
1593
+ end
1594
+ end
1595
+
1596
+ # Create (i.e., \save to the remote service) the \new resource.
1597
+ def create
1598
+ run_callbacks :create do
1599
+ connection.post(collection_path, encode, self.class.headers).tap do |response|
1600
+ self.id = id_from_response(response)
1601
+ load_attributes_from_response(response)
1602
+ end
1603
+ end
1604
+ end
1605
+
1606
+ def load_attributes_from_response(response)
1607
+ if response_code_allows_body?(response.code.to_i) &&
1608
+ (response["Content-Length"].nil? || response["Content-Length"] != "0") &&
1609
+ !response.body.nil? && response.body.strip.size > 0
1610
+ load(self.class.format.decode(response.body), true, true)
1611
+ @persisted = true
1612
+ end
1613
+ end
1614
+
1615
+ # Takes a response from a typical create post and pulls the ID out
1616
+ def id_from_response(response)
1617
+ response["Location"][/\/([^\/]*?)(\.\w+)?$/, 1] if response["Location"]
1618
+ end
1619
+
1620
+ def element_path(options = nil)
1621
+ self.class.element_path(to_param, options || prefix_options)
1622
+ end
1623
+
1624
+ def element_url(options = nil)
1625
+ self.class.element_url(to_param, options || prefix_options)
1626
+ end
1627
+
1628
+ def new_element_path
1629
+ self.class.new_element_path(prefix_options)
1630
+ end
1631
+
1632
+ def collection_path(options = nil)
1633
+ self.class.collection_path(options || prefix_options)
1634
+ end
1635
+
1636
+ private
1637
+ # Determine whether the response is allowed to have a body per HTTP 1.1 spec section 4.4.1
1638
+ def response_code_allows_body?(c)
1639
+ !((100..199).include?(c) || [204, 304].include?(c))
1640
+ end
1641
+
1642
+ # Tries to find a resource for a given collection name; if it fails, then the resource is created
1643
+ def find_or_create_resource_for_collection(name)
1644
+ return reflections[name.to_sym].klass if reflections.key?(name.to_sym)
1645
+ find_or_create_resource_for(ActiveSupport::Inflector.singularize(name.to_s))
1646
+ end
1647
+
1648
+ # Tries to find a resource in a non empty list of nested modules
1649
+ # if it fails, then the resource is created
1650
+ def find_or_create_resource_in_modules(resource_name, module_names)
1651
+ receiver = Object
1652
+ namespaces = module_names[0, module_names.size - 1].map do |module_name|
1653
+ receiver = receiver.const_get(module_name)
1654
+ end
1655
+ const_args = [resource_name, false]
1656
+ if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(*const_args) }
1657
+ namespace.const_get(*const_args)
1658
+ else
1659
+ create_resource_for(resource_name)
1660
+ end
1661
+ end
1662
+
1663
+ # Tries to find a resource for a given name; if it fails, then the resource is created
1664
+ def find_or_create_resource_for(name)
1665
+ return reflections[name.to_sym].klass if reflections.key?(name.to_sym)
1666
+ resource_name = name.to_s.camelize
1667
+
1668
+ const_args = [resource_name, false]
1669
+
1670
+ if !const_valid?(*const_args)
1671
+ # resource_name is not a valid ruby module name and cannot be created normally
1672
+ find_or_create_resource_for(:UnnamedResource)
1673
+ elsif self.class.const_defined?(*const_args)
1674
+ self.class.const_get(*const_args)
1675
+ else
1676
+ ancestors = self.class.name.to_s.split("::")
1677
+ if ancestors.size > 1
1678
+ find_or_create_resource_in_modules(resource_name, ancestors)
1679
+ else
1680
+ if Object.const_defined?(*const_args)
1681
+ Object.const_get(*const_args)
1682
+ else
1683
+ create_resource_for(resource_name)
1684
+ end
1685
+ end
1686
+ end
1687
+ end
1688
+
1689
+ def const_valid?(*const_args)
1690
+ self.class.const_defined?(*const_args)
1691
+ true
1692
+ rescue NameError
1693
+ false
1694
+ end
1695
+
1696
+ # Create and return a class definition for a resource inside the current resource
1697
+ def create_resource_for(resource_name)
1698
+ resource = Class.new(ActiveResource::Base)
1699
+ resource.prefix = self.class.prefix
1700
+ resource.site = self.class.site
1701
+ self.class.const_set(resource_name, resource)
1702
+
1703
+ resource
1704
+ end
1705
+
1706
+ def split_options(options = {})
1707
+ self.class.__send__(:split_options, options)
1708
+ end
1709
+
1710
+ def method_missing(method_symbol, *arguments) # :nodoc:
1711
+ method_name = method_symbol.to_s
1712
+
1713
+ if method_name =~ /(=|\?)$/
1714
+ case $1
1715
+ when "="
1716
+ attributes[$`] = arguments.first
1717
+ when "?"
1718
+ attributes[$`]
1719
+ end
1720
+ else
1721
+ return attributes[method_name] if attributes.include?(method_name)
1722
+ # not set right now but we know about it
1723
+ return nil if known_attributes.include?(method_name)
1724
+ super
1725
+ end
1726
+ end
1727
+ end
1728
+
1729
+ class Base
1730
+ extend ActiveModel::Naming
1731
+ extend ActiveResource::Associations
1732
+
1733
+ include Callbacks, CustomMethods, Validations
1734
+ include ActiveModel::Conversion
1735
+ include ActiveModel::Serializers::JSON
1736
+ include ActiveModel::Serializers::Xml
1737
+ include ActiveResource::Reflection
1738
+ end
1739
+
1740
+ ActiveSupport.run_load_hooks(:active_resource, Base)
1741
+ end