active_cached_resource 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
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