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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +22 -0
- data/.standard.yml +2 -0
- data/CHANGELOG.md +5 -0
- data/README.md +45 -0
- data/Rakefile +21 -0
- data/example/consumer/.dockerignore +41 -0
- data/example/consumer/.gitattributes +9 -0
- data/example/consumer/.gitignore +36 -0
- data/example/consumer/.kamal/hooks/docker-setup.sample +3 -0
- data/example/consumer/.kamal/hooks/post-deploy.sample +14 -0
- data/example/consumer/.kamal/hooks/post-proxy-reboot.sample +3 -0
- data/example/consumer/.kamal/hooks/pre-build.sample +51 -0
- data/example/consumer/.kamal/hooks/pre-connect.sample +47 -0
- data/example/consumer/.kamal/hooks/pre-deploy.sample +109 -0
- data/example/consumer/.kamal/hooks/pre-proxy-reboot.sample +3 -0
- data/example/consumer/.kamal/secrets +17 -0
- data/example/consumer/Dockerfile +65 -0
- data/example/consumer/Gemfile +17 -0
- data/example/consumer/Rakefile +6 -0
- data/example/consumer/app/controllers/application_controller.rb +2 -0
- data/example/consumer/app/controllers/concerns/.keep +0 -0
- data/example/consumer/app/jobs/application_job.rb +7 -0
- data/example/consumer/app/mailers/application_mailer.rb +4 -0
- data/example/consumer/app/models/application_record.rb +3 -0
- data/example/consumer/app/models/concerns/.keep +0 -0
- data/example/consumer/app/models/person.rb +9 -0
- data/example/consumer/app/views/layouts/mailer.html.erb +13 -0
- data/example/consumer/app/views/layouts/mailer.text.erb +1 -0
- data/example/consumer/bin/brakeman +7 -0
- data/example/consumer/bin/bundle +109 -0
- data/example/consumer/bin/dev +2 -0
- data/example/consumer/bin/docker-entrypoint +14 -0
- data/example/consumer/bin/jobs +6 -0
- data/example/consumer/bin/kamal +27 -0
- data/example/consumer/bin/rails +4 -0
- data/example/consumer/bin/rake +4 -0
- data/example/consumer/bin/rubocop +8 -0
- data/example/consumer/bin/setup +34 -0
- data/example/consumer/bin/thrust +5 -0
- data/example/consumer/config/application.rb +20 -0
- data/example/consumer/config/boot.rb +3 -0
- data/example/consumer/config/cache.yml +16 -0
- data/example/consumer/config/credentials.yml.enc +1 -0
- data/example/consumer/config/database.yml +14 -0
- data/example/consumer/config/deploy.yml +116 -0
- data/example/consumer/config/environment.rb +5 -0
- data/example/consumer/config/environments/development.rb +64 -0
- data/example/consumer/config/environments/production.rb +85 -0
- data/example/consumer/config/environments/test.rb +50 -0
- data/example/consumer/config/initializers/cors.rb +16 -0
- data/example/consumer/config/initializers/filter_parameter_logging.rb +8 -0
- data/example/consumer/config/initializers/inflections.rb +16 -0
- data/example/consumer/config/locales/en.yml +31 -0
- data/example/consumer/config/puma.rb +41 -0
- data/example/consumer/config/queue.yml +18 -0
- data/example/consumer/config/recurring.yml +10 -0
- data/example/consumer/config/routes.rb +10 -0
- data/example/consumer/config.ru +6 -0
- data/example/consumer/db/cache_schema.rb +14 -0
- data/example/consumer/db/queue_schema.rb +129 -0
- data/example/consumer/db/seeds.rb +0 -0
- data/example/consumer/lib/tasks/.keep +0 -0
- data/example/consumer/log/.keep +0 -0
- data/example/consumer/public/robots.txt +1 -0
- data/example/consumer/script/.keep +0 -0
- data/example/consumer/storage/.keep +0 -0
- data/example/consumer/tmp/.keep +0 -0
- data/example/consumer/tmp/cache/.keep +0 -0
- data/example/consumer/tmp/pids/.keep +0 -0
- data/example/consumer/tmp/storage/.keep +0 -0
- data/example/consumer/vendor/.keep +0 -0
- data/example/provider/.dockerignore +41 -0
- data/example/provider/.gitattributes +9 -0
- data/example/provider/.gitignore +32 -0
- data/example/provider/.kamal/hooks/docker-setup.sample +3 -0
- data/example/provider/.kamal/hooks/post-deploy.sample +14 -0
- data/example/provider/.kamal/hooks/post-proxy-reboot.sample +3 -0
- data/example/provider/.kamal/hooks/pre-build.sample +51 -0
- data/example/provider/.kamal/hooks/pre-connect.sample +47 -0
- data/example/provider/.kamal/hooks/pre-deploy.sample +109 -0
- data/example/provider/.kamal/hooks/pre-proxy-reboot.sample +3 -0
- data/example/provider/.kamal/secrets +17 -0
- data/example/provider/Dockerfile +65 -0
- data/example/provider/Gemfile +14 -0
- data/example/provider/Rakefile +6 -0
- data/example/provider/app/controllers/application_controller.rb +2 -0
- data/example/provider/app/controllers/concerns/.keep +0 -0
- data/example/provider/app/controllers/people_controller.rb +68 -0
- data/example/provider/app/jobs/application_job.rb +7 -0
- data/example/provider/app/mailers/application_mailer.rb +4 -0
- data/example/provider/app/models/address.rb +3 -0
- data/example/provider/app/models/application_record.rb +3 -0
- data/example/provider/app/models/company.rb +3 -0
- data/example/provider/app/models/concerns/.keep +0 -0
- data/example/provider/app/models/person.rb +6 -0
- data/example/provider/app/views/layouts/mailer.html.erb +13 -0
- data/example/provider/app/views/layouts/mailer.text.erb +1 -0
- data/example/provider/bin/brakeman +7 -0
- data/example/provider/bin/bundle +109 -0
- data/example/provider/bin/dev +2 -0
- data/example/provider/bin/docker-entrypoint +14 -0
- data/example/provider/bin/jobs +6 -0
- data/example/provider/bin/kamal +27 -0
- data/example/provider/bin/rails +4 -0
- data/example/provider/bin/rake +4 -0
- data/example/provider/bin/rubocop +8 -0
- data/example/provider/bin/setup +34 -0
- data/example/provider/bin/thrust +5 -0
- data/example/provider/config/application.rb +44 -0
- data/example/provider/config/boot.rb +3 -0
- data/example/provider/config/cache.yml +16 -0
- data/example/provider/config/credentials.yml.enc +1 -0
- data/example/provider/config/database.yml +20 -0
- data/example/provider/config/deploy.yml +116 -0
- data/example/provider/config/environment.rb +5 -0
- data/example/provider/config/environments/development.rb +64 -0
- data/example/provider/config/environments/production.rb +85 -0
- data/example/provider/config/environments/test.rb +50 -0
- data/example/provider/config/initializers/cors.rb +16 -0
- data/example/provider/config/initializers/filter_parameter_logging.rb +8 -0
- data/example/provider/config/initializers/inflections.rb +16 -0
- data/example/provider/config/locales/en.yml +31 -0
- data/example/provider/config/puma.rb +41 -0
- data/example/provider/config/queue.yml +18 -0
- data/example/provider/config/recurring.yml +10 -0
- data/example/provider/config/routes.rb +4 -0
- data/example/provider/config.ru +6 -0
- data/example/provider/db/cache_schema.rb +14 -0
- data/example/provider/db/migrate/20241202183937_create_people.rb +11 -0
- data/example/provider/db/migrate/20241202183955_create_addresses.rb +13 -0
- data/example/provider/db/migrate/20241202184017_create_companies.rb +14 -0
- data/example/provider/db/queue_schema.rb +129 -0
- data/example/provider/db/schema.rb +47 -0
- data/example/provider/db/seeds.rb +18 -0
- data/example/provider/lib/tasks/.keep +0 -0
- data/example/provider/log/.keep +0 -0
- data/example/provider/public/robots.txt +1 -0
- data/example/provider/script/.keep +0 -0
- data/example/provider/storage/.keep +0 -0
- data/example/provider/tmp/.keep +0 -0
- data/example/provider/tmp/pids/.keep +0 -0
- data/example/provider/tmp/storage/.keep +0 -0
- data/example/provider/vendor/.keep +0 -0
- data/lib/active_cached_resource/caching.rb +176 -0
- data/lib/active_cached_resource/caching_strategies/active_support_cache.rb +31 -0
- data/lib/active_cached_resource/caching_strategies/base.rb +114 -0
- data/lib/active_cached_resource/caching_strategies/sql_cache.rb +32 -0
- data/lib/active_cached_resource/configuration.rb +50 -0
- data/lib/active_cached_resource/logger.rb +22 -0
- data/lib/active_cached_resource/model.rb +33 -0
- data/lib/active_cached_resource/version.rb +12 -0
- data/lib/active_cached_resource.rb +64 -0
- data/lib/activeresource/.gitignore +15 -0
- data/lib/activeresource/README.md +283 -0
- data/lib/activeresource/examples/performance.rb +72 -0
- data/lib/activeresource/lib/active_resource/active_job_serializer.rb +26 -0
- data/lib/activeresource/lib/active_resource/associations/builder/association.rb +32 -0
- data/lib/activeresource/lib/active_resource/associations/builder/belongs_to.rb +16 -0
- data/lib/activeresource/lib/active_resource/associations/builder/has_many.rb +14 -0
- data/lib/activeresource/lib/active_resource/associations/builder/has_one.rb +14 -0
- data/lib/activeresource/lib/active_resource/associations.rb +175 -0
- data/lib/activeresource/lib/active_resource/base.rb +1741 -0
- data/lib/activeresource/lib/active_resource/callbacks.rb +22 -0
- data/lib/activeresource/lib/active_resource/collection.rb +214 -0
- data/lib/activeresource/lib/active_resource/connection.rb +298 -0
- data/lib/activeresource/lib/active_resource/custom_methods.rb +129 -0
- data/lib/activeresource/lib/active_resource/exceptions.rb +98 -0
- data/lib/activeresource/lib/active_resource/formats/json_format.rb +28 -0
- data/lib/activeresource/lib/active_resource/formats/xml_format.rb +27 -0
- data/lib/activeresource/lib/active_resource/formats.rb +24 -0
- data/lib/activeresource/lib/active_resource/http_mock.rb +386 -0
- data/lib/activeresource/lib/active_resource/inheriting_hash.rb +34 -0
- data/lib/activeresource/lib/active_resource/log_subscriber.rb +26 -0
- data/lib/activeresource/lib/active_resource/railtie.rb +31 -0
- data/lib/activeresource/lib/active_resource/reflection.rb +78 -0
- data/lib/activeresource/lib/active_resource/schema.rb +60 -0
- data/lib/activeresource/lib/active_resource/singleton.rb +111 -0
- data/lib/activeresource/lib/active_resource/threadsafe_attributes.rb +65 -0
- data/lib/activeresource/lib/active_resource/validations.rb +176 -0
- data/lib/activeresource/lib/active_resource.rb +49 -0
- data/lib/activeresource/lib/activeresource.rb +3 -0
- data/lib/activeresource/test/abstract_unit.rb +153 -0
- data/lib/activeresource/test/cases/active_job_serializer_test.rb +53 -0
- data/lib/activeresource/test/cases/association_test.rb +104 -0
- data/lib/activeresource/test/cases/associations/builder/belongs_to_test.rb +42 -0
- data/lib/activeresource/test/cases/associations/builder/has_many_test.rb +28 -0
- data/lib/activeresource/test/cases/associations/builder/has_one_test.rb +28 -0
- data/lib/activeresource/test/cases/authorization_test.rb +276 -0
- data/lib/activeresource/test/cases/base/custom_methods_test.rb +155 -0
- data/lib/activeresource/test/cases/base/equality_test.rb +53 -0
- data/lib/activeresource/test/cases/base/load_test.rb +249 -0
- data/lib/activeresource/test/cases/base/schema_test.rb +428 -0
- data/lib/activeresource/test/cases/base_errors_test.rb +129 -0
- data/lib/activeresource/test/cases/base_test.rb +1622 -0
- data/lib/activeresource/test/cases/callbacks_test.rb +155 -0
- data/lib/activeresource/test/cases/collection_test.rb +172 -0
- data/lib/activeresource/test/cases/connection_test.rb +357 -0
- data/lib/activeresource/test/cases/finder_test.rb +217 -0
- data/lib/activeresource/test/cases/format_test.rb +137 -0
- data/lib/activeresource/test/cases/http_mock_test.rb +213 -0
- data/lib/activeresource/test/cases/inheritence_test.rb +19 -0
- data/lib/activeresource/test/cases/inheriting_hash_test.rb +25 -0
- data/lib/activeresource/test/cases/log_subscriber_test.rb +63 -0
- data/lib/activeresource/test/cases/reflection_test.rb +65 -0
- data/lib/activeresource/test/cases/validations_test.rb +78 -0
- data/lib/activeresource/test/fixtures/address.rb +20 -0
- data/lib/activeresource/test/fixtures/beast.rb +16 -0
- data/lib/activeresource/test/fixtures/comment.rb +5 -0
- data/lib/activeresource/test/fixtures/customer.rb +5 -0
- data/lib/activeresource/test/fixtures/inventory.rb +14 -0
- data/lib/activeresource/test/fixtures/person.rb +15 -0
- data/lib/activeresource/test/fixtures/pet.rb +6 -0
- data/lib/activeresource/test/fixtures/post.rb +5 -0
- data/lib/activeresource/test/fixtures/product.rb +11 -0
- data/lib/activeresource/test/fixtures/project.rb +19 -0
- data/lib/activeresource/test/fixtures/proxy.rb +6 -0
- data/lib/activeresource/test/fixtures/sound.rb +11 -0
- data/lib/activeresource/test/fixtures/street_address.rb +6 -0
- data/lib/activeresource/test/fixtures/subscription_plan.rb +7 -0
- data/lib/activeresource/test/fixtures/weather.rb +21 -0
- data/lib/activeresource/test/setter_trap.rb +28 -0
- data/lib/activeresource/test/singleton_test.rb +138 -0
- data/lib/activeresource/test/threadsafe_attributes_test.rb +91 -0
- data/lib/generators/active_cached_resource/install_generator.rb +31 -0
- data/lib/generators/active_cached_resource/templates/migration.erb +16 -0
- data/sorbet/config +4 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/activemodel.rbi +89 -0
- data/sorbet/rbi/annotations/activesupport.rbi +457 -0
- data/sorbet/rbi/annotations/minitest.rbi +119 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/dsl/.gitattributes +1 -0
- data/sorbet/rbi/dsl/active_support/callbacks.rbi +21 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/actioncable@8.0.0.rbi +252 -0
- data/sorbet/rbi/gems/actionmailbox@8.0.0.rbi +9 -0
- data/sorbet/rbi/gems/actionmailer@8.0.0.rbi +9 -0
- data/sorbet/rbi/gems/actionpack@8.0.0.rbi +20909 -0
- data/sorbet/rbi/gems/actiontext@8.0.0.rbi +9 -0
- data/sorbet/rbi/gems/actionview@8.0.0.rbi +16207 -0
- data/sorbet/rbi/gems/activejob@8.0.0.rbi +9 -0
- data/sorbet/rbi/gems/activemodel-serializers-xml@1.0.3.rbi +166 -0
- data/sorbet/rbi/gems/activemodel@8.0.0.rbi +6857 -0
- data/sorbet/rbi/gems/activerecord@8.0.0.rbi +42896 -0
- data/sorbet/rbi/gems/activeresource@6.1.4.rbi +3944 -0
- data/sorbet/rbi/gems/activestorage@8.0.0.rbi +9 -0
- data/sorbet/rbi/gems/activesupport@8.0.0.rbi +21251 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +585 -0
- data/sorbet/rbi/gems/base64@0.2.0.rbi +509 -0
- data/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
- data/sorbet/rbi/gems/bigdecimal@3.1.8.rbi +78 -0
- data/sorbet/rbi/gems/builder@3.3.0.rbi +9 -0
- data/sorbet/rbi/gems/bump@0.10.0.rbi +169 -0
- data/sorbet/rbi/gems/byebug@11.1.3.rbi +3607 -0
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +3427 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.3.4.rbi +11645 -0
- data/sorbet/rbi/gems/connection_pool@2.4.1.rbi +9 -0
- data/sorbet/rbi/gems/crass@1.0.6.rbi +623 -0
- data/sorbet/rbi/gems/date@3.4.0.rbi +75 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.1.rbi +1131 -0
- data/sorbet/rbi/gems/docile@1.4.1.rbi +377 -0
- data/sorbet/rbi/gems/drb@2.2.1.rbi +1347 -0
- data/sorbet/rbi/gems/erubi@1.13.0.rbi +150 -0
- data/sorbet/rbi/gems/globalid@1.2.1.rbi +9 -0
- data/sorbet/rbi/gems/i18n@1.14.6.rbi +2359 -0
- data/sorbet/rbi/gems/io-console@0.7.2.rbi +9 -0
- data/sorbet/rbi/gems/json@2.8.2.rbi +1901 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14238 -0
- data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +240 -0
- data/sorbet/rbi/gems/logger@1.6.1.rbi +920 -0
- data/sorbet/rbi/gems/loofah@2.23.1.rbi +1081 -0
- data/sorbet/rbi/gems/mail@2.8.1.rbi +9 -0
- data/sorbet/rbi/gems/marcel@1.0.4.rbi +9 -0
- data/sorbet/rbi/gems/method_source@1.1.0.rbi +304 -0
- data/sorbet/rbi/gems/mini_mime@1.1.5.rbi +9 -0
- data/sorbet/rbi/gems/minitest@5.25.2.rbi +1547 -0
- data/sorbet/rbi/gems/net-imap@0.5.1.rbi +9 -0
- data/sorbet/rbi/gems/net-pop@0.1.2.rbi +9 -0
- data/sorbet/rbi/gems/net-protocol@0.2.2.rbi +292 -0
- data/sorbet/rbi/gems/net-smtp@0.5.0.rbi +9 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
- data/sorbet/rbi/gems/nio4r@2.7.4.rbi +9 -0
- data/sorbet/rbi/gems/nokogiri@1.16.7.rbi +7311 -0
- data/sorbet/rbi/gems/parallel@1.26.3.rbi +291 -0
- data/sorbet/rbi/gems/parser@3.3.6.0.rbi +5519 -0
- data/sorbet/rbi/gems/prism@1.2.0.rbi +39085 -0
- data/sorbet/rbi/gems/pry-byebug@3.10.1.rbi +1151 -0
- data/sorbet/rbi/gems/pry@0.14.2.rbi +10076 -0
- data/sorbet/rbi/gems/psych@5.2.0.rbi +1785 -0
- data/sorbet/rbi/gems/racc@1.8.1.rbi +162 -0
- data/sorbet/rbi/gems/rack-session@2.0.0.rbi +727 -0
- data/sorbet/rbi/gems/rack-test@2.1.0.rbi +747 -0
- data/sorbet/rbi/gems/rack@3.1.8.rbi +4905 -0
- data/sorbet/rbi/gems/rackup@2.2.1.rbi +230 -0
- data/sorbet/rbi/gems/rails-dom-testing@2.2.0.rbi +758 -0
- data/sorbet/rbi/gems/rails-html-sanitizer@1.6.0.rbi +785 -0
- data/sorbet/rbi/gems/rails@8.0.0.rbi +9 -0
- data/sorbet/rbi/gems/railties@8.0.0.rbi +6287 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +403 -0
- data/sorbet/rbi/gems/rake@13.2.1.rbi +3091 -0
- data/sorbet/rbi/gems/rbi@0.2.1.rbi +4535 -0
- data/sorbet/rbi/gems/rdoc@6.8.1.rbi +12572 -0
- data/sorbet/rbi/gems/regexp_parser@2.9.2.rbi +3772 -0
- data/sorbet/rbi/gems/reline@0.5.12.rbi +2416 -0
- data/sorbet/rbi/gems/rexml@3.3.9.rbi +4858 -0
- data/sorbet/rbi/gems/rspec-core@3.13.2.rbi +11287 -0
- data/sorbet/rbi/gems/rspec-expectations@3.13.3.rbi +8183 -0
- data/sorbet/rbi/gems/rspec-mocks@3.13.2.rbi +5341 -0
- data/sorbet/rbi/gems/rspec-support@3.13.1.rbi +1630 -0
- data/sorbet/rbi/gems/rspec@3.13.0.rbi +83 -0
- data/sorbet/rbi/gems/rubocop-ast@1.36.1.rbi +7303 -0
- data/sorbet/rbi/gems/rubocop-performance@1.21.1.rbi +9 -0
- data/sorbet/rbi/gems/rubocop@1.65.1.rbi +58170 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1318 -0
- data/sorbet/rbi/gems/securerandom@0.3.2.rbi +395 -0
- data/sorbet/rbi/gems/simplecov-html@0.13.1.rbi +225 -0
- data/sorbet/rbi/gems/simplecov@0.22.0.rbi +2149 -0
- data/sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi +9 -0
- data/sorbet/rbi/gems/spoom@1.5.0.rbi +4932 -0
- data/sorbet/rbi/gems/standard-custom@1.0.2.rbi +9 -0
- data/sorbet/rbi/gems/standard-performance@1.4.0.rbi +9 -0
- data/sorbet/rbi/gems/standard@1.40.0.rbi +929 -0
- data/sorbet/rbi/gems/stringio@3.1.2.rbi +9 -0
- data/sorbet/rbi/gems/tapioca@0.16.4.rbi +3597 -0
- data/sorbet/rbi/gems/thor@1.3.2.rbi +4378 -0
- data/sorbet/rbi/gems/timeout@0.4.2.rbi +151 -0
- data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5918 -0
- data/sorbet/rbi/gems/unicode-display_width@2.6.0.rbi +66 -0
- data/sorbet/rbi/gems/uri@1.0.2.rbi +2377 -0
- data/sorbet/rbi/gems/useragent@0.16.10.rbi +9 -0
- data/sorbet/rbi/gems/websocket-driver@0.7.6.rbi +9 -0
- data/sorbet/rbi/gems/websocket-extensions@0.1.5.rbi +9 -0
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
- data/sorbet/rbi/gems/yard@0.9.37.rbi +18504 -0
- data/sorbet/rbi/gems/zeitwerk@2.7.1.rbi +9 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +12 -0
- metadata +543 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/array/wrap"
|
|
4
|
+
|
|
5
|
+
module ActiveResource
|
|
6
|
+
module Callbacks
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
CALLBACKS = [
|
|
10
|
+
:before_validation, :after_validation, :before_save, :around_save, :after_save,
|
|
11
|
+
:before_create, :around_create, :after_create, :before_update, :around_update,
|
|
12
|
+
:after_update, :before_destroy, :around_destroy, :after_destroy
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
extend ActiveModel::Callbacks
|
|
17
|
+
include ActiveModel::Validations::Callbacks
|
|
18
|
+
|
|
19
|
+
define_model_callbacks :save, :create, :update, :destroy
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/module/delegation"
|
|
4
|
+
require "active_support/inflector"
|
|
5
|
+
|
|
6
|
+
module ActiveResource # :nodoc:
|
|
7
|
+
class Collection # :nodoc:
|
|
8
|
+
include Enumerable
|
|
9
|
+
delegate :to_yaml, *Array.public_instance_methods(false), to: :request_resources!
|
|
10
|
+
|
|
11
|
+
attr_accessor :resource_class, :query_params, :path_params
|
|
12
|
+
attr_writer :prefix_options
|
|
13
|
+
attr_reader :from
|
|
14
|
+
|
|
15
|
+
# ActiveResource::Collection is a wrapper to handle parsing index responses that
|
|
16
|
+
# do not directly map to Rails conventions.
|
|
17
|
+
#
|
|
18
|
+
# You can define a custom class that inherits from ActiveResource::Collection
|
|
19
|
+
# in order to to set the elements instance.
|
|
20
|
+
#
|
|
21
|
+
# GET /posts.json delivers following response body:
|
|
22
|
+
# {
|
|
23
|
+
# posts: [
|
|
24
|
+
# {
|
|
25
|
+
# title: "ActiveResource now has associations",
|
|
26
|
+
# body: "Lorem Ipsum"
|
|
27
|
+
# },
|
|
28
|
+
# {...}
|
|
29
|
+
# ],
|
|
30
|
+
# next_page: "/posts.json?page=2"
|
|
31
|
+
# }
|
|
32
|
+
#
|
|
33
|
+
# A Post class can be setup to handle it with:
|
|
34
|
+
#
|
|
35
|
+
# class Post < ActiveResource::Base
|
|
36
|
+
# self.site = "http://example.com"
|
|
37
|
+
# self.collection_parser = PostCollection
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# And the collection parser:
|
|
41
|
+
#
|
|
42
|
+
# class PostCollection < ActiveResource::Collection
|
|
43
|
+
# attr_accessor :next_page
|
|
44
|
+
# def parse_response(parsed = {})
|
|
45
|
+
# @elements = parsed['posts']
|
|
46
|
+
# @next_page = parsed['next_page']
|
|
47
|
+
# end
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# The result from a find method that returns multiple entries will now be a
|
|
51
|
+
# PostParser instance. ActiveResource::Collection includes Enumerable and
|
|
52
|
+
# instances can be iterated over just like an array.
|
|
53
|
+
# @posts = Post.find(:all) # => PostCollection:xxx
|
|
54
|
+
# @posts.next_page # => "/posts.json?page=2"
|
|
55
|
+
# @posts.map(&:id) # =>[1, 3, 5 ...]
|
|
56
|
+
#
|
|
57
|
+
# The ActiveResource::Collection#parse_response method will receive the ActiveResource::Formats parsed result
|
|
58
|
+
# and should set @elements.
|
|
59
|
+
def initialize(elements = [], from = nil)
|
|
60
|
+
@from = from
|
|
61
|
+
@elements = elements
|
|
62
|
+
@requested = false
|
|
63
|
+
# This can get called without a response, so parse only if response is present
|
|
64
|
+
parse_response(@elements) if @elements.present?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Processes and sets the collection elements. This method assigns the provided `elements`
|
|
68
|
+
# (or an empty array if none provided) to the `@elements` instance variable.
|
|
69
|
+
#
|
|
70
|
+
# ==== Arguments
|
|
71
|
+
#
|
|
72
|
+
# +elements+ (Array<Object>) - An optional array of resources to be set as the collection elements.
|
|
73
|
+
# Defaults to an empty array.
|
|
74
|
+
#
|
|
75
|
+
# This method is called after fetching the resource and can be overridden by subclasses to
|
|
76
|
+
# handle any specific response format of the API.
|
|
77
|
+
def parse_response(elements)
|
|
78
|
+
@elements = elements || []
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns the prefix options for the collection, which are used for constructing the resource path.
|
|
82
|
+
#
|
|
83
|
+
# ==== Returns
|
|
84
|
+
#
|
|
85
|
+
# [Hash] The prefix options for the collection.
|
|
86
|
+
def prefix_options
|
|
87
|
+
@prefix_options || {}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Refreshes the collection by re-fetching the resources from the API.
|
|
91
|
+
#
|
|
92
|
+
# ==== Returns
|
|
93
|
+
#
|
|
94
|
+
# [Array<Object>] The collection of resources retrieved from the API.
|
|
95
|
+
def refresh
|
|
96
|
+
@requested = false
|
|
97
|
+
request_resources!
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Executes the request to fetch the collection of resources from the API and returns the collection.
|
|
101
|
+
#
|
|
102
|
+
# ==== Returns
|
|
103
|
+
#
|
|
104
|
+
# [ActiveResource::Collection] The collection of resources.
|
|
105
|
+
def call
|
|
106
|
+
request_resources!
|
|
107
|
+
self
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Checks if the collection has been requested.
|
|
111
|
+
#
|
|
112
|
+
# ==== Returns
|
|
113
|
+
#
|
|
114
|
+
# [Boolean] true if the collection has been requested, false otherwise.
|
|
115
|
+
def requested?
|
|
116
|
+
@requested
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Returns the first resource in the collection, or creates a new resource using the provided
|
|
120
|
+
# attributes if the collection is empty.
|
|
121
|
+
#
|
|
122
|
+
# ==== Arguments
|
|
123
|
+
#
|
|
124
|
+
# +attributes+ (Hash) - The attributes for creating the resource.
|
|
125
|
+
#
|
|
126
|
+
# ==== Returns
|
|
127
|
+
#
|
|
128
|
+
# [Object] The first resource, or a newly created resource if none exist.
|
|
129
|
+
#
|
|
130
|
+
# ==== Example
|
|
131
|
+
# post = PostCollection.where(title: "New Post").first_or_create
|
|
132
|
+
# # => Post instance with title "New Post"
|
|
133
|
+
def first_or_create(attributes = {})
|
|
134
|
+
first || resource_class.create(query_params.update(attributes))
|
|
135
|
+
rescue NoMethodError
|
|
136
|
+
raise "Cannot create resource from resource type: #{resource_class.inspect}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Returns the first resource in the collection, or initializes a new resource using the provided
|
|
140
|
+
# attributes if the collection is empty.
|
|
141
|
+
#
|
|
142
|
+
# ==== Arguments
|
|
143
|
+
#
|
|
144
|
+
# +attributes+ (Hash) - The attributes for initializing the resource.
|
|
145
|
+
#
|
|
146
|
+
# ==== Returns
|
|
147
|
+
#
|
|
148
|
+
# [Object] The first resource, or a newly initialized resource if none exist.
|
|
149
|
+
#
|
|
150
|
+
# ==== Example
|
|
151
|
+
# post = PostCollection.where(title: "New Post").first_or_initialize
|
|
152
|
+
# # => Post instance with title "New Post"
|
|
153
|
+
def first_or_initialize(attributes = {})
|
|
154
|
+
first || resource_class.new(query_params.update(attributes))
|
|
155
|
+
rescue NoMethodError
|
|
156
|
+
raise "Cannot build resource from resource type: #{resource_class.inspect}"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Filters the collection based on the provided clauses (query parameters).
|
|
160
|
+
#
|
|
161
|
+
# ==== Arguments
|
|
162
|
+
#
|
|
163
|
+
# +clauses+ (Hash) - A hash of query parameters used to filter the collection.
|
|
164
|
+
#
|
|
165
|
+
# ==== Returns
|
|
166
|
+
#
|
|
167
|
+
# [ActiveResource::Collection] A new collection filtered by the specified clauses.
|
|
168
|
+
#
|
|
169
|
+
# ==== Example
|
|
170
|
+
# filtered_posts = PostCollection.where(title: "Post 1")
|
|
171
|
+
# # => PostCollection:xxx (filtered collection)
|
|
172
|
+
def where(clauses = {})
|
|
173
|
+
raise ArgumentError, "expected a clauses Hash, got #{clauses.inspect}" unless clauses.is_a? Hash
|
|
174
|
+
new_clauses = query_params.merge(clauses)
|
|
175
|
+
resource_class.where(new_clauses)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
def query_string(options)
|
|
180
|
+
"?#{options.to_query}" unless options.nil? || options.empty?
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Requests resources from the API and parses the response. The resources are then mapped to their respective
|
|
184
|
+
# resource class instances.
|
|
185
|
+
#
|
|
186
|
+
# ==== Returns
|
|
187
|
+
#
|
|
188
|
+
# [Array<Object>] The collection of resources retrieved from the API.
|
|
189
|
+
def request_resources!
|
|
190
|
+
return @elements if requested?
|
|
191
|
+
response =
|
|
192
|
+
case from
|
|
193
|
+
when Symbol
|
|
194
|
+
resource_class.get(from, path_params)
|
|
195
|
+
when String
|
|
196
|
+
path = "#{from}#{query_string(query_params)}"
|
|
197
|
+
resource_class.format.decode(resource_class.connection.get(path, resource_class.headers).body)
|
|
198
|
+
else
|
|
199
|
+
path = resource_class.collection_path(prefix_options, query_params)
|
|
200
|
+
resource_class.format.decode(resource_class.connection.get(path, resource_class.headers).body)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Update the elements
|
|
204
|
+
parse_response(response)
|
|
205
|
+
@elements.map! { |e| resource_class.instantiate_record(e, prefix_options) }
|
|
206
|
+
rescue ActiveResource::ResourceNotFound
|
|
207
|
+
# Swallowing ResourceNotFound exceptions and return nothing - as per ActiveRecord.
|
|
208
|
+
# Needs to be empty array as Array methods are delegated
|
|
209
|
+
[]
|
|
210
|
+
ensure
|
|
211
|
+
@requested = true
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/benchmark"
|
|
4
|
+
require "active_support/core_ext/object/inclusion"
|
|
5
|
+
require "net/https"
|
|
6
|
+
require "date"
|
|
7
|
+
require "time"
|
|
8
|
+
|
|
9
|
+
module ActiveResource
|
|
10
|
+
# Class to handle connections to remote web services.
|
|
11
|
+
# This class is used by ActiveResource::Base to interface with REST
|
|
12
|
+
# services.
|
|
13
|
+
class Connection
|
|
14
|
+
HTTP_FORMAT_HEADER_NAMES = { get: "Accept",
|
|
15
|
+
put: "Content-Type",
|
|
16
|
+
post: "Content-Type",
|
|
17
|
+
patch: "Content-Type",
|
|
18
|
+
delete: "Accept",
|
|
19
|
+
head: "Accept"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
attr_reader :site, :user, :password, :bearer_token, :auth_type, :timeout, :open_timeout, :read_timeout, :proxy, :ssl_options
|
|
23
|
+
attr_accessor :format, :logger
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
def requests
|
|
27
|
+
@@requests ||= []
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The +site+ parameter is required and will set the +site+
|
|
32
|
+
# attribute to the URI for the remote resource service.
|
|
33
|
+
def initialize(site, format = ActiveResource::Formats::JsonFormat, logger: nil)
|
|
34
|
+
raise ArgumentError, "Missing site URI" unless site
|
|
35
|
+
@proxy = @user = @password = @bearer_token = nil
|
|
36
|
+
self.site = site
|
|
37
|
+
self.format = format
|
|
38
|
+
self.logger = logger
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Set URI for remote service.
|
|
42
|
+
def site=(site)
|
|
43
|
+
@site = site.is_a?(URI) ? site : URI.parse(site)
|
|
44
|
+
@ssl_options ||= {} if @site.is_a?(URI::HTTPS)
|
|
45
|
+
@user = URI_PARSER.unescape(@site.user) if @site.user
|
|
46
|
+
@password = URI_PARSER.unescape(@site.password) if @site.password
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Set the proxy for remote service.
|
|
50
|
+
def proxy=(proxy)
|
|
51
|
+
@proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Sets the user for remote service.
|
|
55
|
+
attr_writer :user
|
|
56
|
+
|
|
57
|
+
# Sets the password for remote service.
|
|
58
|
+
attr_writer :password
|
|
59
|
+
|
|
60
|
+
# Sets the bearer token for remote service.
|
|
61
|
+
attr_writer :bearer_token
|
|
62
|
+
|
|
63
|
+
# Sets the auth type for remote service.
|
|
64
|
+
def auth_type=(auth_type)
|
|
65
|
+
@auth_type = legitimize_auth_type(auth_type)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Sets the number of seconds after which HTTP requests to the remote service should time out.
|
|
69
|
+
attr_writer :timeout
|
|
70
|
+
|
|
71
|
+
# Sets the number of seconds after which HTTP connects to the remote service should time out.
|
|
72
|
+
attr_writer :open_timeout
|
|
73
|
+
|
|
74
|
+
# Sets the number of seconds after which HTTP read requests to the remote service should time out.
|
|
75
|
+
attr_writer :read_timeout
|
|
76
|
+
|
|
77
|
+
# Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'.
|
|
78
|
+
attr_writer :ssl_options
|
|
79
|
+
|
|
80
|
+
# Executes a GET request.
|
|
81
|
+
# Used to get (find) resources.
|
|
82
|
+
def get(path, headers = {})
|
|
83
|
+
with_auth { request(:get, path, build_request_headers(headers, :get, self.site.merge(path))) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Executes a DELETE request (see HTTP protocol documentation if unfamiliar).
|
|
87
|
+
# Used to delete resources.
|
|
88
|
+
def delete(path, headers = {})
|
|
89
|
+
with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Executes a PATCH request (see HTTP protocol documentation if unfamiliar).
|
|
93
|
+
# Used to update resources.
|
|
94
|
+
def patch(path, body = "", headers = {})
|
|
95
|
+
with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Executes a PUT request (see HTTP protocol documentation if unfamiliar).
|
|
99
|
+
# Used to update resources.
|
|
100
|
+
def put(path, body = "", headers = {})
|
|
101
|
+
with_auth { request(:put, path, body.to_s, build_request_headers(headers, :put, self.site.merge(path))) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Executes a POST request.
|
|
105
|
+
# Used to create new resources.
|
|
106
|
+
def post(path, body = "", headers = {})
|
|
107
|
+
with_auth { request(:post, path, body.to_s, build_request_headers(headers, :post, self.site.merge(path))) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Executes a HEAD request.
|
|
111
|
+
# Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
|
|
112
|
+
def head(path, headers = {})
|
|
113
|
+
with_auth { request(:head, path, build_request_headers(headers, :head, self.site.merge(path))) }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
# Makes a request to the remote service.
|
|
118
|
+
def request(method, path, *arguments)
|
|
119
|
+
result = ActiveSupport::Notifications.instrument("request.active_resource") do |payload|
|
|
120
|
+
payload[:method] = method
|
|
121
|
+
payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}"
|
|
122
|
+
payload[:result] = http.send(method, path, *arguments)
|
|
123
|
+
end
|
|
124
|
+
handle_response(result)
|
|
125
|
+
rescue Timeout::Error => e
|
|
126
|
+
raise TimeoutError.new(e.message)
|
|
127
|
+
rescue OpenSSL::SSL::SSLError => e
|
|
128
|
+
raise SSLError.new(e.message)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Handles response and error codes from the remote service.
|
|
132
|
+
def handle_response(response)
|
|
133
|
+
case response.code.to_i
|
|
134
|
+
when 301, 302, 303, 307
|
|
135
|
+
raise(Redirection.new(response))
|
|
136
|
+
when 200...400
|
|
137
|
+
response
|
|
138
|
+
when 400
|
|
139
|
+
raise(BadRequest.new(response))
|
|
140
|
+
when 401
|
|
141
|
+
raise(UnauthorizedAccess.new(response))
|
|
142
|
+
when 402
|
|
143
|
+
raise(PaymentRequired.new(response))
|
|
144
|
+
when 403
|
|
145
|
+
raise(ForbiddenAccess.new(response))
|
|
146
|
+
when 404
|
|
147
|
+
raise(ResourceNotFound.new(response))
|
|
148
|
+
when 405
|
|
149
|
+
raise(MethodNotAllowed.new(response))
|
|
150
|
+
when 409
|
|
151
|
+
raise(ResourceConflict.new(response))
|
|
152
|
+
when 410
|
|
153
|
+
raise(ResourceGone.new(response))
|
|
154
|
+
when 412
|
|
155
|
+
raise(PreconditionFailed.new(response))
|
|
156
|
+
when 422
|
|
157
|
+
raise(ResourceInvalid.new(response))
|
|
158
|
+
when 429
|
|
159
|
+
raise(TooManyRequests.new(response))
|
|
160
|
+
when 401...500
|
|
161
|
+
raise(ClientError.new(response))
|
|
162
|
+
when 500...600
|
|
163
|
+
raise(ServerError.new(response))
|
|
164
|
+
else
|
|
165
|
+
raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Creates new Net::HTTP instance for communication with the
|
|
170
|
+
# remote service and resources.
|
|
171
|
+
def http
|
|
172
|
+
configure_http(new_http)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def new_http
|
|
176
|
+
if @proxy
|
|
177
|
+
user = URI_PARSER.unescape(@proxy.user) if @proxy.user
|
|
178
|
+
password = URI_PARSER.unescape(@proxy.password) if @proxy.password
|
|
179
|
+
Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, user, password)
|
|
180
|
+
else
|
|
181
|
+
Net::HTTP.new(@site.host, @site.port)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def configure_http(http)
|
|
186
|
+
apply_ssl_options(http).tap do |https|
|
|
187
|
+
# Net::HTTP timeouts default to 60 seconds.
|
|
188
|
+
if defined? @timeout
|
|
189
|
+
https.open_timeout = @timeout
|
|
190
|
+
https.read_timeout = @timeout
|
|
191
|
+
end
|
|
192
|
+
https.open_timeout = @open_timeout if defined?(@open_timeout)
|
|
193
|
+
https.read_timeout = @read_timeout if defined?(@read_timeout)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def apply_ssl_options(http)
|
|
198
|
+
http.tap do |https|
|
|
199
|
+
# Skip config if site is already a https:// URI.
|
|
200
|
+
if defined? @ssl_options
|
|
201
|
+
http.use_ssl = true
|
|
202
|
+
|
|
203
|
+
# All the SSL options have corresponding http settings.
|
|
204
|
+
@ssl_options.each { |key, value| http.send "#{key}=", value }
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def default_header
|
|
210
|
+
@default_header ||= {}
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Builds headers for request to remote service.
|
|
214
|
+
def build_request_headers(headers, http_method, uri)
|
|
215
|
+
authorization_header(http_method, uri).update(default_header).update(http_format_header(http_method)).update(headers.to_hash)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def response_auth_header
|
|
219
|
+
@response_auth_header ||= ""
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def with_auth
|
|
223
|
+
retried ||= false
|
|
224
|
+
yield
|
|
225
|
+
rescue UnauthorizedAccess => e
|
|
226
|
+
raise if retried || auth_type != :digest
|
|
227
|
+
@response_auth_header = e.response["WWW-Authenticate"]
|
|
228
|
+
retried = true
|
|
229
|
+
retry
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def authorization_header(http_method, uri)
|
|
233
|
+
if @user || @password
|
|
234
|
+
if auth_type == :digest
|
|
235
|
+
{ "Authorization" => digest_auth_header(http_method, uri) }
|
|
236
|
+
else
|
|
237
|
+
{ "Authorization" => "Basic " + ["#{@user}:#{@password}"].pack("m").delete("\r\n") }
|
|
238
|
+
end
|
|
239
|
+
elsif @bearer_token
|
|
240
|
+
{ "Authorization" => "Bearer #{@bearer_token}" }
|
|
241
|
+
else
|
|
242
|
+
{}
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def digest_auth_header(http_method, uri)
|
|
247
|
+
params = extract_params_from_response
|
|
248
|
+
|
|
249
|
+
request_uri = uri.path
|
|
250
|
+
request_uri << "?#{uri.query}" if uri.query
|
|
251
|
+
|
|
252
|
+
ha1 = Digest::MD5.hexdigest("#{@user}:#{params['realm']}:#{@password}")
|
|
253
|
+
ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{request_uri}")
|
|
254
|
+
|
|
255
|
+
params["cnonce"] = client_nonce
|
|
256
|
+
request_digest = Digest::MD5.hexdigest([ha1, params["nonce"], "0", params["cnonce"], params["qop"], ha2].join(":"))
|
|
257
|
+
"Digest #{auth_attributes_for(uri, request_digest, params)}"
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def client_nonce
|
|
261
|
+
Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535)))
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def extract_params_from_response
|
|
265
|
+
params = {}
|
|
266
|
+
if response_auth_header =~ /^(\w+) (.*)/
|
|
267
|
+
$2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
|
|
268
|
+
end
|
|
269
|
+
params
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def auth_attributes_for(uri, request_digest, params)
|
|
273
|
+
auth_attrs =
|
|
274
|
+
[
|
|
275
|
+
%Q(username="#{@user}"),
|
|
276
|
+
%Q(realm="#{params['realm']}"),
|
|
277
|
+
%Q(qop="#{params['qop']}"),
|
|
278
|
+
%Q(uri="#{uri.path}"),
|
|
279
|
+
%Q(nonce="#{params['nonce']}"),
|
|
280
|
+
'nc="0"',
|
|
281
|
+
%Q(cnonce="#{params['cnonce']}"),
|
|
282
|
+
%Q(response="#{request_digest}")]
|
|
283
|
+
|
|
284
|
+
auth_attrs << %Q(opaque="#{params['opaque']}") unless params["opaque"].blank?
|
|
285
|
+
auth_attrs.join(", ")
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def http_format_header(http_method)
|
|
289
|
+
{ HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type }
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def legitimize_auth_type(auth_type)
|
|
293
|
+
return :basic if auth_type.nil?
|
|
294
|
+
auth_type = auth_type.to_sym
|
|
295
|
+
auth_type.in?([:basic, :digest, :bearer]) ? auth_type : :basic
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/object/blank"
|
|
4
|
+
|
|
5
|
+
module ActiveResource
|
|
6
|
+
# A module to support custom REST methods and sub-resources, allowing you to break out
|
|
7
|
+
# of the "default" REST methods with your own custom resource requests. For example,
|
|
8
|
+
# say you use Rails to expose a REST service and configure your routes with:
|
|
9
|
+
#
|
|
10
|
+
# map.resources :people, :new => { :register => :post },
|
|
11
|
+
# :member => { :promote => :put, :deactivate => :delete }
|
|
12
|
+
# :collection => { :active => :get }
|
|
13
|
+
#
|
|
14
|
+
# This route set creates routes for the following HTTP requests:
|
|
15
|
+
#
|
|
16
|
+
# POST /people/new/register.json # PeopleController.register
|
|
17
|
+
# PATCH/PUT /people/1/promote.json # PeopleController.promote with :id => 1
|
|
18
|
+
# DELETE /people/1/deactivate.json # PeopleController.deactivate with :id => 1
|
|
19
|
+
# GET /people/active.json # PeopleController.active
|
|
20
|
+
#
|
|
21
|
+
# Using this module, Active Resource can use these custom REST methods just like the
|
|
22
|
+
# standard methods.
|
|
23
|
+
#
|
|
24
|
+
# class Person < ActiveResource::Base
|
|
25
|
+
# self.site = "https://37s.sunrise.com"
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# Person.new(:name => 'Ryan').post(:register) # POST /people/new/register.json
|
|
29
|
+
# # => { :id => 1, :name => 'Ryan' }
|
|
30
|
+
#
|
|
31
|
+
# Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.json
|
|
32
|
+
# Person.find(1).delete(:deactivate) # DELETE /people/1/deactivate.json
|
|
33
|
+
#
|
|
34
|
+
# Person.get(:active) # GET /people/active.json
|
|
35
|
+
# # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
|
|
36
|
+
#
|
|
37
|
+
module CustomMethods
|
|
38
|
+
extend ActiveSupport::Concern
|
|
39
|
+
|
|
40
|
+
included do
|
|
41
|
+
class << self
|
|
42
|
+
alias :orig_delete :delete
|
|
43
|
+
|
|
44
|
+
# Invokes a GET to a given custom REST method. For example:
|
|
45
|
+
#
|
|
46
|
+
# Person.get(:active) # GET /people/active.json
|
|
47
|
+
# # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
|
|
48
|
+
#
|
|
49
|
+
# Person.get(:active, :awesome => true) # GET /people/active.json?awesome=true
|
|
50
|
+
# # => [{:id => 1, :name => 'Ryan'}]
|
|
51
|
+
#
|
|
52
|
+
# Note: the objects returned from this method are not automatically converted
|
|
53
|
+
# into ActiveResource::Base instances - they are ordinary Hashes. If you are expecting
|
|
54
|
+
# ActiveResource::Base instances, use the <tt>find</tt> class method with the
|
|
55
|
+
# <tt>:from</tt> option. For example:
|
|
56
|
+
#
|
|
57
|
+
# Person.find(:all, :from => :active)
|
|
58
|
+
def get(custom_method_name, options = {})
|
|
59
|
+
hashified = format.decode(connection.get(custom_method_collection_url(custom_method_name, options), headers).body)
|
|
60
|
+
derooted = Formats.remove_root(hashified)
|
|
61
|
+
derooted.is_a?(Array) ? derooted.map { |e| Formats.remove_root(e) } : derooted
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def post(custom_method_name, options = {}, body = "")
|
|
65
|
+
connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def patch(custom_method_name, options = {}, body = "")
|
|
69
|
+
connection.patch(custom_method_collection_url(custom_method_name, options), body, headers)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def put(custom_method_name, options = {}, body = "")
|
|
73
|
+
connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def delete(custom_method_name, options = {})
|
|
77
|
+
# Need to jump through some hoops to retain the original class 'delete' method
|
|
78
|
+
if custom_method_name.is_a?(Symbol)
|
|
79
|
+
connection.delete(custom_method_collection_url(custom_method_name, options), headers)
|
|
80
|
+
else
|
|
81
|
+
orig_delete(custom_method_name, options)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
module ClassMethods
|
|
88
|
+
def custom_method_collection_url(method_name, options = {})
|
|
89
|
+
prefix_options, query_options = split_options(options)
|
|
90
|
+
"#{prefix(prefix_options)}#{collection_name}/#{method_name}#{format_extension}#{query_string(query_options)}"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def get(method_name, options = {})
|
|
95
|
+
self.class.format.decode(connection.get(custom_method_element_url(method_name, options), self.class.headers).body)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def post(method_name, options = {}, body = nil)
|
|
99
|
+
request_body = body.blank? ? encode : body
|
|
100
|
+
if new?
|
|
101
|
+
connection.post(custom_method_new_element_url(method_name, options), request_body, self.class.headers)
|
|
102
|
+
else
|
|
103
|
+
connection.post(custom_method_element_url(method_name, options), request_body, self.class.headers)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def patch(method_name, options = {}, body = "")
|
|
108
|
+
connection.patch(custom_method_element_url(method_name, options), body, self.class.headers)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def put(method_name, options = {}, body = "")
|
|
112
|
+
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def delete(method_name, options = {})
|
|
116
|
+
connection.delete(custom_method_element_url(method_name, options), self.class.headers)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
def custom_method_element_url(method_name, options = {})
|
|
122
|
+
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{URI.encode_www_form_component(id.to_s)}/#{method_name}#{self.class.format_extension}#{self.class.__send__(:query_string, options)}"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def custom_method_new_element_url(method_name, options = {})
|
|
126
|
+
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}#{self.class.format_extension}#{self.class.__send__(:query_string, options)}"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|