├── example ├── consumer │ ├── db │ │ ├── seeds.rb │ │ └── cache_schema.rb │ ├── log │ │ └── .keep │ ├── tmp │ │ ├── .keep │ │ ├── cache │ │ │ └── .keep │ │ ├── pids │ │ │ └── .keep │ │ └── storage │ │ │ └── .keep │ ├── lib │ │ └── tasks │ │ │ └── .keep │ ├── script │ │ └── .keep │ ├── storage │ │ └── .keep │ ├── vendor │ │ └── .keep │ ├── app │ │ ├── models │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ ├── application_record.rb │ │ │ └── person.rb │ │ ├── controllers │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ └── application_controller.rb │ │ ├── views │ │ │ └── layouts │ │ │ │ ├── mailer.text.erb │ │ │ │ └── mailer.html.erb │ │ ├── mailers │ │ │ └── application_mailer.rb │ │ └── jobs │ │ │ └── application_job.rb │ ├── bin │ │ ├── dev │ │ ├── rake │ │ ├── thrust │ │ ├── jobs │ │ ├── rails │ │ ├── brakeman │ │ ├── rubocop │ │ ├── docker-entrypoint │ │ ├── kamal │ │ ├── setup │ │ └── bundle │ ├── .kamal │ │ ├── hooks │ │ │ ├── docker-setup.sample │ │ │ ├── post-proxy-reboot.sample │ │ │ ├── pre-proxy-reboot.sample │ │ │ ├── post-deploy.sample │ │ │ ├── pre-connect.sample │ │ │ ├── pre-build.sample │ │ │ └── pre-deploy.sample │ │ └── secrets │ ├── public │ │ └── robots.txt │ ├── config │ │ ├── boot.rb │ │ ├── environment.rb │ │ ├── database.yml │ │ ├── recurring.yml │ │ ├── cache.yml │ │ ├── queue.yml │ │ ├── routes.rb │ │ ├── initializers │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── cors.rb │ │ │ └── inflections.rb │ │ ├── credentials.yml.enc │ │ ├── application.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── puma.rb │ │ └── environments │ │ │ ├── test.rb │ │ │ └── development.rb │ ├── config.ru │ ├── Rakefile │ ├── .gitattributes │ ├── Gemfile │ ├── .dockerignore │ ├── .gitignore │ └── Dockerfile └── provider │ ├── log │ └── .keep │ ├── tmp │ ├── .keep │ ├── pids │ │ └── .keep │ └── storage │ │ └── .keep │ ├── lib │ └── tasks │ │ └── .keep │ ├── script │ └── .keep │ ├── storage │ └── .keep │ ├── vendor │ └── .keep │ ├── app │ ├── models │ │ ├── concerns │ │ │ └── .keep │ │ ├── address.rb │ │ ├── company.rb │ │ ├── application_record.rb │ │ └── person.rb │ ├── controllers │ │ ├── concerns │ │ │ └── .keep │ │ ├── application_controller.rb │ │ └── people_controller.rb │ ├── views │ │ └── layouts │ │ │ ├── mailer.text.erb │ │ │ └── mailer.html.erb │ ├── mailers │ │ └── application_mailer.rb │ └── jobs │ │ └── application_job.rb │ ├── bin │ ├── dev │ ├── rake │ ├── thrust │ ├── jobs │ ├── rails │ ├── brakeman │ ├── rubocop │ ├── docker-entrypoint │ ├── kamal │ └── setup │ ├── config │ ├── routes.rb │ ├── boot.rb │ ├── environment.rb │ ├── recurring.yml │ ├── cache.yml │ ├── queue.yml │ ├── database.yml │ ├── initializers │ │ ├── filter_parameter_logging.rb │ │ ├── cors.rb │ │ └── inflections.rb │ ├── credentials.yml.enc │ ├── locales │ │ └── en.yml │ ├── application.rb │ ├── puma.rb │ └── environments │ │ ├── test.rb │ │ └── development.rb │ ├── .kamal │ ├── hooks │ │ ├── docker-setup.sample │ │ ├── post-proxy-reboot.sample │ │ ├── pre-proxy-reboot.sample │ │ ├── post-deploy.sample │ │ ├── pre-connect.sample │ │ ├── pre-build.sample │ │ └── pre-deploy.sample │ └── secrets │ ├── public │ └── robots.txt │ ├── config.ru │ ├── Rakefile │ ├── db │ ├── migrate │ │ ├── 20241202183937_create_people.rb │ │ ├── 20241202183955_create_addresses.rb │ │ └── 20241202184017_create_companies.rb │ ├── seeds.rb │ ├── cache_schema.rb │ └── schema.rb │ ├── Gemfile │ ├── .gitattributes │ ├── .gitignore │ ├── .dockerignore │ └── Dockerfile ├── .env ├── .standard.yml ├── .rspec ├── sorbet ├── rbi │ ├── dsl │ │ ├── .gitattributes │ │ └── active_support │ │ │ └── callbacks.rbi │ ├── gems │ │ ├── .gitattributes │ │ ├── mail@2.8.1.rbi │ │ ├── marcel@1.0.4.rbi │ │ ├── nio4r@2.7.4.rbi │ │ ├── rails@8.0.1.rbi │ │ ├── globalid@1.2.1.rbi │ │ ├── net-imap@0.5.2.rbi │ │ ├── net-pop@0.1.2.rbi │ │ ├── net-smtp@0.5.0.rbi │ │ ├── stringio@3.1.2.rbi │ │ ├── zeitwerk@2.7.1.rbi │ │ ├── mini_mime@1.1.5.rbi │ │ ├── useragent@0.16.11.rbi │ │ ├── actiontext@8.0.1.rbi │ │ ├── io-console@0.8.0.rbi │ │ ├── actionmailbox@8.0.1.rbi │ │ ├── actionmailer@8.0.1.rbi │ │ ├── activestorage@8.0.1.rbi │ │ ├── ruby2_keywords@0.0.5.rbi │ │ ├── connection_pool@2.4.1.rbi │ │ ├── standard-custom@1.0.2.rbi │ │ ├── websocket-driver@0.7.6.rbi │ │ ├── rubocop-performance@1.23.0.rbi │ │ ├── standard-performance@1.6.0.rbi │ │ ├── websocket-extensions@0.1.5.rbi │ │ ├── mocha@2.7.1.rbi │ │ ├── simplecov_json_formatter@0.1.4.rbi │ │ ├── date@3.4.1.rbi │ │ ├── securerandom@0.4.1.rbi │ │ ├── bigdecimal@3.1.8.rbi │ │ ├── activejob@8.0.1.rbi │ │ └── rspec@3.13.0.rbi │ └── annotations │ │ └── .gitattributes ├── config └── tapioca │ ├── config.yml │ └── require.rb ├── .yardopts ├── lib ├── activeresource │ ├── lib │ │ ├── activeresource.rb │ │ ├── active_resource │ │ │ ├── associations │ │ │ │ └── builder │ │ │ │ │ ├── has_one.rb │ │ │ │ │ ├── has_many.rb │ │ │ │ │ ├── belongs_to.rb │ │ │ │ │ └── association.rb │ │ │ ├── formats │ │ │ │ ├── xml_format.rb │ │ │ │ └── json_format.rb │ │ │ ├── callbacks.rb │ │ │ ├── active_job_serializer.rb │ │ │ ├── railtie.rb │ │ │ ├── inheriting_hash.rb │ │ │ ├── formats.rb │ │ │ ├── log_subscriber.rb │ │ │ ├── schema.rb │ │ │ ├── reflection.rb │ │ │ ├── threadsafe_attributes.rb │ │ │ └── exceptions.rb │ │ └── active_resource.rb │ ├── test │ │ ├── fixtures │ │ │ ├── post.rb │ │ │ ├── customer.rb │ │ │ ├── comment.rb │ │ │ ├── pet.rb │ │ │ ├── proxy.rb │ │ │ ├── street_address.rb │ │ │ ├── subscription_plan.rb │ │ │ ├── sound.rb │ │ │ ├── inventory.rb │ │ │ ├── product.rb │ │ │ ├── beast.rb │ │ │ ├── person.rb │ │ │ ├── weather.rb │ │ │ ├── address.rb │ │ │ ├── project.rb │ │ │ └── fixtures.rbi │ │ ├── cases │ │ │ ├── inheritence_test.rb │ │ │ ├── inheriting_hash_test.rb │ │ │ ├── associations │ │ │ │ └── builder │ │ │ │ │ ├── has_one_test.rb │ │ │ │ │ ├── has_many_test.rb │ │ │ │ │ └── belongs_to_test.rb │ │ │ ├── base │ │ │ │ └── equality_test.rb │ │ │ ├── active_job_serializer_test.rb │ │ │ ├── log_subscriber_test.rb │ │ │ ├── reflection_test.rb │ │ │ └── validations_test.rb │ │ ├── setter_trap.rb │ │ └── threadsafe_attributes_test.rb │ ├── .gitignore │ └── examples │ │ └── performance.rb ├── active_cached_resource │ ├── version.rb │ ├── constants.rb │ ├── caching_strategies │ │ ├── sql_cache.rb │ │ └── active_support_cache.rb │ ├── logger.rb │ ├── model.rb │ ├── collection.rb │ └── configuration.rb ├── active_cached_resource.rb └── generators │ └── active_cached_resource │ ├── templates │ └── migration.erb │ └── active_record_generator.rb ├── bin ├── setup ├── console └── tapioca ├── spec ├── active_cached_resource │ ├── caching_strategies │ │ ├── active_support_cache_spec.rb │ │ ├── sql_cache_spec.rb │ │ └── base_spec.rb │ ├── logger_spec.rb │ └── configuration_spec.rb ├── spec_helper.rb └── support │ └── shared_examples │ └── caching_strategies_examples.rb ├── docker-compose.yml ├── .rubocop.yml ├── .github └── workflows │ ├── ci.yml │ ├── lint.yml │ ├── test.yml │ └── release.yml ├── Rakefile ├── Gemfile ├── LICENSE ├── scripts └── run_tests.sh ├── active_cached_resource.gemspec ├── README.md ├── Dockerfile └── CHANGELOG.md /example/consumer/db/seeds.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/consumer/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/consumer/tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/provider/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/provider/tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/consumer/lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/consumer/script/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/consumer/storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/consumer/tmp/cache/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/consumer/tmp/pids/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/consumer/vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/provider/lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/provider/script/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/provider/storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/provider/tmp/pids/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/provider/vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/consumer/tmp/storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/provider/tmp/storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/consumer/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/provider/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | JUNIT_REPORTS_PATH='tmp/junit_test_reports' -------------------------------------------------------------------------------- /example/consumer/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/provider/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | ruby_version: 3.2 2 | format: progress -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format progress 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /example/consumer/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /example/provider/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /sorbet/rbi/dsl/.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.rbi linguist-generated=true 2 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.rbi linguist-generated=true 2 | -------------------------------------------------------------------------------- /sorbet/rbi/annotations/.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.rbi linguist-vendored=true 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --exclude lib/activeresource/test 2 | --exclude lib/activeresource/examples -------------------------------------------------------------------------------- /example/consumer/bin/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | exec "./bin/rails", "server", *ARGV 3 | -------------------------------------------------------------------------------- /example/provider/bin/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | exec "./bin/rails", "server", *ARGV 3 | -------------------------------------------------------------------------------- /example/provider/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | resources :people 4 | end 5 | -------------------------------------------------------------------------------- /example/consumer/.kamal/hooks/docker-setup.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Docker set up on $KAMAL_HOSTS..." 4 | -------------------------------------------------------------------------------- /example/provider/.kamal/hooks/docker-setup.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Docker set up on $KAMAL_HOSTS..." 4 | -------------------------------------------------------------------------------- /example/provider/app/models/address.rb: -------------------------------------------------------------------------------- 1 | class Address < ApplicationRecord 2 | belongs_to :person 3 | end 4 | -------------------------------------------------------------------------------- /example/provider/app/models/company.rb: -------------------------------------------------------------------------------- 1 | class Company < ApplicationRecord 2 | belongs_to :person 3 | end 4 | -------------------------------------------------------------------------------- /lib/activeresource/lib/activeresource.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "active_resource" 4 | -------------------------------------------------------------------------------- /example/consumer/.kamal/hooks/post-proxy-reboot.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Rebooted kamal-proxy on $KAMAL_HOSTS" 4 | -------------------------------------------------------------------------------- /example/consumer/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::API 2 | end 3 | -------------------------------------------------------------------------------- /example/provider/.kamal/hooks/post-proxy-reboot.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Rebooted kamal-proxy on $KAMAL_HOSTS" 4 | -------------------------------------------------------------------------------- /example/provider/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::API 2 | end 3 | -------------------------------------------------------------------------------- /example/consumer/.kamal/hooks/pre-proxy-reboot.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Rebooting kamal-proxy on $KAMAL_HOSTS..." 4 | -------------------------------------------------------------------------------- /example/provider/.kamal/hooks/pre-proxy-reboot.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Rebooting kamal-proxy on $KAMAL_HOSTS..." 4 | -------------------------------------------------------------------------------- /example/consumer/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /example/provider/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /example/consumer/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /example/consumer/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /example/provider/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /example/provider/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /lib/active_cached_resource/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveCachedResource 4 | VERSION = "0.2.3" 5 | end 6 | -------------------------------------------------------------------------------- /sorbet/config: -------------------------------------------------------------------------------- 1 | --dir 2 | . 3 | --ignore=tmp/ 4 | --ignore=vendor/ 5 | --ignore=example 6 | --suppress-payload-superclass-redefinition-for=Reline::ANSI -------------------------------------------------------------------------------- /example/consumer/bin/thrust: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | load Gem.bin_path("thruster", "thrust") 6 | -------------------------------------------------------------------------------- /example/provider/bin/thrust: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | load Gem.bin_path("thruster", "thrust") 6 | -------------------------------------------------------------------------------- /example/consumer/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /example/consumer/bin/jobs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../config/environment" 4 | require "solid_queue/cli" 5 | 6 | SolidQueue::Cli.start(ARGV) 7 | -------------------------------------------------------------------------------- /example/provider/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /example/provider/bin/jobs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../config/environment" 4 | require "solid_queue/cli" 5 | 6 | SolidQueue::Cli.start(ARGV) 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /example/consumer/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /example/provider/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/post.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Post < ActiveResource::Base 4 | self.site = "http://37s.sunrise.i:3000" 5 | end 6 | -------------------------------------------------------------------------------- /example/consumer/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /example/provider/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/customer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Customer < ActiveResource::Base 4 | self.site = "http://37s.sunrise.i:3000" 5 | end 6 | -------------------------------------------------------------------------------- /example/consumer/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /example/provider/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/comment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Comment < ActiveResource::Base 4 | self.site = "http://37s.sunrise.i:3000/posts/:post_id" 5 | end 6 | -------------------------------------------------------------------------------- /example/consumer/bin/brakeman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | ARGV.unshift("--ensure-latest") 6 | 7 | load Gem.bin_path("brakeman", "brakeman") 8 | -------------------------------------------------------------------------------- /example/provider/bin/brakeman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | ARGV.unshift("--ensure-latest") 6 | 7 | load Gem.bin_path("brakeman", "brakeman") 8 | -------------------------------------------------------------------------------- /example/consumer/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /example/provider/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /lib/active_cached_resource/constants.rb: -------------------------------------------------------------------------------- 1 | module ActiveCachedResource 2 | module Constants 3 | GLOBAL_PREFIX = "acr" 4 | PREFIX_SEPARATOR = ":" 5 | RELOAD_PARAM = :_acr_reload 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/pet.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Pet < ActiveResource::Base 4 | self.site = "http://37s.sunrise.i:3000" 5 | self.prefix = "/people/:person_id/" 6 | end 7 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/proxy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ProxyResource < ActiveResource::Base 4 | self.site = "http://localhost" 5 | self.proxy = "http://user:password@proxy.local:3000" 6 | end 7 | -------------------------------------------------------------------------------- /example/provider/app/models/person.rb: -------------------------------------------------------------------------------- 1 | class Person < ApplicationRecord 2 | has_many :addresses, dependent: :destroy 3 | has_one :company, dependent: :destroy 4 | 5 | accepts_nested_attributes_for :addresses, :company 6 | end 7 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/street_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class StreetAddress < ActiveResource::Base 4 | self.site = "http://37s.sunrise.i:3000/people/:person_id" 5 | self.element_name = "address" 6 | end 7 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/subscription_plan.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SubscriptionPlan < ActiveResource::Base 4 | self.site = "http://37s.sunrise.i:3000" 5 | self.element_name = "plan" 6 | self.primary_key = :code 7 | end 8 | -------------------------------------------------------------------------------- /example/consumer/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /example/provider/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/sound.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asset 4 | class Sound < ActiveResource::Base 5 | self.site = "http://37s.sunrise.i:3000" 6 | end 7 | end 8 | 9 | # to test namespacing in a module 10 | class Author 11 | end 12 | -------------------------------------------------------------------------------- /spec/active_cached_resource/caching_strategies/active_support_cache_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ActiveCachedResource::CachingStrategies::ActiveSupportCache do 2 | let(:constructor_args) { [ActiveSupport::Cache::MemoryStore.new] } 3 | 4 | it_behaves_like "a caching strategy" 5 | end 6 | -------------------------------------------------------------------------------- /sorbet/tapioca/config.yml: -------------------------------------------------------------------------------- 1 | gem: 2 | # Add your `gem` command parameters here: 3 | # 4 | # exclude: 5 | # - gem_name 6 | # doc: true 7 | # workers: 5 8 | dsl: 9 | # Add your `dsl` command parameters here: 10 | # 11 | # exclude: 12 | # - SomeGeneratorName 13 | # workers: 5 14 | -------------------------------------------------------------------------------- /example/consumer/config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: sqlite3 3 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 4 | timeout: 5000 5 | database: storage/db.sqlite3 6 | 7 | development: 8 | <<: *default 9 | 10 | test: 11 | <<: *default 12 | 13 | production: 14 | <<: *default -------------------------------------------------------------------------------- /example/provider/db/migrate/20241202183937_create_people.rb: -------------------------------------------------------------------------------- 1 | class CreatePeople < ActiveRecord::Migration[8.0] 2 | def change 3 | create_table :people do |t| 4 | t.string :first_name 5 | t.string :last_name 6 | t.integer :age 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /example/consumer/bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | # explicit rubocop config increases performance slightly while avoiding config confusion. 6 | ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) 7 | 8 | load Gem.bin_path("rubocop", "rubocop") 9 | -------------------------------------------------------------------------------- /example/provider/bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | # explicit rubocop config increases performance slightly while avoiding config confusion. 6 | ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) 7 | 8 | load Gem.bin_path("rubocop", "rubocop") 9 | -------------------------------------------------------------------------------- /example/consumer/app/models/person.rb: -------------------------------------------------------------------------------- 1 | class Person < ActiveResource::Base 2 | cached_resource({cache_store: Rails.cache, cache_strategy: :active_support_cache}) 3 | 4 | self.site = "http://localhost:3000" 5 | self.include_format_in_path = false 6 | 7 | self.user = "admin" 8 | self.password = "secret" 9 | end 10 | -------------------------------------------------------------------------------- /example/consumer/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /example/consumer/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/provider/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /example/provider/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /spec/active_cached_resource/caching_strategies/sql_cache_spec.rb: -------------------------------------------------------------------------------- 1 | class CacheModel < ActiveRecord::Base 2 | self.table_name = "active_cached_resources" 3 | end 4 | 5 | RSpec.describe ActiveCachedResource::CachingStrategies::SQLCache do 6 | let(:constructor_args) { [CacheModel] } 7 | 8 | it_behaves_like "a caching strategy" 9 | end 10 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "active_cached_resource" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | require "irb" 11 | IRB.start(__FILE__) 12 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | services: 4 | app: 5 | container_name: acr 6 | build: 7 | context: . 8 | dockerfile: Dockerfile 9 | volumes: 10 | - .:/app 11 | test: 12 | build: 13 | context: . 14 | dockerfile: Dockerfile 15 | command: bash scripts/run_tests.sh 16 | volumes: 17 | - .:/app -------------------------------------------------------------------------------- /sorbet/rbi/gems/mail@2.8.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `mail` gem. 5 | # Please instead update this file by running `bin/tapioca gem mail`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/marcel@1.0.4.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `marcel` gem. 5 | # Please instead update this file by running `bin/tapioca gem marcel`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/nio4r@2.7.4.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `nio4r` gem. 5 | # Please instead update this file by running `bin/tapioca gem nio4r`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/rails@8.0.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `rails` gem. 5 | # Please instead update this file by running `bin/tapioca gem rails`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/globalid@1.2.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `globalid` gem. 5 | # Please instead update this file by running `bin/tapioca gem globalid`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/net-imap@0.5.2.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `net-imap` gem. 5 | # Please instead update this file by running `bin/tapioca gem net-imap`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/net-pop@0.1.2.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `net-pop` gem. 5 | # Please instead update this file by running `bin/tapioca gem net-pop`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/net-smtp@0.5.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `net-smtp` gem. 5 | # Please instead update this file by running `bin/tapioca gem net-smtp`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/stringio@3.1.2.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `stringio` gem. 5 | # Please instead update this file by running `bin/tapioca gem stringio`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/zeitwerk@2.7.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `zeitwerk` gem. 5 | # Please instead update this file by running `bin/tapioca gem zeitwerk`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /lib/activeresource/.gitignore: -------------------------------------------------------------------------------- 1 | # Don't put *.swp, *.bak, etc here; those belong in a global ~/.gitignore. 2 | # Check out http://help.github.com/ignore-files/ for how to set that up. 3 | 4 | debug.log 5 | .Gemfile 6 | /.bundle 7 | /.ruby-version 8 | *.lock 9 | /pkg 10 | /dist 11 | /doc/rdoc 12 | /*/doc 13 | /*/test/tmp 14 | /RDOC_MAIN.rdoc 15 | .rubocop-http* 16 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/mini_mime@1.1.5.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `mini_mime` gem. 5 | # Please instead update this file by running `bin/tapioca gem mini_mime`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/useragent@0.16.11.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `useragent` gem. 5 | # Please instead update this file by running `bin/tapioca gem useragent`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /example/consumer/config/recurring.yml: -------------------------------------------------------------------------------- 1 | # production: 2 | # periodic_cleanup: 3 | # class: CleanSoftDeletedRecordsJob 4 | # queue: background 5 | # args: [ 1000, { batch_size: 500 } ] 6 | # schedule: every hour 7 | # periodic_command: 8 | # command: "SoftDeletedRecord.due.delete_all" 9 | # priority: 2 10 | # schedule: at 5am every day 11 | -------------------------------------------------------------------------------- /example/provider/config/recurring.yml: -------------------------------------------------------------------------------- 1 | # production: 2 | # periodic_cleanup: 3 | # class: CleanSoftDeletedRecordsJob 4 | # queue: background 5 | # args: [ 1000, { batch_size: 500 } ] 6 | # schedule: every hour 7 | # periodic_command: 8 | # command: "SoftDeletedRecord.due.delete_all" 9 | # priority: 2 10 | # schedule: at 5am every day 11 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/actiontext@8.0.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `actiontext` gem. 5 | # Please instead update this file by running `bin/tapioca gem actiontext`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/io-console@0.8.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `io-console` gem. 5 | # Please instead update this file by running `bin/tapioca gem io-console`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/inventory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Inventory < ActiveResource::Base 4 | include ActiveResource::Singleton 5 | self.site = "http://37s.sunrise.i:3000" 6 | self.prefix = "/products/:product_id/" 7 | 8 | schema do 9 | integer :total 10 | integer :used 11 | 12 | string :status 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/actionmailbox@8.0.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `actionmailbox` gem. 5 | # Please instead update this file by running `bin/tapioca gem actionmailbox`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/actionmailer@8.0.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `actionmailer` gem. 5 | # Please instead update this file by running `bin/tapioca gem actionmailer`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/activestorage@8.0.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `activestorage` gem. 5 | # Please instead update this file by running `bin/tapioca gem activestorage`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/product.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Product < ActiveResource::Base 4 | self.site = "http://37s.sunrise.i:3000" 5 | # X-Inherited-Header is for testing that any subclasses 6 | # include the headers of this class 7 | self.headers["X-Inherited-Header"] = "present" 8 | end 9 | 10 | class SubProduct < Product 11 | end 12 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `ruby2_keywords` gem. 5 | # Please instead update this file by running `bin/tapioca gem ruby2_keywords`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/connection_pool@2.4.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `connection_pool` gem. 5 | # Please instead update this file by running `bin/tapioca gem connection_pool`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/standard-custom@1.0.2.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `standard-custom` gem. 5 | # Please instead update this file by running `bin/tapioca gem standard-custom`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/websocket-driver@0.7.6.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `websocket-driver` gem. 5 | # Please instead update this file by running `bin/tapioca gem websocket-driver`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/rubocop-performance@1.23.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `rubocop-performance` gem. 5 | # Please instead update this file by running `bin/tapioca gem rubocop-performance`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /example/provider/db/migrate/20241202183955_create_addresses.rb: -------------------------------------------------------------------------------- 1 | class CreateAddresses < ActiveRecord::Migration[8.0] 2 | def change 3 | create_table :addresses do |t| 4 | t.string :street 5 | t.string :city 6 | t.string :state 7 | t.string :zip 8 | t.references :person, null: false, foreign_key: true 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/standard-performance@1.6.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `standard-performance` gem. 5 | # Please instead update this file by running `bin/tapioca gem standard-performance`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/websocket-extensions@0.1.5.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `websocket-extensions` gem. 5 | # Please instead update this file by running `bin/tapioca gem websocket-extensions`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /example/consumer/config/cache.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | store_options: 3 | # Cap age of oldest cache entry to fulfill retention policies 4 | # max_age: <%= 60.days.to_i %> 5 | max_size: <%= 256.megabytes %> 6 | namespace: <%= Rails.env %> 7 | 8 | development: 9 | <<: *default 10 | 11 | test: 12 | <<: *default 13 | 14 | production: 15 | database: cache 16 | <<: *default 17 | -------------------------------------------------------------------------------- /example/consumer/config/queue.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | dispatchers: 3 | - polling_interval: 1 4 | batch_size: 500 5 | workers: 6 | - queues: "*" 7 | threads: 3 8 | processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %> 9 | polling_interval: 0.1 10 | 11 | development: 12 | <<: *default 13 | 14 | test: 15 | <<: *default 16 | 17 | production: 18 | <<: *default 19 | -------------------------------------------------------------------------------- /example/provider/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", "~> 8.0.0" 4 | gem "sqlite3", ">= 2.1" 5 | gem "puma", ">= 5.0" 6 | gem "tzinfo-data", platforms: %i[ windows jruby ] 7 | gem "solid_cache" 8 | gem "solid_queue" 9 | gem "kamal", require: false 10 | gem "thruster", require: false 11 | 12 | group :development, :test do 13 | gem "pry-byebug", "~> 3.10", ">= 3.10.1" 14 | end 15 | -------------------------------------------------------------------------------- /example/provider/config/cache.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | store_options: 3 | # Cap age of oldest cache entry to fulfill retention policies 4 | # max_age: <%= 60.days.to_i %> 5 | max_size: <%= 256.megabytes %> 6 | namespace: <%= Rails.env %> 7 | 8 | development: 9 | <<: *default 10 | 11 | test: 12 | <<: *default 13 | 14 | production: 15 | database: cache 16 | <<: *default 17 | -------------------------------------------------------------------------------- /example/provider/config/queue.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | dispatchers: 3 | - polling_interval: 1 4 | batch_size: 500 5 | workers: 6 | - queues: "*" 7 | threads: 3 8 | processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %> 9 | polling_interval: 0.1 10 | 11 | development: 12 | <<: *default 13 | 14 | test: 15 | <<: *default 16 | 17 | production: 18 | <<: *default 19 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/mocha@2.7.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `mocha` gem. 5 | # Please instead update this file by running `bin/tapioca gem mocha`. 6 | 7 | 8 | # source://mocha//lib/mocha/version.rb#1 9 | module Mocha; end 10 | 11 | # source://mocha//lib/mocha/version.rb#2 12 | Mocha::VERSION = T.let(T.unsafe(nil), String) 13 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `simplecov_json_formatter` gem. 5 | # Please instead update this file by running `bin/tapioca gem simplecov_json_formatter`. 6 | 7 | 8 | # THIS IS AN EMPTY RBI FILE. 9 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 10 | -------------------------------------------------------------------------------- /example/consumer/.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark any vendored files as having been vendored. 7 | vendor/* linguist-vendored 8 | config/credentials/*.yml.enc diff=rails_credentials 9 | config/credentials.yml.enc diff=rails_credentials 10 | -------------------------------------------------------------------------------- /example/provider/.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark any vendored files as having been vendored. 7 | vendor/* linguist-vendored 8 | config/credentials/*.yml.enc diff=rails_credentials 9 | config/credentials.yml.enc diff=rails_credentials 10 | -------------------------------------------------------------------------------- /example/consumer/.kamal/hooks/post-deploy.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A sample post-deploy hook 4 | # 5 | # These environment variables are available: 6 | # KAMAL_RECORDED_AT 7 | # KAMAL_PERFORMER 8 | # KAMAL_VERSION 9 | # KAMAL_HOSTS 10 | # KAMAL_ROLE (if set) 11 | # KAMAL_DESTINATION (if set) 12 | # KAMAL_RUNTIME 13 | 14 | echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" 15 | -------------------------------------------------------------------------------- /example/provider/.kamal/hooks/post-deploy.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A sample post-deploy hook 4 | # 5 | # These environment variables are available: 6 | # KAMAL_RECORDED_AT 7 | # KAMAL_PERFORMER 8 | # KAMAL_VERSION 9 | # KAMAL_HOSTS 10 | # KAMAL_ROLE (if set) 11 | # KAMAL_DESTINATION (if set) 12 | # KAMAL_RUNTIME 13 | 14 | echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" 15 | -------------------------------------------------------------------------------- /example/provider/db/migrate/20241202184017_create_companies.rb: -------------------------------------------------------------------------------- 1 | class CreateCompanies < ActiveRecord::Migration[8.0] 2 | def change 3 | create_table :companies do |t| 4 | t.string :name 5 | t.string :street 6 | t.string :city 7 | t.string :state 8 | t.string :zip 9 | t.references :person, null: false, foreign_key: true 10 | 11 | t.timestamps 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/beast.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class BeastResource < ActiveResource::Base 4 | self.site = "http://beast.caboo.se" 5 | site.user = "foo" 6 | site.password = "bar" 7 | end 8 | 9 | class Forum < BeastResource 10 | # taken from BeastResource 11 | # self.site = 'http://beast.caboo.se' 12 | end 13 | 14 | class Topic < BeastResource 15 | self.site += "/forums/:forum_id" 16 | end 17 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/person.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Person < ActiveResource::Base 4 | self.site = "http://37s.sunrise.i:3000" 5 | end 6 | 7 | module External 8 | class Person < ActiveResource::Base 9 | self.site = "http://atq.caffeine.intoxication.it" 10 | end 11 | 12 | class ProfileData < ActiveResource::Base 13 | self.site = "http://external.profile.data.nl" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/active_cached_resource.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "activeresource/lib/activeresource" 4 | 5 | require_relative "active_cached_resource/constants" 6 | require_relative "active_cached_resource/model" 7 | require_relative "active_cached_resource/version" 8 | 9 | module ActiveCachedResource 10 | end 11 | 12 | module ActiveResource 13 | class Base 14 | include ActiveCachedResource::Model 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/associations/builder/has_one.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveResource::Associations::Builder 4 | class HasOne < Association 5 | self.macro = :has_one 6 | 7 | def build 8 | validate_options 9 | model.create_reflection(self.class.macro, name, options).tap do |reflection| 10 | model.defines_has_one_finder_method(reflection) 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /example/consumer/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "active_cached_resource", path: "../../" 4 | gem "rails", "~> 8.0.0" 5 | gem "sqlite3", ">= 2.1" 6 | gem "puma", ">= 5.0" 7 | gem "tzinfo-data", platforms: %i[ windows jruby ] 8 | gem "solid_cache" 9 | gem "solid_queue" 10 | gem "kamal", require: false 11 | gem "thruster", require: false 12 | 13 | group :development, :test do 14 | gem "pry-byebug" 15 | gem "brakeman", require: false 16 | end 17 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/associations/builder/has_many.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveResource::Associations::Builder 4 | class HasMany < Association 5 | self.macro = :has_many 6 | 7 | def build 8 | validate_options 9 | model.create_reflection(self.class.macro, name, options).tap do |reflection| 10 | model.defines_has_many_finder_method(reflection) 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /example/consumer/bin/docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Enable jemalloc for reduced memory usage and latency. 4 | if [ -z "${LD_PRELOAD+x}" ]; then 5 | LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit) 6 | export LD_PRELOAD 7 | fi 8 | 9 | # If running the rails server then create or migrate existing database 10 | if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then 11 | ./bin/rails db:prepare 12 | fi 13 | 14 | exec "${@}" 15 | -------------------------------------------------------------------------------- /example/provider/bin/docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Enable jemalloc for reduced memory usage and latency. 4 | if [ -z "${LD_PRELOAD+x}" ]; then 5 | LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit) 6 | export LD_PRELOAD 7 | fi 8 | 9 | # If running the rails server then create or migrate existing database 10 | if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then 11 | ./bin/rails db:prepare 12 | fi 13 | 14 | exec "${@}" 15 | -------------------------------------------------------------------------------- /example/provider/db/seeds.rb: -------------------------------------------------------------------------------- 1 | person = Person.create!( 2 | first_name: "John", 3 | last_name: "Doe", 4 | age: 25 5 | ) 6 | 7 | person.addresses.create!([ 8 | { street: "123 Main St", city: "Springfield", state: "IL", zip: "62701" }, 9 | { street: "456 Elm St", city: "Springfield", state: "IL", zip: "62702" } 10 | ]) 11 | 12 | person.create_company!( 13 | name: "Acme", 14 | street: "789 Maple St", 15 | city: "Springfield", 16 | state: "IL", 17 | zip: "62703" 18 | ) -------------------------------------------------------------------------------- /example/provider/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem "sqlite3" 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | database: storage/db.sqlite3 12 | 13 | development: 14 | <<: *default 15 | 16 | test: 17 | <<: *default 18 | 19 | production: 20 | <<: *default 21 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_mode: 2 | merge: 3 | - Exclude 4 | 5 | require: 6 | - standard 7 | - standard-custom 8 | - standard-performance 9 | - rubocop-performance 10 | 11 | inherit_gem: 12 | standard: config/base.yml 13 | standard-performance: config/base.yml 14 | standard-custom: config/base.yml 15 | 16 | AllCops: 17 | SuggestExtensions: false 18 | TargetRubyVersion: 3.2.x 19 | NewCops: enable 20 | Exclude: 21 | - example/**/* 22 | - lib/activeresource/**/* 23 | - sorbet/**/* -------------------------------------------------------------------------------- /example/consumer/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html 3 | 4 | # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. 5 | # Can be used by load balancers and uptime monitors to verify that the app is live. 6 | get "up" => "rails/health#show", as: :rails_health_check 7 | 8 | # Defines the root path route ("/") 9 | # root "posts#index" 10 | end 11 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/associations/builder/belongs_to.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveResource::Associations::Builder 4 | class BelongsTo < Association 5 | self.valid_options += [:foreign_key] 6 | 7 | self.macro = :belongs_to 8 | 9 | def build 10 | validate_options 11 | reflection = model.create_reflection(self.class.macro, name, options) 12 | model.defines_belongs_to_finder_method(reflection) 13 | reflection 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | workflow_dispatch: 5 | workflow_call: 6 | concurrency: 7 | group: ${{ github.ref }}-${{ github.workflow }}-ci 8 | cancel-in-progress: true 9 | jobs: 10 | lint: 11 | permissions: 12 | checks: write 13 | contents: write 14 | pull-requests: write 15 | uses: ./.github/workflows/lint.yml 16 | test: 17 | permissions: 18 | contents: read 19 | checks: write 20 | pull-requests: write 21 | uses: ./.github/workflows/test.yml 22 | -------------------------------------------------------------------------------- /example/consumer/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc 8 | ] 9 | -------------------------------------------------------------------------------- /example/provider/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc 8 | ] 9 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/weather.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Weather < ActiveResource::Base 4 | include ActiveResource::Singleton 5 | self.site = "http://37s.sunrise.i:3000" 6 | 7 | schema do 8 | string :status 9 | string :temperature 10 | end 11 | end 12 | 13 | class WeatherDashboard < ActiveResource::Base 14 | include ActiveResource::Singleton 15 | self.site = "http://37s.sunrise.i:3000" 16 | self.singleton_name = "dashboard" 17 | 18 | schema do 19 | string :status 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /example/consumer/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | uu65VYomvmWVEWW6SSl5DSXMmMkwCOK5sZf1hhDv++aixw2P6H6FZGq4Z5qszMcnMPn1l2rn2ud/N0WUCozWiaqUG1pDXgI06lrQ6SwiINeIepICNjmJqWaNK9+P9I1SggMnbYFZ5GqYTmpZOtBtQRyX5HTqGuBATmWdcY0MJEkgJ65ShJZ7vBMDrzYNd3OtiFxnBGO4JrgnI+R9gp+lPzijz5FAD+Dmo+Ymq6q2lrNdxGhNLEya2v6muyeXNaXAXob7R0TlBuNG3E2KgVP9mnflJ1HnbgMCEWXRuV6Hr3rUVDjOxVYsmA0UefITb+LZmRQ8gyw7b1uMgIFVPAaChM95MA/I+VU5DL2McXl4KmOvuITV5KFUkKo5Z4W+d8dELS5jxnqJF27cRl+sDbFEtp0aWjYZZI8sREqf+4g15feJ10g5+Ip+03FXmAWjq1Nn0eqhaZWe0vbR9oeHlEtLxOjVXPhIlmXEQnjPrxzzgST2S9Tyszu47mZX--ZP7jipbGIUdfcjcS--4zIO4SwRee5gC86ZqZ8E5A== -------------------------------------------------------------------------------- /example/provider/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | oLf/r/U+4Xw/JBFp8Z985fyzuzpHSmoLclxhjnOu/qCZTH01VcdOSt2YJKyhADvjlMjrhkK/InDPs5AtY3KdGH4xe8WY06m1a6iclC4OPaoKgiNKeGB9XK+G4yec8SBIGE9VHWu6Thb3IT0HFnxVSncoZqgtbOd5kek3w6HQ1nE2hsqNALlt6349SfyIXDTB+7wum8uvkA9cy+YwZat/jTlXxY86mvTYmam4XB6GbeLX2464ZaQv2W+3B08nubVWtKqh+z9ZNTvUQPfy+6iycZVJDDIJVNLNViiuHwFNisUNJrwCr7rUKN+p72c/sWR5rzcSqAfFxhs7HfY90Y0yae6rD6XwT00yrxIz0o9Pz70aZqWpc6/ot5imdEAgOU3ctjPnl5WGV4/oXcfJr7L4Ofg3PaVOoI79klwKEzt4Y0g9i7ualSUIxIRtu6xwdZlhTj69ffEFrb9AGCTuFCNFzIcN45jLduEUxW42O5RD4yJySNLqH4U5R7P4--STxlRDIaEzWJamko--OR6vwoRNehHeH1+fFTHTpQ== -------------------------------------------------------------------------------- /sorbet/tapioca/require.rb: -------------------------------------------------------------------------------- 1 | # typed: true 2 | # frozen_string_literal: true 3 | 4 | require "active_job/serializers/object_serializer" 5 | require "active_support/all" 6 | require "active_support/concern" 7 | require "active_support/log_subscriber/test_helper" 8 | require "dotenv/load" 9 | require "minitest/reporters" 10 | require "rails/generators" 11 | require "rails/generators/active_record" 12 | require "rails/generators/channel/channel_generator" 13 | require "rails/generators/migration" 14 | require "rake/testtask" 15 | require "rspec/core/rake_task" 16 | require "rubocop/rake_task" 17 | -------------------------------------------------------------------------------- /example/consumer/config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Avoid CORS issues when API is called from the frontend app. 4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin Ajax requests. 5 | 6 | # Read more: https://github.com/cyu/rack-cors 7 | 8 | # Rails.application.config.middleware.insert_before 0, Rack::Cors do 9 | # allow do 10 | # origins "example.com" 11 | # 12 | # resource "*", 13 | # headers: :any, 14 | # methods: [:get, :post, :put, :patch, :delete, :options, :head] 15 | # end 16 | # end 17 | -------------------------------------------------------------------------------- /example/provider/config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Avoid CORS issues when API is called from the frontend app. 4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin Ajax requests. 5 | 6 | # Read more: https://github.com/cyu/rack-cors 7 | 8 | # Rails.application.config.middleware.insert_before 0, Rack::Cors do 9 | # allow do 10 | # origins "example.com" 11 | # 12 | # resource "*", 13 | # headers: :any, 14 | # methods: [:get, :post, :put, :patch, :delete, :options, :head] 15 | # end 16 | # end 17 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/formats/xml_format.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "active_support/core_ext/hash/conversions" 4 | 5 | module ActiveResource 6 | module Formats 7 | module XmlFormat 8 | extend self 9 | 10 | def extension 11 | "xml" 12 | end 13 | 14 | def mime_type 15 | "application/xml" 16 | end 17 | 18 | def encode(hash, options = {}) 19 | hash.to_xml(options) 20 | end 21 | 22 | def decode(xml) 23 | Formats.remove_root(Hash.from_xml(xml)) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # turns everything into the same object 4 | class AddressXMLFormatter 5 | include ActiveResource::Formats::XmlFormat 6 | 7 | def decode(xml) 8 | data = ActiveResource::Formats::XmlFormat.decode(xml) 9 | # process address fields 10 | data.each do |address| 11 | address["city_state"] = "#{address['city']}, #{address['state']}" 12 | end 13 | data 14 | end 15 | end 16 | 17 | class AddressResource < ActiveResource::Base 18 | self.element_name = "address" 19 | self.format = AddressXMLFormatter.new 20 | end 21 | -------------------------------------------------------------------------------- /sorbet/rbi/dsl/active_support/callbacks.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for dynamic methods in `ActiveSupport::Callbacks`. 5 | # Please instead update this file by running `bin/tapioca dsl ActiveSupport::Callbacks`. 6 | 7 | 8 | module ActiveSupport::Callbacks 9 | include GeneratedInstanceMethods 10 | 11 | mixes_in_class_methods GeneratedClassMethods 12 | 13 | module GeneratedClassMethods 14 | def __callbacks; end 15 | def __callbacks=(value); end 16 | end 17 | 18 | module GeneratedInstanceMethods 19 | def __callbacks; end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/generators/active_cached_resource/templates/migration.erb: -------------------------------------------------------------------------------- 1 | class CreateActiveCachedResourceTable < ActiveRecord::Migration<%= migration_version %> 2 | def up 3 | create_table :active_cached_resources, id: false do |t| 4 | t.binary :key, limit: 512, null: false 5 | t.binary :value, null: false 6 | t.datetime :expires_at, null: false 7 | 8 | t.index [:key, :expires_at], unique: true, name: "index_active_cached_resources_on_key_and_expires_at" 9 | t.index :key, name: "index_active_cached_resources_on_key" 10 | end 11 | end 12 | 13 | def down 14 | drop_table :active_cached_resources 15 | end 16 | end -------------------------------------------------------------------------------- /example/consumer/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | require "active_model/railtie" 5 | require "active_job/railtie" 6 | require "active_record/railtie" 7 | require "action_controller/railtie" 8 | require "action_mailer/railtie" 9 | require "action_view/railtie" 10 | Bundler.require(*Rails.groups) 11 | 12 | module Consumer 13 | class Application < Rails::Application 14 | config.load_defaults 8.0 15 | config.autoload_lib(ignore: %w[assets tasks]) 16 | config.api_only = true 17 | 18 | config.cache_store = :file_store, Rails.root.join("tmp", "storage"), { expires_in: 1.day } 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/activeresource/test/cases/inheritence_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "abstract_unit" 4 | 5 | require "fixtures/product" 6 | 7 | class InheritenceTest < ActiveSupport::TestCase 8 | def test_sub_class_retains_ancestor_headers 9 | ActiveResource::HttpMock.respond_to do |mock| 10 | mock.get "/sub_products/1.json", 11 | { "Accept" => "application/json", "X-Inherited-Header" => "present" }, 12 | { id: 1, name: "Sub Product" }.to_json, 13 | 200 14 | end 15 | 16 | sub_product = SubProduct.find(1) 17 | assert_equal "SubProduct", sub_product.class.to_s 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/formats/json_format.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "active_support/json" 4 | 5 | module ActiveResource 6 | module Formats 7 | module JsonFormat 8 | extend self 9 | 10 | def extension 11 | "json" 12 | end 13 | 14 | def mime_type 15 | "application/json" 16 | end 17 | 18 | def encode(hash, options = nil) 19 | ActiveSupport::JSON.encode(hash, options) 20 | end 21 | 22 | def decode(json) 23 | return nil if json.nil? 24 | Formats.remove_root(ActiveSupport::JSON.decode(json)) 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/project.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # used to test validations 4 | class Project < ActiveResource::Base 5 | self.site = "http://37s.sunrise.i:3000" 6 | schema do 7 | string :email 8 | string :name 9 | end 10 | 11 | validates :name, presence: true 12 | validates :description, presence: false, length: { maximum: 10 } 13 | validate :description_greater_than_three_letters 14 | 15 | # to test the validate *callback* works 16 | def description_greater_than_three_letters 17 | errors.add :description, "must be greater than three letters long" if description.length < 3 unless description.blank? 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/activeresource/test/setter_trap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SetterTrap < BasicObject 4 | class << self 5 | def rollback_sets(obj) 6 | trapped = new(obj) 7 | yield(trapped).tap { trapped.rollback_sets } 8 | end 9 | end 10 | 11 | def initialize(obj) 12 | @cache = {} 13 | @obj = obj 14 | end 15 | 16 | def respond_to?(method) 17 | @obj.respond_to?(method) 18 | end 19 | 20 | def method_missing(method, *args, &proc) 21 | @cache[method] ||= @obj.send($`) if method.to_s =~ /=$/ 22 | @obj.send method, *args, &proc 23 | end 24 | 25 | def rollback_sets 26 | @cache.each { |k, v| @obj.send k, v } 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /example/consumer/db/cache_schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ActiveRecord::Schema[7.2].define(version: 1) do 4 | create_table "solid_cache_entries", force: :cascade do |t| 5 | t.binary "key", limit: 1024, null: false 6 | t.binary "value", limit: 536870912, null: false 7 | t.datetime "created_at", null: false 8 | t.integer "key_hash", limit: 8, null: false 9 | t.integer "byte_size", limit: 4, null: false 10 | t.index ["byte_size"], name: "index_solid_cache_entries_on_byte_size" 11 | t.index ["key_hash", "byte_size"], name: "index_solid_cache_entries_on_key_hash_and_byte_size" 12 | t.index ["key_hash"], name: "index_solid_cache_entries_on_key_hash", unique: true 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /example/provider/db/cache_schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ActiveRecord::Schema[7.2].define(version: 1) do 4 | create_table "solid_cache_entries", force: :cascade do |t| 5 | t.binary "key", limit: 1024, null: false 6 | t.binary "value", limit: 536870912, null: false 7 | t.datetime "created_at", null: false 8 | t.integer "key_hash", limit: 8, null: false 9 | t.integer "byte_size", limit: 4, null: false 10 | t.index ["byte_size"], name: "index_solid_cache_entries_on_byte_size" 11 | t.index ["key_hash", "byte_size"], name: "index_solid_cache_entries_on_key_hash_and_byte_size" 12 | t.index ["key_hash"], name: "index_solid_cache_entries_on_key_hash", unique: true 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/callbacks.rb: -------------------------------------------------------------------------------- 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 23 | -------------------------------------------------------------------------------- /example/consumer/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /example/provider/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/active_job_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveResource 4 | class ActiveJobSerializer < ActiveJob::Serializers::ObjectSerializer 5 | def serialize(resource) 6 | super( 7 | "class" => resource.class.name, 8 | "persisted" => resource.persisted?, 9 | "prefix_options" => resource.prefix_options.as_json, 10 | "attributes" => resource.attributes.as_json 11 | ) 12 | end 13 | 14 | def deserialize(hash) 15 | hash["class"].constantize.new(hash["attributes"]).tap do |resource| 16 | resource.persisted = hash["persisted"] 17 | resource.prefix_options = hash["prefix_options"] 18 | end 19 | end 20 | 21 | private 22 | def klass 23 | ActiveResource::Base 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/activeresource/test/cases/inheriting_hash_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class InheritingHashTest < ActiveSupport::TestCase 4 | def setup 5 | @parent = ActiveResource::InheritingHash.new({ override_me: "foo", parent_key: "parent_value" }) 6 | @child = ActiveResource::InheritingHash.new(@parent) 7 | @child[:override_me] = "bar" 8 | @child[:child_only] = "baz" 9 | end 10 | 11 | def test_child_key_overrides_parent_key 12 | assert_equal "bar", @child[:override_me] 13 | end 14 | 15 | def test_parent_key_available_on_lookup 16 | assert_equal "parent_value", @child[:parent_key] 17 | end 18 | 19 | def test_conversion_to_regular_hash_includes_parent_keys 20 | hash = @child.to_hash 21 | 22 | assert_equal 3, hash.keys.length 23 | assert_equal "parent_value", hash[:parent_key] 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /bin/tapioca: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'tapioca' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("tapioca", "tapioca") 28 | -------------------------------------------------------------------------------- /example/consumer/bin/kamal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'kamal' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("kamal", "kamal") 28 | -------------------------------------------------------------------------------- /example/provider/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # Temporary files generated by your text editor or operating system 4 | # belong in git's global ignore instead: 5 | # `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore` 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files. 11 | /.env* 12 | 13 | # Ignore all logfiles and tempfiles. 14 | /log/* 15 | /tmp/* 16 | !/log/.keep 17 | !/tmp/.keep 18 | 19 | # Ignore pidfiles, but keep the directory. 20 | /tmp/pids/* 21 | !/tmp/pids/ 22 | !/tmp/pids/.keep 23 | 24 | # Ignore storage (uploaded files in development and any SQLite databases). 25 | /storage/* 26 | !/storage/.keep 27 | /tmp/storage/* 28 | !/tmp/storage/ 29 | !/tmp/storage/.keep 30 | 31 | # Ignore master key for decrypting credentials and more. 32 | /config/master.key 33 | -------------------------------------------------------------------------------- /example/provider/bin/kamal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'kamal' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("kamal", "kamal") 28 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/railtie.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../active_resource" 4 | require "rails" 5 | 6 | module ActiveResource 7 | class Railtie < Rails::Railtie 8 | config.active_resource = ActiveSupport::OrderedOptions.new 9 | 10 | initializer "active_resource.set_configs" do |app| 11 | ActiveSupport.on_load(:active_resource) do 12 | app.config.active_resource.each do |k, v| 13 | send "#{k}=", v 14 | end 15 | end 16 | end 17 | 18 | initializer "active_resource.add_active_job_serializer" do |app| 19 | if app.config.try(:active_job).try(:custom_serializers) 20 | require_relative "../active_resource/active_job_serializer" 21 | app.config.active_job.custom_serializers << ActiveResource::ActiveJobSerializer 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/inheriting_hash.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveResource 4 | class InheritingHash < Hash 5 | def initialize(parent_hash = {}) 6 | # Default hash value must be nil, which allows fallback lookup on parent hash 7 | super(nil) 8 | @parent_hash = parent_hash 9 | end 10 | 11 | def [](key) 12 | super || @parent_hash[key] 13 | end 14 | 15 | # Merges the flattened parent hash (if it's an InheritingHash) 16 | # with ourself 17 | def to_hash 18 | @parent_hash.to_hash.merge(self) 19 | end 20 | 21 | # So we can see the merged object in IRB or the Rails console 22 | def pretty_print(pp) 23 | pp.pp_hash to_hash 24 | end 25 | 26 | def inspect 27 | to_hash.inspect 28 | end 29 | 30 | def to_s 31 | inspect 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/formats.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveResource 4 | module Formats 5 | autoload :XmlFormat, "#{__dir__}/formats/xml_format.rb" 6 | autoload :JsonFormat, "#{__dir__}/formats/json_format.rb" 7 | 8 | # Lookup the format class from a mime type reference symbol. Example: 9 | # 10 | # ActiveResource::Formats[:xml] # => ActiveResource::Formats::XmlFormat 11 | # ActiveResource::Formats[:json] # => ActiveResource::Formats::JsonFormat 12 | def self.[](mime_type_reference) 13 | ActiveResource::Formats.const_get(ActiveSupport::Inflector.camelize(mime_type_reference.to_s) + "Format") 14 | end 15 | 16 | def self.remove_root(data) 17 | if data.is_a?(Hash) && data.keys.size == 1 && data.values.first.is_a?(Enumerable) 18 | data.values.first 19 | else 20 | data 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /example/consumer/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | # Ignore git directory. 4 | /.git/ 5 | /.gitignore 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files. 11 | /.env* 12 | 13 | # Ignore all default key files. 14 | /config/master.key 15 | /config/credentials/*.key 16 | 17 | # Ignore all logfiles and tempfiles. 18 | /log/* 19 | /tmp/* 20 | !/log/.keep 21 | !/tmp/.keep 22 | 23 | # Ignore pidfiles, but keep the directory. 24 | /tmp/pids/* 25 | !/tmp/pids/.keep 26 | 27 | # Ignore storage (uploaded files in development and any SQLite databases). 28 | /storage/* 29 | !/storage/.keep 30 | /tmp/storage/* 31 | !/tmp/storage/.keep 32 | 33 | # Ignore CI service files. 34 | /.github 35 | 36 | # Ignore development files 37 | /.devcontainer 38 | 39 | # Ignore Docker-related files 40 | /.dockerignore 41 | /Dockerfile* 42 | -------------------------------------------------------------------------------- /example/provider/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | # Ignore git directory. 4 | /.git/ 5 | /.gitignore 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files. 11 | /.env* 12 | 13 | # Ignore all default key files. 14 | /config/master.key 15 | /config/credentials/*.key 16 | 17 | # Ignore all logfiles and tempfiles. 18 | /log/* 19 | /tmp/* 20 | !/log/.keep 21 | !/tmp/.keep 22 | 23 | # Ignore pidfiles, but keep the directory. 24 | /tmp/pids/* 25 | !/tmp/pids/.keep 26 | 27 | # Ignore storage (uploaded files in development and any SQLite databases). 28 | /storage/* 29 | !/storage/.keep 30 | /tmp/storage/* 31 | !/tmp/storage/.keep 32 | 33 | # Ignore CI service files. 34 | /.github 35 | 36 | # Ignore development files 37 | /.devcontainer 38 | 39 | # Ignore Docker-related files 40 | /.dockerignore 41 | /Dockerfile* 42 | -------------------------------------------------------------------------------- /example/consumer/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # Temporary files generated by your text editor or operating system 4 | # belong in git's global ignore instead: 5 | # `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore` 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files. 11 | /.env* 12 | 13 | # Ignore all logfiles and tempfiles. 14 | /log/* 15 | /tmp/* 16 | !/log/.keep 17 | !/tmp/.keep 18 | 19 | # Ignore pidfiles, but keep the directory. 20 | /tmp/pids/* 21 | !/tmp/pids/ 22 | !/tmp/pids/.keep 23 | 24 | # Ignore storage (uploaded files in development and any SQLite databases). 25 | /storage/* 26 | !/storage/.keep 27 | /tmp/storage/* 28 | !/tmp/storage/ 29 | !/tmp/storage/.keep 30 | 31 | /tmp/cache/* 32 | !/tmp/cache/ 33 | !/tmp/cache/.keep 34 | 35 | # Ignore master key for decrypting credentials and more. 36 | /config/master.key 37 | -------------------------------------------------------------------------------- /spec/active_cached_resource/logger_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "stringio" 3 | 4 | RSpec.describe ActiveCachedResource::Logger do 5 | let(:model_name) { "TestModel" } 6 | let(:output) { StringIO.new } 7 | let(:logger) { described_class.new(model_name) } 8 | 9 | before do 10 | allow($stdout).to receive(:write).and_wrap_original do |original, message| 11 | output.write(message) 12 | end 13 | end 14 | 15 | described_class::COLORS.excluding(:reset).each do |severity, color_code| 16 | it "logs debug messages with color" do 17 | logger.public_send(severity, "This is a message") 18 | output.rewind 19 | log_output = output.read 20 | 21 | expect(log_output).to include(color_code) 22 | expect(log_output).to include("#{severity.upcase} [CACHE][ACR][#{model_name}] This is a message") 23 | expect(log_output).to include(described_class::COLORS[:reset]) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/activeresource/test/cases/associations/builder/has_one_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "abstract_unit" 4 | 5 | require "fixtures/product" 6 | require "fixtures/inventory" 7 | 8 | class ActiveResource::Associations::Builder::HasOneTest < ActiveSupport::TestCase 9 | def setup 10 | @klass = ActiveResource::Associations::Builder::HasOne 11 | end 12 | 13 | def test_validations_for_instance 14 | object = @klass.new(Product, :inventory, {}) 15 | assert_equal({}, object.send(:validate_options)) 16 | end 17 | 18 | def test_instance_build 19 | object = @klass.new(Product, :inventory, {}) 20 | Product.expects(:defines_has_one_finder_method).with(kind_of(ActiveResource::Reflection::AssociationReflection)) 21 | 22 | reflection = object.build 23 | 24 | assert_kind_of ActiveResource::Reflection::AssociationReflection, reflection 25 | assert_equal :inventory, reflection.name 26 | assert_equal Inventory, reflection.klass 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler" 4 | require "bundler/gem_tasks" 5 | 6 | if (Bundler.definition.groups - Bundler.settings[:without] + Bundler.settings[:with]).include?(:test) 7 | require "rake/testtask" 8 | require "rspec/core/rake_task" 9 | require "rubocop/rake_task" 10 | 11 | RSpec::Core::RakeTask.new(:spec) do |r| 12 | r.rspec_opts = ["--no-color"] 13 | r.verbose = false 14 | end 15 | 16 | RuboCop::RakeTask.new(:rubocop) do |r| 17 | r.options = ["--no-color", "--format", "simple"] 18 | r.verbose = false 19 | end 20 | 21 | # ActiveResource tests 22 | Rake::TestTask.new(:active_resource_test) do |t| 23 | t.libs = ["lib/activeresource/test"] 24 | t.pattern = "lib/activeresource/test/**/*_test.rb" 25 | t.warning = false 26 | t.verbose = false 27 | end 28 | 29 | desc "Type check" 30 | task :tc do 31 | sh "bundle exec srb tc" 32 | end 33 | 34 | task default: [:tc, :rubocop, :spec, :active_resource_test] 35 | end 36 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/associations/builder/association.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveResource::Associations::Builder 4 | class Association # :nodoc: 5 | # providing a Class-Variable, which will have a different store of subclasses 6 | class_attribute :valid_options 7 | self.valid_options = [:class_name, :foreign_key] 8 | 9 | # would identify subclasses of association 10 | class_attribute :macro 11 | 12 | attr_reader :model, :name, :options, :klass 13 | 14 | def self.build(model, name, options) 15 | new(model, name, options).build 16 | end 17 | 18 | def initialize(model, name, options) 19 | @model, @name, @options = model, name, options 20 | end 21 | 22 | def build 23 | validate_options 24 | model.create_reflection(self.class.macro, name, options) 25 | end 26 | 27 | private 28 | def validate_options 29 | options.assert_valid_keys(self.class.valid_options) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/activeresource/test/cases/associations/builder/has_many_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "abstract_unit" 4 | 5 | require "fixtures/person" 6 | require "fixtures/street_address" 7 | 8 | class ActiveResource::Associations::Builder::HasManyTest < ActiveSupport::TestCase 9 | def setup 10 | @klass = ActiveResource::Associations::Builder::HasMany 11 | end 12 | 13 | def test_validations_for_instance 14 | object = @klass.new(Person, :street_address, {}) 15 | assert_equal({}, object.send(:validate_options)) 16 | end 17 | 18 | def test_instance_build 19 | object = @klass.new(Person, :street_address, {}) 20 | Person.expects(:defines_has_many_finder_method).with(kind_of(ActiveResource::Reflection::AssociationReflection)) 21 | 22 | reflection = object.build 23 | 24 | assert_kind_of ActiveResource::Reflection::AssociationReflection, reflection 25 | assert_equal :street_address, reflection.name 26 | assert_equal StreetAddress, reflection.klass 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/generators/active_cached_resource/active_record_generator.rb: -------------------------------------------------------------------------------- 1 | require "rails/generators/active_record" 2 | 3 | module ActiveCachedResource 4 | module Generators 5 | class ActiveRecordGenerator < ::Rails::Generators::Base 6 | include ::Rails::Generators::Migration 7 | desc "Generates migration for cache tables" 8 | 9 | source_paths << File.join(File.dirname(__FILE__), "templates") 10 | 11 | def self.next_migration_number(dirname) 12 | ::ActiveRecord::Generators::Base.next_migration_number(dirname) 13 | end 14 | 15 | def self.migration_version 16 | "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" 17 | end 18 | 19 | def create_migration_file 20 | options = { 21 | migration_version: migration_version 22 | } 23 | migration_template "migration.erb", "db/migrate/create_active_cached_resource_table.rb", options 24 | end 25 | 26 | def migration_version 27 | self.class.migration_version 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "dotenv", "~> 3.1", ">= 3.1.7" 6 | gem "rake", "~> 13.2", ">= 13.2.1" 7 | 8 | group :versioning do 9 | gem "bump", "~> 0.10.0" 10 | end 11 | 12 | group :development, :test do 13 | gem "activejob", ">= 6.0" 14 | gem "activerecord", ">= 6.0" 15 | gem "pry-byebug", "~> 3.10", ">= 3.10.1" 16 | gem "rails", ">= 6.0" 17 | gem "rexml", "~> 3.4" 18 | gem "rubocop-md", "~> 1.2", ">= 1.2.4" 19 | gem "rubocop-minitest", "~> 0.36.0" 20 | gem "rubocop-packaging", "~> 0.5.2" 21 | gem "rubocop-rails", "~> 2.27" 22 | gem "sorbet-static-and-runtime", "~> 0.5.11609", require: false 23 | gem "sqlite3", "~> 2.3", ">= 2.3.1" 24 | gem "standard", "~> 1.40" 25 | gem "tapioca", "~> 0.16.3", require: false 26 | end 27 | 28 | group :test do 29 | gem "minitest-reporters", "~> 1.7", ">= 1.7.1" 30 | gem "mocha", ">= 0.13.0" 31 | gem "rspec_junit_formatter", "~> 0.6.0" 32 | gem "rspec", "~> 3.13" 33 | gem "simplecov", "~> 0.22.0", require: false 34 | end 35 | 36 | gemspec 37 | -------------------------------------------------------------------------------- /example/consumer/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization and 2 | # are automatically loaded by Rails. If you want to use locales other than 3 | # English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more about the API, please read the Rails Internationalization guide 20 | # at https://guides.rubyonrails.org/i18n.html. 21 | # 22 | # Be aware that YAML interprets the following case-insensitive strings as 23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings 24 | # must be quoted to be interpreted as strings. For example: 25 | # 26 | # en: 27 | # "yes": yup 28 | # enabled: "ON" 29 | 30 | en: 31 | hello: "Hello world" 32 | -------------------------------------------------------------------------------- /example/provider/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization and 2 | # are automatically loaded by Rails. If you want to use locales other than 3 | # English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more about the API, please read the Rails Internationalization guide 20 | # at https://guides.rubyonrails.org/i18n.html. 21 | # 22 | # Be aware that YAML interprets the following case-insensitive strings as 23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings 24 | # must be quoted to be interpreted as strings. For example: 25 | # 26 | # en: 27 | # "yes": yup 28 | # enabled: "ON" 29 | 30 | en: 31 | hello: "Hello world" 32 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/log_subscriber.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveResource 4 | class LogSubscriber < ActiveSupport::LogSubscriber 5 | def request(event) 6 | result = event.payload[:result] 7 | 8 | # When result is nil, the connection could not even be initiated 9 | # with the server, so we log an internal synthetic error response (523). 10 | code = result.try(:code) || 523 # matches CloudFlare's convention 11 | message = result.try(:message) || "ActiveResource connection error" 12 | body = result.try(:body) || "" 13 | 14 | log_level_method = code.to_i < 400 ? :info : :error 15 | 16 | send log_level_method, "#{event.payload[:method].to_s.upcase} #{event.payload[:request_uri]}" 17 | send log_level_method, "--> %d %s %d (%.1fms)" % [code, message, body.to_s.length, event.duration] 18 | end 19 | 20 | def logger 21 | ActiveResource::Base.logger 22 | end 23 | end 24 | end 25 | 26 | ActiveResource::LogSubscriber.attach_to :active_resource 27 | -------------------------------------------------------------------------------- /example/consumer/.kamal/secrets: -------------------------------------------------------------------------------- 1 | # Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, 2 | # and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either 3 | # password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. 4 | 5 | # Example of extracting secrets from 1password (or another compatible pw manager) 6 | # SECRETS=$(kamal secrets fetch --adapter 1password --account your-account --from Vault/Item KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY) 7 | # KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS}) 8 | # RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY ${SECRETS}) 9 | 10 | # Use a GITHUB_TOKEN if private repositories are needed for the image 11 | # GITHUB_TOKEN=$(gh config get -h github.com oauth_token) 12 | 13 | # Grab the registry password from ENV 14 | KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD 15 | 16 | # Improve security by using a password manager. Never check config/master.key into git! 17 | RAILS_MASTER_KEY=$(cat config/master.key) 18 | -------------------------------------------------------------------------------- /example/provider/.kamal/secrets: -------------------------------------------------------------------------------- 1 | # Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, 2 | # and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either 3 | # password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. 4 | 5 | # Example of extracting secrets from 1password (or another compatible pw manager) 6 | # SECRETS=$(kamal secrets fetch --adapter 1password --account your-account --from Vault/Item KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY) 7 | # KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS}) 8 | # RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY ${SECRETS}) 9 | 10 | # Use a GITHUB_TOKEN if private repositories are needed for the image 11 | # GITHUB_TOKEN=$(gh config get -h github.com oauth_token) 12 | 13 | # Grab the registry password from ENV 14 | KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD 15 | 16 | # Improve security by using a password manager. Never check config/master.key into git! 17 | RAILS_MASTER_KEY=$(cat config/master.key) 18 | -------------------------------------------------------------------------------- /lib/active_cached_resource/caching_strategies/sql_cache.rb: -------------------------------------------------------------------------------- 1 | require_relative "base" 2 | 3 | module ActiveCachedResource 4 | module CachingStrategies 5 | class SQLCache < Base 6 | def initialize(model, options = {}) 7 | super() 8 | @model = model 9 | @batch_clear_size = options.fetch(:batch_clear_size, 1000) 10 | end 11 | 12 | protected 13 | 14 | def delete_raw(key) 15 | @model.where(key: key).delete_all 16 | end 17 | 18 | def read_raw(key) 19 | record = @model.where(key: key).where(@model.arel_table[:expires_at].gt(Time.current)).first 20 | record&.value 21 | end 22 | 23 | def write_raw(key, value, options = {}) 24 | expires_at = Time.current + options.fetch(:expires_in) 25 | 26 | @model.create({key: key, value: value, expires_at: expires_at}) 27 | end 28 | 29 | def clear_raw(pattern) 30 | @model.where(@model.arel_table[:key].matches("#{pattern}%")).in_batches(of: @batch_clear_size) do |batch| 31 | batch.delete_all 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2024] [Jean Luis Urena] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bash 2 | 3 | set -e 4 | 5 | export BUNDLE_SILENCE_ROOT_WARNING=1 6 | export BUNDLE_IGNORE_MESSAGES=1 7 | 8 | . "$ASDF_DIR/asdf.sh" 9 | SPEC_RESULTS="coverage/spec_results" 10 | mkdir -p "$SPEC_RESULTS" 11 | 12 | # Ruby versions to test 13 | LINTER_RUBY_VERSION=$(asdf latest ruby 3.2) 14 | RUBY_VERSIONS=$(asdf list ruby) 15 | # Maximum number of concurrent processes 16 | MAX_PARALLEL=3 17 | CURRENT_PARALLEL=0 18 | 19 | run_rspec() { 20 | local ruby_version="$1" 21 | 22 | printf "\n********* Testing with Ruby $ruby_version *********\n\n" 23 | 24 | bundle install --quiet --no-cache 25 | # Run tests 26 | bundle exec rake 27 | } 28 | 29 | # Iterate over Ruby versions 30 | for RUBY_VERSION in $RUBY_VERSIONS; do 31 | ( 32 | asdf reshim ruby $RUBY_VERSION 33 | asdf shell ruby $RUBY_VERSION 34 | 35 | run_rspec "$RUBY_VERSION" "$RAILS_VERSION" 36 | ) & 37 | 38 | CURRENT_PARALLEL=$((CURRENT_PARALLEL + 1)) 39 | while [ $CURRENT_PARALLEL -ge $MAX_PARALLEL ]; do 40 | sleep 1 41 | CURRENT_PARALLEL=$(jobs -r | wc -l) 42 | done 43 | done 44 | 45 | wait 46 | 47 | printf "\n********* DONE *********\n" -------------------------------------------------------------------------------- /example/consumer/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | APP_ROOT = File.expand_path("..", __dir__) 5 | 6 | def system!(*args) 7 | system(*args, exception: true) 8 | end 9 | 10 | FileUtils.chdir APP_ROOT do 11 | # This script is a way to set up or update your development environment automatically. 12 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 13 | # Add necessary setup steps to this file. 14 | 15 | puts "== Installing dependencies ==" 16 | system("bundle check") || system!("bundle install") 17 | 18 | # puts "\n== Copying sample files ==" 19 | # unless File.exist?("config/database.yml") 20 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 21 | # end 22 | 23 | puts "\n== Preparing database ==" 24 | system! "bin/rails db:prepare" 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! "bin/rails log:clear tmp:clear" 28 | 29 | unless ARGV.include?("--skip-server") 30 | puts "\n== Starting development server ==" 31 | STDOUT.flush # flush the output before exec(2) so that it displays 32 | exec "bin/dev" 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /example/provider/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | APP_ROOT = File.expand_path("..", __dir__) 5 | 6 | def system!(*args) 7 | system(*args, exception: true) 8 | end 9 | 10 | FileUtils.chdir APP_ROOT do 11 | # This script is a way to set up or update your development environment automatically. 12 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 13 | # Add necessary setup steps to this file. 14 | 15 | puts "== Installing dependencies ==" 16 | system("bundle check") || system!("bundle install") 17 | 18 | # puts "\n== Copying sample files ==" 19 | # unless File.exist?("config/database.yml") 20 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 21 | # end 22 | 23 | puts "\n== Preparing database ==" 24 | system! "bin/rails db:prepare" 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! "bin/rails log:clear tmp:clear" 28 | 29 | unless ARGV.include?("--skip-server") 30 | puts "\n== Starting development server ==" 31 | STDOUT.flush # flush the output before exec(2) so that it displays 32 | exec "bin/dev" 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/activeresource/test/fixtures/fixtures.rbi: -------------------------------------------------------------------------------- 1 | # typed: strong 2 | 3 | class Customer 4 | class Friend 5 | class Brother 6 | class Child; end 7 | end 8 | end 9 | end 10 | 11 | class Person 12 | class Books 13 | class UnnamedResource; end 14 | end 15 | 16 | class Address; end 17 | class Street 18 | class State 19 | class NotableRiver; end 20 | end 21 | end 22 | end 23 | 24 | # Defining specific instances based on the structure 25 | class Luis < Customer; end 26 | 27 | class JK < Customer::Friend; end 28 | 29 | class Mateo < Customer::Friend::Brother; end 30 | class Felipe < Customer::Friend::Brother; end 31 | 32 | class Edith < Customer::Friend::Brother::Child; end 33 | class Martha < Customer::Friend::Brother::Child; end 34 | 35 | class Bryan < Customer::Friend::Brother::Child; end 36 | class Luke < Customer::Friend::Brother::Child; end 37 | 38 | class Eduardo < Customer::Friend; end 39 | 40 | class Sebas < Customer::Friend::Brother; end 41 | class Elsa < Customer::Friend::Brother; end 42 | class Milena < Customer::Friend::Brother; end 43 | 44 | class Andres < Customer::Friend::Brother::Child; end 45 | class Jorge < Customer::Friend::Brother::Child; end 46 | class Natacha < Customer::Friend::Brother::Child; end 47 | -------------------------------------------------------------------------------- /example/consumer/.kamal/hooks/pre-connect.sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # A sample pre-connect check 4 | # 5 | # Warms DNS before connecting to hosts in parallel 6 | # 7 | # These environment variables are available: 8 | # KAMAL_RECORDED_AT 9 | # KAMAL_PERFORMER 10 | # KAMAL_VERSION 11 | # KAMAL_HOSTS 12 | # KAMAL_ROLE (if set) 13 | # KAMAL_DESTINATION (if set) 14 | # KAMAL_RUNTIME 15 | 16 | hosts = ENV["KAMAL_HOSTS"].split(",") 17 | results = nil 18 | max = 3 19 | 20 | elapsed = Benchmark.realtime do 21 | results = hosts.map do |host| 22 | Thread.new do 23 | tries = 1 24 | 25 | begin 26 | Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) 27 | rescue SocketError 28 | if tries < max 29 | puts "Retrying DNS warmup: #{host}" 30 | tries += 1 31 | sleep rand 32 | retry 33 | else 34 | puts "DNS warmup failed: #{host}" 35 | host 36 | end 37 | end 38 | 39 | tries 40 | end 41 | end.map(&:value) 42 | end 43 | 44 | retries = results.sum - hosts.size 45 | nopes = results.count { |r| r == max } 46 | 47 | puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ] 48 | -------------------------------------------------------------------------------- /example/provider/.kamal/hooks/pre-connect.sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # A sample pre-connect check 4 | # 5 | # Warms DNS before connecting to hosts in parallel 6 | # 7 | # These environment variables are available: 8 | # KAMAL_RECORDED_AT 9 | # KAMAL_PERFORMER 10 | # KAMAL_VERSION 11 | # KAMAL_HOSTS 12 | # KAMAL_ROLE (if set) 13 | # KAMAL_DESTINATION (if set) 14 | # KAMAL_RUNTIME 15 | 16 | hosts = ENV["KAMAL_HOSTS"].split(",") 17 | results = nil 18 | max = 3 19 | 20 | elapsed = Benchmark.realtime do 21 | results = hosts.map do |host| 22 | Thread.new do 23 | tries = 1 24 | 25 | begin 26 | Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) 27 | rescue SocketError 28 | if tries < max 29 | puts "Retrying DNS warmup: #{host}" 30 | tries += 1 31 | sleep rand 32 | retry 33 | else 34 | puts "DNS warmup failed: #{host}" 35 | host 36 | end 37 | end 38 | 39 | tries 40 | end 41 | end.map(&:value) 42 | end 43 | 44 | retries = results.sum - hosts.size 45 | nopes = results.count { |r| r == max } 46 | 47 | puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ] 48 | -------------------------------------------------------------------------------- /lib/active_cached_resource/logger.rb: -------------------------------------------------------------------------------- 1 | require "logger" 2 | 3 | module ActiveCachedResource 4 | class Logger < ::Logger 5 | # @!constant COLORS 6 | # @return [Hash] A hash that maps log levels to their corresponding ANSI color codes. 7 | # @example 8 | # COLORS[:debug] # => "\e[36m" (Blue) 9 | # COLORS[:info] # => "\e[0m" (Default) 10 | # COLORS[:warn] # => "\e[33m" (Yellow) 11 | # COLORS[:error] # => "\e[31m" (Red) 12 | # COLORS[:fatal] # => "\e[31m" (Red) 13 | # COLORS[:reset] # => "\e[0m" (Reset) 14 | COLORS = { 15 | debug: "\e[36m", # Blue 16 | info: "\e[0m", # Default 17 | warn: "\e[33m", # Yellow 18 | error: "\e[31m", # Red 19 | fatal: "\e[31m", # Red 20 | reset: "\e[0m" # Reset 21 | } 22 | 23 | # Initializes a new logger instance for the specified model. 24 | # 25 | # @param model_name [String] the name of the model to be logged 26 | # 27 | # @return [void] 28 | def initialize(model_name) 29 | super($stdout) 30 | @model_name = model_name 31 | self.formatter = proc do |severity, datetime, _progname, msg| 32 | "#{COLORS[severity.downcase.to_sym]}#{datetime} -- #{severity} [CACHE][ACR][#{model_name}] #{msg}#{COLORS[:reset]}\n" 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /example/consumer/.kamal/hooks/pre-build.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A sample pre-build hook 4 | # 5 | # Checks: 6 | # 1. We have a clean checkout 7 | # 2. A remote is configured 8 | # 3. The branch has been pushed to the remote 9 | # 4. The version we are deploying matches the remote 10 | # 11 | # These environment variables are available: 12 | # KAMAL_RECORDED_AT 13 | # KAMAL_PERFORMER 14 | # KAMAL_VERSION 15 | # KAMAL_HOSTS 16 | # KAMAL_ROLE (if set) 17 | # KAMAL_DESTINATION (if set) 18 | 19 | if [ -n "$(git status --porcelain)" ]; then 20 | echo "Git checkout is not clean, aborting..." >&2 21 | git status --porcelain >&2 22 | exit 1 23 | fi 24 | 25 | first_remote=$(git remote) 26 | 27 | if [ -z "$first_remote" ]; then 28 | echo "No git remote set, aborting..." >&2 29 | exit 1 30 | fi 31 | 32 | current_branch=$(git branch --show-current) 33 | 34 | if [ -z "$current_branch" ]; then 35 | echo "Not on a git branch, aborting..." >&2 36 | exit 1 37 | fi 38 | 39 | remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1) 40 | 41 | if [ -z "$remote_head" ]; then 42 | echo "Branch not pushed to remote, aborting..." >&2 43 | exit 1 44 | fi 45 | 46 | if [ "$KAMAL_VERSION" != "$remote_head" ]; then 47 | echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2 48 | exit 1 49 | fi 50 | 51 | exit 0 52 | -------------------------------------------------------------------------------- /example/provider/.kamal/hooks/pre-build.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A sample pre-build hook 4 | # 5 | # Checks: 6 | # 1. We have a clean checkout 7 | # 2. A remote is configured 8 | # 3. The branch has been pushed to the remote 9 | # 4. The version we are deploying matches the remote 10 | # 11 | # These environment variables are available: 12 | # KAMAL_RECORDED_AT 13 | # KAMAL_PERFORMER 14 | # KAMAL_VERSION 15 | # KAMAL_HOSTS 16 | # KAMAL_ROLE (if set) 17 | # KAMAL_DESTINATION (if set) 18 | 19 | if [ -n "$(git status --porcelain)" ]; then 20 | echo "Git checkout is not clean, aborting..." >&2 21 | git status --porcelain >&2 22 | exit 1 23 | fi 24 | 25 | first_remote=$(git remote) 26 | 27 | if [ -z "$first_remote" ]; then 28 | echo "No git remote set, aborting..." >&2 29 | exit 1 30 | fi 31 | 32 | current_branch=$(git branch --show-current) 33 | 34 | if [ -z "$current_branch" ]; then 35 | echo "Not on a git branch, aborting..." >&2 36 | exit 1 37 | fi 38 | 39 | remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1) 40 | 41 | if [ -z "$remote_head" ]; then 42 | echo "Branch not pushed to remote, aborting..." >&2 43 | exit 1 44 | fi 45 | 46 | if [ "$KAMAL_VERSION" != "$remote_head" ]; then 47 | echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2 48 | exit 1 49 | fi 50 | 51 | exit 0 52 | -------------------------------------------------------------------------------- /lib/activeresource/test/cases/associations/builder/belongs_to_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "abstract_unit" 4 | 5 | require "fixtures/person" 6 | require "fixtures/beast" 7 | require "fixtures/customer" 8 | 9 | 10 | class ActiveResource::Associations::Builder::BelongsToTest < ActiveSupport::TestCase 11 | def setup 12 | @klass = ActiveResource::Associations::Builder::BelongsTo 13 | end 14 | 15 | 16 | def test_validations_for_instance 17 | object = @klass.new(Person, :customer, {}) 18 | assert_equal({}, object.send(:validate_options)) 19 | end 20 | 21 | def test_instance_build 22 | object = @klass.new(Person, :customer, {}) 23 | Person.expects(:defines_belongs_to_finder_method).with(kind_of(ActiveResource::Reflection::AssociationReflection)) 24 | 25 | reflection = object.build 26 | 27 | assert_kind_of ActiveResource::Reflection::AssociationReflection, reflection 28 | assert_equal :customer, reflection.name 29 | assert_equal Customer, reflection.klass 30 | assert_equal "customer_id", reflection.foreign_key 31 | end 32 | 33 | 34 | def test_valid_options 35 | assert @klass.build(Person, :customer, class_name: "Person") 36 | assert @klass.build(Person, :customer, foreign_key: "person_id") 37 | 38 | assert_raise ArgumentError do 39 | @klass.build(Person, :customer, soo_invalid: true) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "dotenv/load" 4 | require "simplecov" 5 | 6 | Dir.glob(File.join(__dir__, "support", "**", "*.rb")).each { |f| require_relative f } 7 | 8 | SimpleCov.start do 9 | add_filter "/spec/" 10 | add_filter "/example/" 11 | add_filter "/lib/active_resource/" 12 | end 13 | require "active_cached_resource" 14 | require "active_record" 15 | require "sqlite3" 16 | 17 | ActiveRecord::Schema.verbose = false 18 | 19 | RSpec.configure do |config| 20 | include ActiveSupport::LoggerSilence 21 | # Enable flags like --only-failures and --next-failure 22 | config.example_status_persistence_file_path = ".rspec_status" 23 | 24 | # Disable RSpec exposing methods globally on `Module` and `main` 25 | config.disable_monkey_patching! 26 | 27 | config.expect_with :rspec do |c| 28 | c.syntax = :expect 29 | end 30 | 31 | config.before(:suite) do 32 | ActiveSupport::LoggerSilence.silence do 33 | ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") 34 | 35 | ActiveRecord::Schema.define do 36 | create_table :active_cached_resources, force: true do |t| 37 | t.string :key, null: false 38 | t.binary :value, null: false 39 | t.datetime :expires_at, null: false 40 | 41 | t.index [:key, :expires_at], unique: true, name: "index_active_cached_resources_on_key_and_expires_at" 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | workflow_call: 4 | concurrency: 5 | group: ${{ github.ref }}-${{ github.workflow }}-lint 6 | cancel-in-progress: true 7 | jobs: 8 | lint: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | - name: Get changed files 14 | id: changed-files 15 | uses: tj-actions/changed-files@v46 16 | with: 17 | files: | 18 | **/*.{rb,ru,rake,gemspec} 19 | Gemfile 20 | active_cached_resource.gemspec 21 | files_ignore: | 22 | .bundle/**/* 23 | .git/**/* 24 | sorbet/**/* 25 | Gemfile.lock 26 | tmp/**/* 27 | vendor/**/* 28 | lib/activeresource/**/* 29 | separator: " " 30 | - name: Set up Ruby 31 | if: steps.changed-files.outputs.any_changed == 'true' 32 | uses: ruby/setup-ruby@v1.197.0 33 | with: 34 | ruby-version: 3.2.2 35 | bundler-cache: true 36 | - name: Type Check 37 | if: steps.changed-files.outputs.any_changed == 'true' 38 | run: bundle exec srb tc 39 | - name: Lint Code 40 | if: steps.changed-files.outputs.any_changed == 'true' 41 | uses: wearerequired/lint-action@v2.3.0 42 | with: 43 | auto_fix: true 44 | rubocop: true 45 | rubocop_command_prefix: bundle exec 46 | rubocop_args: --parallel ${{ steps.changed-files.outputs.all_changed_files }} -------------------------------------------------------------------------------- /lib/active_cached_resource/caching_strategies/active_support_cache.rb: -------------------------------------------------------------------------------- 1 | require_relative "base" 2 | 3 | module ActiveCachedResource 4 | module CachingStrategies 5 | class ActiveSupportCache < Base 6 | def initialize(cache_store) 7 | super() 8 | @cache_store = cache_store 9 | end 10 | 11 | protected 12 | 13 | def read_raw(key) 14 | @cache_store.read(key) 15 | end 16 | 17 | def write_raw(key, compressed_value, options) 18 | successful_write = @cache_store.write(key, compressed_value, options) 19 | update_master_key(key, options) if successful_write 20 | 21 | successful_write 22 | end 23 | 24 | def delete_raw(key) 25 | @cache_store.delete(key) 26 | end 27 | 28 | def clear_raw(prefix) 29 | existing_keys = @cache_store.read(prefix) 30 | return if existing_keys.nil? 31 | 32 | existing_keys.add(prefix) 33 | @cache_store.delete_multi(existing_keys.to_a) # Redis implementation does not work with Sets 34 | end 35 | 36 | private 37 | 38 | # Updates the `master` key, which contains keys for a given prefix. 39 | def update_master_key(key, options) 40 | prefix, _ = split_key(key) 41 | 42 | existing_keys = @cache_store.read(prefix) || Set.new 43 | existing_keys.add(key) 44 | 45 | # Maintain the list of keys for twice the expiration time 46 | @cache_store.write(prefix, existing_keys, expires_in: options[:expires_in]) 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | workflow_call: 4 | concurrency: 5 | group: ${{ github.ref }}-${{ github.workflow }}-test 6 | cancel-in-progress: true 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | env: 11 | BUNDLE_WITHOUT: 'versioning:development' 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - ruby: "3.2" 17 | - ruby: "3.3" 18 | - ruby: "3.4" 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Load dotenv into workflow 22 | id: dotenv 23 | uses: falti/dotenv-action@v1.1.4 24 | with: 25 | keys-case: bypass 26 | - name: Set up Ruby 27 | uses: ruby/setup-ruby@v1 28 | with: 29 | ruby-version: ${{ matrix.ruby }} 30 | bundler-cache: true 31 | - name: Test ActiveCachedResource 32 | id: active-cached-resource-test 33 | continue-on-error: true 34 | run: | 35 | bundle exec rspec \ 36 | -f RspecJunitFormatter -o ${{ steps.dotenv.outputs.JUNIT_REPORTS_PATH }}/rspec_ruby${{ matrix.ruby }}.xml 37 | - name: Test ActiveResource 38 | continue-on-error: true 39 | id: active-resource-test 40 | run: | 41 | bundle exec rake active_resource_test 42 | - name: Publish Test Report 43 | uses: mikepenz/action-junit-report@v5.2.0 44 | with: 45 | report_paths: '${{ steps.dotenv.outputs.JUNIT_REPORTS_PATH }}/**/*.xml' 46 | check_name: 'Test Result for Ruby:${{ matrix.ruby }}' 47 | fail_on_failure: true 48 | comment: true 49 | -------------------------------------------------------------------------------- /lib/activeresource/test/cases/base/equality_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "abstract_unit" 4 | require "fixtures/person" 5 | require "fixtures/street_address" 6 | 7 | class BaseEqualityTest < ActiveSupport::TestCase 8 | def setup 9 | @new = Person.new 10 | @one = Person.new(id: 1) 11 | @two = Person.new(id: 2) 12 | @street = StreetAddress.new(id: 2) 13 | end 14 | 15 | def test_should_equal_self 16 | assert @new == @new, "@new == @new" 17 | assert @one == @one, "@one == @one" 18 | end 19 | 20 | def test_shouldnt_equal_new_resource 21 | assert @new != @one, "@new != @one" 22 | assert @one != @new, "@one != @new" 23 | end 24 | 25 | def test_shouldnt_equal_different_class 26 | assert @two != @street, "person != street_address with same id" 27 | assert @street != @two, "street_address != person with same id" 28 | end 29 | 30 | def test_eql_should_alias_equals_operator 31 | assert_equal @new == @new, @new.eql?(@new) 32 | assert_equal @new == @one, @new.eql?(@one) 33 | 34 | assert_equal @one == @one, @one.eql?(@one) 35 | assert_equal @one == @new, @one.eql?(@new) 36 | 37 | assert_equal @one == @street, @one.eql?(@street) 38 | end 39 | 40 | def test_hash_should_be_id_hash 41 | [@new, @one, @two, @street].each do |resource| 42 | assert_equal resource.id.hash, resource.hash 43 | end 44 | end 45 | 46 | def test_with_prefix_options 47 | assert_equal @one == @one, @one.eql?(@one) 48 | assert_equal @one == @one.dup, @one.eql?(@one.dup) 49 | new_one = @one.dup 50 | new_one.prefix_options = { foo: "bar" } 51 | assert_not_equal @one, new_one 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /active_cached_resource.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/active_cached_resource/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "active_cached_resource" 7 | spec.version = ActiveCachedResource::VERSION 8 | spec.authors = ["Jean Luis Urena"] 9 | spec.email = ["eljean@live.com"] 10 | 11 | spec.summary = "ActiveResource, but with a caching layer." 12 | spec.homepage = "https://github.com/jlurena/active_cached_resource" 13 | spec.required_ruby_version = ">= 3.2.0" 14 | spec.platform = Gem::Platform::RUBY 15 | 16 | spec.metadata = { 17 | "changelog_uri" => "https://github.com/jlurena/active_cached_resource/blob/main/CHANGELOG.md", 18 | "documentation_uri" => "https://rubydoc.info/gems/active_cached_resource/", 19 | "source_code_uri" => "https://github.com/jlurena/active_cached_resource", 20 | "homepage_uri" => spec.homepage, 21 | "wiki_uri" => "https://github.com/jlurena/active_cached_resource/wiki" 22 | } 23 | 24 | spec.files = [ 25 | "README.md", 26 | "LICENSE", 27 | "CHANGELOG.md", 28 | "lib/active_cached_resource.rb", 29 | *Dir.glob("lib/active_cached_resource/**/*"), 30 | *Dir.glob("lib/activeresource/lib/**/*"), 31 | "lib/activeresource/README.md", 32 | *Dir.glob("lib/generators/**/*") 33 | ] 34 | 35 | spec.bindir = "exe" 36 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } 37 | spec.require_paths = ["lib"] 38 | 39 | spec.add_dependency "activemodel-serializers-xml", "~> 1.0" 40 | spec.add_dependency "activemodel", ">= 6.0" 41 | spec.add_dependency "activesupport", ">= 6.0" 42 | spec.add_dependency "ostruct", "~> 0.6.1" 43 | end 44 | -------------------------------------------------------------------------------- /lib/activeresource/test/cases/active_job_serializer_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "abstract_unit" 4 | 5 | require "fixtures/project" 6 | require "fixtures/person" 7 | require "fixtures/product" 8 | require "active_job" 9 | require "active_job/arguments" 10 | require_relative "../../lib/active_resource/active_job_serializer" 11 | 12 | class ActiveJobSerializerTest < ActiveSupport::TestCase 13 | setup do 14 | @klass = ActiveResource::ActiveJobSerializer 15 | end 16 | 17 | def test_serialize 18 | project = Project.new(id: 1, name: "Ruby on Rails") 19 | project.prefix_options[:person_id] = 1 20 | project_json = { 21 | _aj_serialized: @klass.name, 22 | class: project.class.name, 23 | persisted: project.persisted?, 24 | prefix_options: project.prefix_options, 25 | attributes: project.attributes 26 | }.as_json 27 | serialized_json = @klass.serialize(project) 28 | 29 | assert_equal project_json, serialized_json 30 | end 31 | 32 | def test_deserialize 33 | person = Person.new(id: 2, name: "David") 34 | person.persisted = true 35 | person_json = { 36 | _aj_serialized: @klass.name, 37 | class: person.class.name, 38 | persisted: person.persisted?, 39 | prefix_options: person.prefix_options, 40 | attributes: person.attributes 41 | }.as_json 42 | deserialized_object = @klass.deserialize(person_json) 43 | 44 | assert_equal person, deserialized_object 45 | end 46 | 47 | def test_serialize? 48 | product = Product.new(id: 3, name: "Chunky Bacon") 49 | 50 | assert @klass.serialize?(product) 51 | assert_not @klass.serialize?("not a resource") 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ActiveCachedResource 2 | [ActiveResource](https://github.com/rails/activeresource) but with a caching layer. 3 | 4 | ## Installation 5 | 6 | Install the gem and add to the application's Gemfile by executing: 7 | 8 | $ bundle add active_cached_resource 9 | 10 | If bundler is not being used to manage dependencies, install the gem by executing: 11 | 12 | $ gem install active_cached_resource 13 | 14 | ## Usage 15 | 16 | Check out the Wiki! https://github.com/jlurena/active_cached_resource/wiki 17 | 18 | ## Development 19 | 20 | After checking out the repo, run `bin/setup` to install dependencies. 21 | Run `rake` to run the linter and tests. 22 | You can also run `bin/console` for an interactive prompt that will allow you to experiment. 23 | 24 | Additionally, there is an `example` Rails Application you can play with. 25 | In there you'll find two small rails app: 26 | 27 | - Provider 28 | - This application contains the remote data. 29 | - Consumer 30 | - This application consumes a remote resource from Provider. 31 | 32 | 33 | ## Contributing 34 | 35 | Bug reports and pull requests are welcome on GitHub at https://github.com/jlurena/active_cached_resource. 36 | 37 | ## Inspirations & Credits 38 | - [CachedResource](https://github.com/mhgbrown/cached_resource) 39 | 40 | Major differences between this gem and `CachedResource` are: 41 | - This uses a custom, vendored version of the gem [`ActiveResource`](https://github.com/rails/activeresource) that adds the following features 42 | - Lazy `where` chaining 43 | - Flexibility to add your own caching strategies, this gem comes built in with two of them: 44 | - Caching using `ActiveSupport` 45 | - Caching using `SQL` 46 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/date@3.4.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `date` gem. 5 | # Please instead update this file by running `bin/tapioca gem date`. 6 | 7 | 8 | # source://date//lib/date.rb#6 9 | class Date 10 | include ::Comparable 11 | 12 | # call-seq: 13 | # infinite? -> false 14 | # 15 | # Returns +false+ 16 | # 17 | # @return [Boolean] 18 | # 19 | # source://date//lib/date.rb#13 20 | def infinite?; end 21 | end 22 | 23 | # source://date//lib/date.rb#17 24 | class Date::Infinity < ::Numeric 25 | # @return [Infinity] a new instance of Infinity 26 | # 27 | # source://date//lib/date.rb#19 28 | def initialize(d = T.unsafe(nil)); end 29 | 30 | # source://date//lib/date.rb#33 31 | def +@; end 32 | 33 | # source://date//lib/date.rb#32 34 | def -@; end 35 | 36 | # source://date//lib/date.rb#35 37 | def <=>(other); end 38 | 39 | # source://date//lib/date.rb#30 40 | def abs; end 41 | 42 | # source://date//lib/date.rb#51 43 | def coerce(other); end 44 | 45 | # @return [Boolean] 46 | # 47 | # source://date//lib/date.rb#26 48 | def finite?; end 49 | 50 | # @return [Boolean] 51 | # 52 | # source://date//lib/date.rb#27 53 | def infinite?; end 54 | 55 | # @return [Boolean] 56 | # 57 | # source://date//lib/date.rb#28 58 | def nan?; end 59 | 60 | # source://date//lib/date.rb#59 61 | def to_f; end 62 | 63 | # @return [Boolean] 64 | # 65 | # source://date//lib/date.rb#25 66 | def zero?; end 67 | 68 | protected 69 | 70 | # source://date//lib/date.rb#21 71 | def d; end 72 | end 73 | 74 | # source://date//lib/date.rb#7 75 | Date::VERSION = T.let(T.unsafe(nil), String) 76 | -------------------------------------------------------------------------------- /example/provider/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | require "active_record/railtie" 8 | # require "active_storage/engine" 9 | require "action_controller/railtie" 10 | require "action_mailer/railtie" 11 | # require "action_mailbox/engine" 12 | # require "action_text/engine" 13 | require "action_view/railtie" 14 | # require "action_cable/engine" 15 | # require "rails/test_unit/railtie" 16 | 17 | # Require the gems listed in Gemfile, including any gems 18 | # you've limited to :test, :development, or :production. 19 | Bundler.require(*Rails.groups) 20 | 21 | module Provider 22 | class Application < Rails::Application 23 | # Initialize configuration defaults for originally generated Rails version. 24 | config.load_defaults 8.0 25 | 26 | # Please, add to the `ignore` list any other `lib` subdirectories that do 27 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 28 | # Common ones are `templates`, `generators`, or `middleware`, for example. 29 | config.autoload_lib(ignore: %w[assets tasks]) 30 | 31 | # Configuration for the application, engines, and railties goes here. 32 | # 33 | # These settings can be overridden in specific environments using the files 34 | # in config/environments, which are processed later. 35 | # 36 | # config.time_zone = "Central Time (US & Canada)" 37 | # config.eager_load_paths << Rails.root.join("extras") 38 | 39 | # Only loads a smaller set of middleware suitable for API only apps. 40 | # Middleware like session, flash, cookies can be added back manually. 41 | # Skip views, helpers and assets when generating a new resource. 42 | config.api_only = true 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /example/provider/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema[8.0].define(version: 2024_12_02_184017) do 14 | create_table "addresses", force: :cascade do |t| 15 | t.string "street" 16 | t.string "city" 17 | t.string "state" 18 | t.string "zip" 19 | t.integer "person_id", null: false 20 | t.datetime "created_at", null: false 21 | t.datetime "updated_at", null: false 22 | t.index ["person_id"], name: "index_addresses_on_person_id" 23 | end 24 | 25 | create_table "companies", force: :cascade do |t| 26 | t.string "name" 27 | t.string "street" 28 | t.string "city" 29 | t.string "state" 30 | t.string "zip" 31 | t.integer "person_id", null: false 32 | t.datetime "created_at", null: false 33 | t.datetime "updated_at", null: false 34 | t.index ["person_id"], name: "index_companies_on_person_id" 35 | end 36 | 37 | create_table "people", force: :cascade do |t| 38 | t.string "first_name" 39 | t.string "last_name" 40 | t.integer "age" 41 | t.datetime "created_at", null: false 42 | t.datetime "updated_at", null: false 43 | end 44 | 45 | add_foreign_key "addresses", "people" 46 | add_foreign_key "companies", "people" 47 | end 48 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye-slim AS base 2 | 3 | ENV LANG C.UTF-8 4 | ENV LC_ALL C.UTF-8 5 | ENV ASDF_DIR=/root/.asdf 6 | 7 | # Install dependencies 8 | RUN apt-get update && apt-get install -y \ 9 | bash \ 10 | build-essential \ 11 | curl \ 12 | git \ 13 | libffi-dev \ 14 | libssl-dev \ 15 | perl \ 16 | libreadline-dev \ 17 | tzdata \ 18 | libyaml-dev \ 19 | zlib1g-dev 20 | 21 | RUN git clone https://github.com/asdf-vm/asdf.git /root/.asdf --branch v0.14.0 && \ 22 | . "$ASDF_DIR/asdf.sh" && \ 23 | asdf plugin add ruby 24 | 25 | # Ruby 3.2 26 | FROM base AS ruby-3.2 27 | RUN . "$ASDF_DIR/asdf.sh" && \ 28 | asdf install ruby $(asdf latest ruby 3.2) && \ 29 | asdf global ruby $(asdf latest ruby 3.2) && \ 30 | gem install bundler 31 | 32 | # Ruby 3.3 33 | FROM base AS ruby-3.3 34 | RUN . "$ASDF_DIR/asdf.sh" && \ 35 | asdf install ruby $(asdf latest ruby 3.3) && \ 36 | asdf global ruby $(asdf latest ruby 3.3) && \ 37 | gem install bundler 38 | 39 | # Ruby 3.4 40 | FROM base AS ruby-3.4 41 | RUN . "$ASDF_DIR/asdf.sh" && \ 42 | asdf install ruby $(asdf latest ruby 3.4) && \ 43 | asdf global ruby $(asdf latest ruby 3.4) && \ 44 | gem install bundler 45 | 46 | # Final Image with Application Code 47 | FROM base AS final 48 | 49 | # Copy and merge installed ASDF directory from ruby versions 50 | COPY --from=ruby-3.2 /root/.asdf /tmp/.asdf-3.2 51 | COPY --from=ruby-3.3 /root/.asdf /tmp/.asdf-3.3 52 | COPY --from=ruby-3.4 /root/.asdf /tmp/.asdf-3.4 53 | RUN cp -r /tmp/.asdf-3.2/* /root/.asdf/ && \ 54 | cp -r /tmp/.asdf-3.3/* /root/.asdf/ && \ 55 | cp -r /tmp/.asdf-3.4/* /root/.asdf/ && \ 56 | rm -rf /tmp/.asdf* 57 | 58 | WORKDIR /app 59 | 60 | COPY bin bin 61 | COPY lib lib 62 | COPY spec spec 63 | COPY scripts scripts 64 | COPY sorbet sorbet 65 | COPY .env .rubocop.yml .standard.yml active_cached_resource.gemspec Gemfile Rakefile . 66 | 67 | CMD ['. "$ASDF_DIR/asdf.sh"', "&&", "tail", "-f", "/dev/null"] -------------------------------------------------------------------------------- /lib/activeresource/examples/performance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rubygems" 4 | require_relative "../lib/active_resource" 5 | require "benchmark" 6 | 7 | TIMES = (ENV["N"] || 10_000).to_i 8 | 9 | # deep nested resource 10 | attrs = { 11 | id: 1, 12 | name: "Luis", 13 | age: 21, 14 | friends: [ 15 | { 16 | name: "JK", 17 | age: 24, 18 | colors: ["red", "green", "blue"], 19 | brothers: [ 20 | { 21 | name: "Mateo", 22 | age: 35, 23 | children: [{ name: "Edith", age: 5 }, { name: "Martha", age: 4 }] 24 | }, 25 | { 26 | name: "Felipe", 27 | age: 33, 28 | children: [{ name: "Bryan", age: 1 }, { name: "Luke", age: 0 }] 29 | } 30 | ] 31 | }, 32 | { 33 | name: "Eduardo", 34 | age: 20, 35 | colors: [], 36 | brothers: [ 37 | { 38 | name: "Sebas", 39 | age: 23, 40 | children: [{ name: "Andres", age: 0 }, { name: "Jorge", age: 2 }] 41 | }, 42 | { 43 | name: "Elsa", 44 | age: 19, 45 | children: [{ name: "Natacha", age: 1 }] 46 | }, 47 | { 48 | name: "Milena", 49 | age: 16, 50 | children: [] 51 | } 52 | ] 53 | } 54 | ] 55 | } 56 | 57 | class Customer < ActiveResource::Base 58 | self.site = "http://37s.sunrise.i:3000" 59 | end 60 | 61 | module Nested 62 | class Customer < ActiveResource::Base 63 | self.site = "http://37s.sunrise.i:3000" 64 | end 65 | end 66 | 67 | Benchmark.bm(40) do |x| 68 | x.report("Model.new (instantiation)") { TIMES.times { Customer.new } } 69 | x.report("Nested::Model.new (instantiation)") { TIMES.times { Nested::Customer.new } } 70 | x.report("Model.new (setting attributes)") { TIMES.times { Customer.new attrs } } 71 | x.report("Nested::Model.new (setting attributes)") { TIMES.times { Nested::Customer.new attrs } } 72 | end 73 | -------------------------------------------------------------------------------- /example/consumer/config/puma.rb: -------------------------------------------------------------------------------- 1 | # This configuration file will be evaluated by Puma. The top-level methods that 2 | # are invoked here are part of Puma's configuration DSL. For more information 3 | # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. 4 | # 5 | # Puma starts a configurable number of processes (workers) and each process 6 | # serves each request in a thread from an internal thread pool. 7 | # 8 | # You can control the number of workers using ENV["WEB_CONCURRENCY"]. You 9 | # should only set this value when you want to run 2 or more workers. The 10 | # default is already 1. 11 | # 12 | # The ideal number of threads per worker depends both on how much time the 13 | # application spends waiting for IO operations and on how much you wish to 14 | # prioritize throughput over latency. 15 | # 16 | # As a rule of thumb, increasing the number of threads will increase how much 17 | # traffic a given process can handle (throughput), but due to CRuby's 18 | # Global VM Lock (GVL) it has diminishing returns and will degrade the 19 | # response time (latency) of the application. 20 | # 21 | # The default is set to 3 threads as it's deemed a decent compromise between 22 | # throughput and latency for the average Rails application. 23 | # 24 | # Any libraries that use a connection pool or another resource pool should 25 | # be configured to provide at least as many connections as the number of 26 | # threads. This includes Active Record's `pool` parameter in `database.yml`. 27 | threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) 28 | threads threads_count, threads_count 29 | 30 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 31 | port ENV.fetch("PORT", 3000) 32 | 33 | # Allow puma to be restarted by `bin/rails restart` command. 34 | plugin :tmp_restart 35 | 36 | # Run the Solid Queue supervisor inside of Puma for single-server deployments 37 | plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] 38 | 39 | # Specify the PID file. Defaults to tmp/pids/server.pid in development. 40 | # In other environments, only set the PID file if requested. 41 | pidfile ENV["PIDFILE"] if ENV["PIDFILE"] 42 | -------------------------------------------------------------------------------- /example/provider/config/puma.rb: -------------------------------------------------------------------------------- 1 | # This configuration file will be evaluated by Puma. The top-level methods that 2 | # are invoked here are part of Puma's configuration DSL. For more information 3 | # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. 4 | # 5 | # Puma starts a configurable number of processes (workers) and each process 6 | # serves each request in a thread from an internal thread pool. 7 | # 8 | # You can control the number of workers using ENV["WEB_CONCURRENCY"]. You 9 | # should only set this value when you want to run 2 or more workers. The 10 | # default is already 1. 11 | # 12 | # The ideal number of threads per worker depends both on how much time the 13 | # application spends waiting for IO operations and on how much you wish to 14 | # prioritize throughput over latency. 15 | # 16 | # As a rule of thumb, increasing the number of threads will increase how much 17 | # traffic a given process can handle (throughput), but due to CRuby's 18 | # Global VM Lock (GVL) it has diminishing returns and will degrade the 19 | # response time (latency) of the application. 20 | # 21 | # The default is set to 3 threads as it's deemed a decent compromise between 22 | # throughput and latency for the average Rails application. 23 | # 24 | # Any libraries that use a connection pool or another resource pool should 25 | # be configured to provide at least as many connections as the number of 26 | # threads. This includes Active Record's `pool` parameter in `database.yml`. 27 | threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) 28 | threads threads_count, threads_count 29 | 30 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 31 | port ENV.fetch("PORT", 3000) 32 | 33 | # Allow puma to be restarted by `bin/rails restart` command. 34 | plugin :tmp_restart 35 | 36 | # Run the Solid Queue supervisor inside of Puma for single-server deployments 37 | plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] 38 | 39 | # Specify the PID file. Defaults to tmp/pids/server.pid in development. 40 | # In other environments, only set the PID file if requested. 41 | pidfile ENV["PIDFILE"] if ENV["PIDFILE"] 42 | -------------------------------------------------------------------------------- /lib/activeresource/test/cases/log_subscriber_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "abstract_unit" 4 | require "fixtures/person" 5 | require "active_support/log_subscriber/test_helper" 6 | require "active_support/core_ext/hash/conversions" 7 | 8 | require_relative "../../lib/active_resource/log_subscriber" 9 | 10 | class LogSubscriberTest < ActiveSupport::TestCase 11 | include ActiveSupport::LogSubscriber::TestHelper 12 | 13 | def setup 14 | super 15 | 16 | @matz = { person: { id: 1, name: "Matz" } }.to_json 17 | ActiveResource::HttpMock.respond_to do |mock| 18 | mock.get "/people/1.json", {}, @matz 19 | mock.get "/people/2.json", {}, nil, 404 20 | mock.get "/people/3.json", {}, nil, 502 21 | end 22 | 23 | ActiveResource::LogSubscriber.attach_to :active_resource 24 | end 25 | 26 | def set_logger(logger) 27 | ActiveResource::Base.logger = logger 28 | end 29 | 30 | def test_request_notification 31 | Person.find(1) 32 | wait 33 | assert_equal 2, @logger.logged(:info).size 34 | assert_equal "GET http://37s.sunrise.i:3000/people/1.json", @logger.logged(:info)[0] 35 | assert_match(/--> 200 200 33/, @logger.logged(:info)[1]) 36 | end 37 | 38 | def test_failure_error_log 39 | Person.find(2) 40 | rescue 41 | wait 42 | assert_equal 2, @logger.logged(:error).size 43 | assert_equal "GET http://37s.sunrise.i:3000/people/2.json", @logger.logged(:error)[0] 44 | assert_match(/--> 404 404 0/, @logger.logged(:error)[1]) 45 | end 46 | 47 | def test_server_error_log 48 | Person.find(3) 49 | rescue 50 | wait 51 | assert_equal 2, @logger.logged(:error).size 52 | assert_equal "GET http://37s.sunrise.i:3000/people/3.json", @logger.logged(:error)[0] 53 | assert_match(/--> 502 502 0/, @logger.logged(:error)[1]) 54 | end 55 | 56 | def test_connection_failure 57 | Person.find(99) 58 | rescue 59 | wait 60 | assert_equal 2, @logger.logged(:error).size 61 | assert_equal "GET http://37s.sunrise.i:3000/people/99.json", @logger.logged(:error)[0] 62 | assert_match(/--> 523 ActiveResource connection error 0/, @logger.logged(:error)[1]) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/securerandom@0.4.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `securerandom` gem. 5 | # Please instead update this file by running `bin/tapioca gem securerandom`. 6 | 7 | 8 | # == Secure random number generator interface. 9 | # 10 | # This library is an interface to secure random number generators which are 11 | # suitable for generating session keys in HTTP cookies, etc. 12 | # 13 | # You can use this library in your application by requiring it: 14 | # 15 | # require 'securerandom' 16 | # 17 | # It supports the following secure random number generators: 18 | # 19 | # * openssl 20 | # * /dev/urandom 21 | # * Win32 22 | # 23 | # SecureRandom is extended by the Random::Formatter module which 24 | # defines the following methods: 25 | # 26 | # * alphanumeric 27 | # * base64 28 | # * choose 29 | # * gen_random 30 | # * hex 31 | # * rand 32 | # * random_bytes 33 | # * random_number 34 | # * urlsafe_base64 35 | # * uuid 36 | # 37 | # These methods are usable as class methods of SecureRandom such as 38 | # +SecureRandom.hex+. 39 | # 40 | # If a secure random number generator is not available, 41 | # +NotImplementedError+ is raised. 42 | # 43 | # source://securerandom//lib/securerandom.rb#41 44 | module SecureRandom 45 | extend ::Random::Formatter 46 | 47 | class << self 48 | # source://securerandom//lib/securerandom.rb#55 49 | def alphanumeric(n = T.unsafe(nil), chars: T.unsafe(nil)); end 50 | 51 | # Returns a random binary string containing +size+ bytes. 52 | # 53 | # See Random.bytes 54 | # 55 | # source://securerandom//lib/securerandom.rb#50 56 | def bytes(n); end 57 | 58 | # source://securerandom//lib/securerandom.rb#70 59 | def gen_random(n); end 60 | 61 | private 62 | 63 | # Implementation using OpenSSL 64 | # 65 | # source://securerandom//lib/securerandom.rb#65 66 | def gen_random_openssl(n); end 67 | 68 | # Implementation using system random device 69 | # 70 | # source://securerandom//lib/securerandom.rb#70 71 | def gen_random_urandom(n); end 72 | end 73 | end 74 | 75 | # The version 76 | # 77 | # source://securerandom//lib/securerandom.rb#44 78 | SecureRandom::VERSION = T.let(T.unsafe(nil), String) 79 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveResource # :nodoc: 4 | class Schema # :nodoc: 5 | # attributes can be known to be one of these types. They are easy to 6 | # cast to/from. 7 | KNOWN_ATTRIBUTE_TYPES = %w( string text integer float decimal datetime timestamp time date binary boolean ) 8 | 9 | # An array of attribute definitions, representing the attributes that 10 | # have been defined. 11 | attr_accessor :attrs 12 | 13 | # The internals of an Active Resource Schema are very simple - 14 | # unlike an Active Record TableDefinition (on which it is based). 15 | # It provides a set of convenience methods for people to define their 16 | # schema using the syntax: 17 | # schema do 18 | # string :foo 19 | # integer :bar 20 | # end 21 | # 22 | # The schema stores the name and type of each attribute. That is then 23 | # read out by the schema method to populate the schema of the actual 24 | # resource. 25 | def initialize 26 | @attrs = {} 27 | end 28 | 29 | def attribute(name, type, options = {}) 30 | raise ArgumentError, "Unknown Attribute type: #{type.inspect} for key: #{name.inspect}" unless type.nil? || Schema::KNOWN_ATTRIBUTE_TYPES.include?(type.to_s) 31 | 32 | the_type = type.to_s 33 | # TODO: add defaults 34 | # the_attr = [type.to_s] 35 | # the_attr << options[:default] if options.has_key? :default 36 | @attrs[name.to_s] = the_type 37 | self 38 | end 39 | 40 | # The following are the attribute types supported by Active Resource 41 | # migrations. 42 | KNOWN_ATTRIBUTE_TYPES.each do |attr_type| 43 | # def string(*args) 44 | # options = args.extract_options! 45 | # attr_names = args 46 | # 47 | # attr_names.each { |name| attribute(name, 'string', options) } 48 | # end 49 | class_eval <<-EOV, __FILE__, __LINE__ + 1 50 | # frozen_string_literal: true 51 | def #{attr_type}(*args) 52 | options = args.extract_options! 53 | attr_names = args 54 | 55 | attr_names.each { |name| attribute(name, '#{attr_type}', options) } 56 | end 57 | EOV 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /example/consumer/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | # check=error=true 3 | 4 | # This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: 5 | # docker build -t consumer . 6 | # docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name consumer consumer 7 | 8 | # For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html 9 | 10 | # Make sure RUBY_VERSION matches the Ruby version in .ruby-version 11 | ARG RUBY_VERSION=3.2.2 12 | FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base 13 | 14 | # Rails app lives here 15 | WORKDIR /rails 16 | 17 | # Install base packages 18 | RUN apt-get update -qq && \ 19 | apt-get install --no-install-recommends -y curl libjemalloc2 sqlite3 && \ 20 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 21 | 22 | # Set production environment 23 | ENV RAILS_ENV="production" \ 24 | BUNDLE_DEPLOYMENT="1" \ 25 | BUNDLE_PATH="/usr/local/bundle" \ 26 | BUNDLE_WITHOUT="development" 27 | 28 | # Throw-away build stage to reduce size of final image 29 | FROM base AS build 30 | 31 | # Install packages needed to build gems 32 | RUN apt-get update -qq && \ 33 | apt-get install --no-install-recommends -y build-essential git pkg-config && \ 34 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 35 | 36 | # Install application gems 37 | COPY Gemfile Gemfile.lock ./ 38 | RUN bundle install && \ 39 | rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git 40 | 41 | # Copy application code 42 | COPY . . 43 | 44 | 45 | 46 | 47 | # Final stage for app image 48 | FROM base 49 | 50 | # Copy built artifacts: gems, application 51 | COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" 52 | COPY --from=build /rails /rails 53 | 54 | # Run and own only the runtime files as a non-root user for security 55 | RUN groupadd --system --gid 1000 rails && \ 56 | useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ 57 | chown -R rails:rails db log storage tmp 58 | USER 1000:1000 59 | 60 | # Entrypoint prepares the database. 61 | ENTRYPOINT ["/rails/bin/docker-entrypoint"] 62 | 63 | # Start server via Thruster by default, this can be overwritten at runtime 64 | EXPOSE 80 65 | CMD ["./bin/thrust", "./bin/rails", "server"] 66 | -------------------------------------------------------------------------------- /example/provider/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | # check=error=true 3 | 4 | # This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: 5 | # docker build -t provider . 6 | # docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name provider provider 7 | 8 | # For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html 9 | 10 | # Make sure RUBY_VERSION matches the Ruby version in .ruby-version 11 | ARG RUBY_VERSION=3.2.2 12 | FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base 13 | 14 | # Rails app lives here 15 | WORKDIR /rails 16 | 17 | # Install base packages 18 | RUN apt-get update -qq && \ 19 | apt-get install --no-install-recommends -y curl libjemalloc2 sqlite3 && \ 20 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 21 | 22 | # Set production environment 23 | ENV RAILS_ENV="production" \ 24 | BUNDLE_DEPLOYMENT="1" \ 25 | BUNDLE_PATH="/usr/local/bundle" \ 26 | BUNDLE_WITHOUT="development" 27 | 28 | # Throw-away build stage to reduce size of final image 29 | FROM base AS build 30 | 31 | # Install packages needed to build gems 32 | RUN apt-get update -qq && \ 33 | apt-get install --no-install-recommends -y build-essential git pkg-config && \ 34 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 35 | 36 | # Install application gems 37 | COPY Gemfile Gemfile.lock ./ 38 | RUN bundle install && \ 39 | rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git 40 | 41 | # Copy application code 42 | COPY . . 43 | 44 | 45 | 46 | 47 | # Final stage for app image 48 | FROM base 49 | 50 | # Copy built artifacts: gems, application 51 | COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" 52 | COPY --from=build /rails /rails 53 | 54 | # Run and own only the runtime files as a non-root user for security 55 | RUN groupadd --system --gid 1000 rails && \ 56 | useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ 57 | chown -R rails:rails db log storage tmp 58 | USER 1000:1000 59 | 60 | # Entrypoint prepares the database. 61 | ENTRYPOINT ["/rails/bin/docker-entrypoint"] 62 | 63 | # Start server via Thruster by default, this can be overwritten at runtime 64 | EXPOSE 80 65 | CMD ["./bin/thrust", "./bin/rails", "server"] 66 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/bigdecimal@3.1.8.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `bigdecimal` gem. 5 | # Please instead update this file by running `bin/tapioca gem bigdecimal`. 6 | 7 | 8 | # source://bigdecimal//lib/bigdecimal/util.rb#78 9 | class BigDecimal < ::Numeric 10 | # call-seq: 11 | # a.to_d -> bigdecimal 12 | # 13 | # Returns self. 14 | # 15 | # require 'bigdecimal/util' 16 | # 17 | # d = BigDecimal("3.14") 18 | # d.to_d # => 0.314e1 19 | # 20 | # source://bigdecimal//lib/bigdecimal/util.rb#110 21 | def to_d; end 22 | 23 | # call-seq: 24 | # a.to_digits -> string 25 | # 26 | # Converts a BigDecimal to a String of the form "nnnnnn.mmm". 27 | # This method is deprecated; use BigDecimal#to_s("F") instead. 28 | # 29 | # require 'bigdecimal/util' 30 | # 31 | # d = BigDecimal("3.14") 32 | # d.to_digits # => "3.14" 33 | # 34 | # source://bigdecimal//lib/bigdecimal/util.rb#90 35 | def to_digits; end 36 | end 37 | 38 | BigDecimal::VERSION = T.let(T.unsafe(nil), String) 39 | 40 | # source://bigdecimal//lib/bigdecimal/util.rb#138 41 | class Complex < ::Numeric 42 | # call-seq: 43 | # cmp.to_d -> bigdecimal 44 | # cmp.to_d(precision) -> bigdecimal 45 | # 46 | # Returns the value as a BigDecimal. 47 | # 48 | # The +precision+ parameter is required for a rational complex number. 49 | # This parameter is used to determine the number of significant digits 50 | # for the result. 51 | # 52 | # require 'bigdecimal' 53 | # require 'bigdecimal/util' 54 | # 55 | # Complex(0.1234567, 0).to_d(4) # => 0.1235e0 56 | # Complex(Rational(22, 7), 0).to_d(3) # => 0.314e1 57 | # 58 | # See also Kernel.BigDecimal. 59 | # 60 | # source://bigdecimal//lib/bigdecimal/util.rb#157 61 | def to_d(*args); end 62 | end 63 | 64 | # source://bigdecimal//lib/bigdecimal/util.rb#171 65 | class NilClass 66 | # call-seq: 67 | # nil.to_d -> bigdecimal 68 | # 69 | # Returns nil represented as a BigDecimal. 70 | # 71 | # require 'bigdecimal' 72 | # require 'bigdecimal/util' 73 | # 74 | # nil.to_d # => 0.0 75 | # 76 | # source://bigdecimal//lib/bigdecimal/util.rb#182 77 | def to_d; end 78 | end 79 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2006-2012 David Heinemeier Hansson 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining 7 | # a copy of this software and associated documentation files (the 8 | # "Software"), to deal in the Software without restriction, including 9 | # without limitation the rights to use, copy, modify, merge, publish, 10 | # distribute, sublicense, and/or sell copies of the Software, and to 11 | # permit persons to whom the Software is furnished to do so, subject to 12 | # the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be 15 | # included in all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | #++ 25 | 26 | require "uri" 27 | 28 | require "active_support" 29 | require "active_model" 30 | require_relative "active_resource/exceptions" 31 | 32 | module ActiveResource 33 | 34 | URI_PARSER = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::RFC2396_Parser.new 35 | 36 | autoload :Base, "#{__dir__}/active_resource/base.rb" 37 | autoload :Callbacks, "#{__dir__}/active_resource/callbacks.rb" 38 | autoload :Collection, "#{__dir__}/active_resource/collection.rb" 39 | autoload :Connection, "#{__dir__}/active_resource/connection.rb" 40 | autoload :CustomMethods, "#{__dir__}/active_resource/custom_methods.rb" 41 | autoload :Formats, "#{__dir__}/active_resource/formats.rb" 42 | autoload :HttpMock, "#{__dir__}/active_resource/http_mock.rb" 43 | autoload :InheritingHash, "#{__dir__}/active_resource/inheriting_hash.rb" 44 | autoload :Schema, "#{__dir__}/active_resource/schema.rb" 45 | autoload :Singleton, "#{__dir__}/active_resource/singleton.rb" 46 | autoload :Validations, "#{__dir__}/active_resource/validations.rb" 47 | end 48 | 49 | require_relative "active_resource/railtie" if defined?(Rails.application) 50 | -------------------------------------------------------------------------------- /example/consumer/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # The test environment is used exclusively to run your application's 2 | # test suite. You never need to work with it otherwise. Remember that 3 | # your test database is "scratch space" for the test suite and is wiped 4 | # and recreated between test runs. Don't rely on the data there! 5 | 6 | Rails.application.configure do 7 | # Settings specified here will take precedence over those in config/application.rb. 8 | 9 | # While tests run files are not watched, reloading is not necessary. 10 | config.enable_reloading = false 11 | 12 | # Eager loading loads your entire application. When running a single test locally, 13 | # this is usually not necessary, and can slow down your test suite. However, it's 14 | # recommended that you enable it in continuous integration systems to ensure eager 15 | # loading is working properly before deploying your code. 16 | config.eager_load = ENV["CI"].present? 17 | 18 | # Configure public file server for tests with cache-control for performance. 19 | config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } 20 | 21 | # Show full error reports. 22 | config.consider_all_requests_local = true 23 | config.cache_store = :null_store 24 | 25 | # Render exception templates for rescuable exceptions and raise for other exceptions. 26 | config.action_dispatch.show_exceptions = :rescuable 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | 31 | # Tell Action Mailer not to deliver emails to the real world. 32 | # The :test delivery method accumulates sent emails in the 33 | # ActionMailer::Base.deliveries array. 34 | config.action_mailer.delivery_method = :test 35 | 36 | # Set host to be used by links generated in mailer templates. 37 | config.action_mailer.default_url_options = { host: "example.com" } 38 | 39 | # Print deprecation notices to the stderr. 40 | config.active_support.deprecation = :stderr 41 | 42 | # Raises error for missing translations. 43 | # config.i18n.raise_on_missing_translations = true 44 | 45 | # Annotate rendered view with file names. 46 | # config.action_view.annotate_rendered_view_with_filenames = true 47 | 48 | # Raise error when a before_action's only/except options reference missing actions. 49 | config.action_controller.raise_on_missing_callback_actions = true 50 | end 51 | -------------------------------------------------------------------------------- /example/provider/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # The test environment is used exclusively to run your application's 2 | # test suite. You never need to work with it otherwise. Remember that 3 | # your test database is "scratch space" for the test suite and is wiped 4 | # and recreated between test runs. Don't rely on the data there! 5 | 6 | Rails.application.configure do 7 | # Settings specified here will take precedence over those in config/application.rb. 8 | 9 | # While tests run files are not watched, reloading is not necessary. 10 | config.enable_reloading = false 11 | 12 | # Eager loading loads your entire application. When running a single test locally, 13 | # this is usually not necessary, and can slow down your test suite. However, it's 14 | # recommended that you enable it in continuous integration systems to ensure eager 15 | # loading is working properly before deploying your code. 16 | config.eager_load = ENV["CI"].present? 17 | 18 | # Configure public file server for tests with cache-control for performance. 19 | config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } 20 | 21 | # Show full error reports. 22 | config.consider_all_requests_local = true 23 | config.cache_store = :null_store 24 | 25 | # Render exception templates for rescuable exceptions and raise for other exceptions. 26 | config.action_dispatch.show_exceptions = :rescuable 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | 31 | # Tell Action Mailer not to deliver emails to the real world. 32 | # The :test delivery method accumulates sent emails in the 33 | # ActionMailer::Base.deliveries array. 34 | config.action_mailer.delivery_method = :test 35 | 36 | # Set host to be used by links generated in mailer templates. 37 | config.action_mailer.default_url_options = { host: "example.com" } 38 | 39 | # Print deprecation notices to the stderr. 40 | config.active_support.deprecation = :stderr 41 | 42 | # Raises error for missing translations. 43 | # config.i18n.raise_on_missing_translations = true 44 | 45 | # Annotate rendered view with file names. 46 | # config.action_view.annotate_rendered_view_with_filenames = true 47 | 48 | # Raise error when a before_action's only/except options reference missing actions. 49 | config.action_controller.raise_on_missing_callback_actions = true 50 | end 51 | -------------------------------------------------------------------------------- /example/provider/app/controllers/people_controller.rb: -------------------------------------------------------------------------------- 1 | class PeopleController < ApplicationController 2 | include ActionController::HttpAuthentication::Basic::ControllerMethods 3 | http_basic_authenticate_with name: "admin", password: "secret" 4 | 5 | # Index expects a response with a 200 status code 6 | # @see https://github.com/rails/activeresource#find 7 | def index 8 | persons = Person.includes(:addresses, :company) 9 | render json: persons.as_json(include: {addresses: {}, company: {}}) 10 | end 11 | 12 | # Show expects a single JSON object with a 200 status code 13 | # If the record is not found, expects a 404 status code 14 | # @see https://github.com/rails/activeresource#find 15 | def show 16 | person = Person.includes(:addresses, :company).find_by(id: params[:id]) 17 | if person 18 | render json: person.as_json(include: {addresses: {}, company: {}}) 19 | else 20 | head :not_found 21 | end 22 | end 23 | 24 | # Create expects an empty response with 201 status and a Location header 25 | # @see https://github.com/rails/activeresource#update 26 | def create 27 | person = Person.new(person_params) 28 | if person.save 29 | head :created, location: person_url(person) 30 | else 31 | render json: person.errors, status: :unprocessable_entity 32 | end 33 | end 34 | 35 | # Update expects an empty response with a 204 status code 36 | # @see https://github.com/rails/activeresource#update 37 | def update 38 | person = Person.find_by(id: params[:id]) 39 | if person&.update(person_params) 40 | head :no_content 41 | else 42 | render json: person&.errors || {error: "Person not found"}, status: :unprocessable_entity 43 | end 44 | end 45 | 46 | # Destroy expects an empty response with a 200 status code 47 | # @see https://github.com/rails/activeresource#delete 48 | def destroy 49 | person = Person.find_by(id: params[:id]) 50 | if person&.destroy 51 | head :ok 52 | else 53 | render json: person&.errors || {error: "Person not found"}, status: :unprocessable_entity 54 | end 55 | end 56 | 57 | private 58 | 59 | # Strong parameters for Person 60 | def person_params 61 | params.require(:person).permit( 62 | :first_name, :last_name, :age, 63 | addresses_attributes: [:id, :street, :city, :state, :zip, :_destroy], 64 | company_attributes: [:id, :name, :street, :city, :state, :zip, :_destroy] 65 | ) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/reflection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "active_support/core_ext/class/attribute" 4 | require "active_support/core_ext/module/deprecation" 5 | 6 | module ActiveResource 7 | # = Active Resource reflection 8 | # 9 | # Associations in ActiveResource would be used to resolve nested attributes 10 | # in a response with correct classes. 11 | # Now they could be specified over Associations with the options :class_name 12 | module Reflection # :nodoc: 13 | extend ActiveSupport::Concern 14 | 15 | included do 16 | class_attribute :reflections 17 | self.reflections = {} 18 | end 19 | 20 | module ClassMethods 21 | def create_reflection(macro, name, options) 22 | reflection = AssociationReflection.new(macro, name, options) 23 | self.reflections = self.reflections.merge(name => reflection) 24 | reflection 25 | end 26 | end 27 | 28 | 29 | class AssociationReflection 30 | def initialize(macro, name, options) 31 | @macro, @name, @options = macro, name, options 32 | end 33 | 34 | # Returns the name of the macro. 35 | # 36 | # has_many :clients returns :clients 37 | attr_reader :name 38 | 39 | # Returns the macro type. 40 | # 41 | # has_many :clients returns :has_many 42 | attr_reader :macro 43 | 44 | # Returns the hash of options used for the macro. 45 | # 46 | # has_many :clients returns +{}+ 47 | attr_reader :options 48 | 49 | # Returns the class for the macro. 50 | # 51 | # has_many :clients returns the Client class 52 | def klass 53 | @klass ||= class_name.constantize 54 | end 55 | 56 | # Returns the class name for the macro. 57 | # 58 | # has_many :clients returns 'Client' 59 | def class_name 60 | @class_name ||= derive_class_name 61 | end 62 | 63 | # Returns the foreign_key for the macro. 64 | def foreign_key 65 | @foreign_key ||= derive_foreign_key 66 | end 67 | 68 | private 69 | def derive_class_name 70 | options[:class_name] ? options[:class_name].to_s.camelize : name.to_s.classify 71 | end 72 | 73 | def derive_foreign_key 74 | options[:foreign_key] ? options[:foreign_key].to_s : "#{name.to_s.downcase}_id" 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.2.0] - 2024-04-24 2 | - Allow virtual attributes to be persisted into cache with introduction of `ActiveCachedResource::Collection.persisted_attribute`. 3 | - Optimized cache key generation by switching from `SHA-256` to `MD5`, improving hashing performance while maintaining uniqueness. 4 | 5 | ## [0.1.10] - 2024-03-05 6 | - Patch Collection#reload to return correct type 7 | 8 | ## [0.1.9] - 2024-02-05 9 | - Fixed bug not allowing reloading collection cache 10 | - Change debug to info for cache hits 11 | 12 | ## [0.1.8] - 2024-01-24 13 | - Fixed issue with `#clear_cache` method with Redis 14 | 15 | ## [0.1.7] - 2024-01-23 16 | - Improved `clear_cache` method for `active_support_cache` strategy to no longer use `delete_matched`. 17 | - Introduced `.delete_from_cache` method to delete single cached resources. 18 | 19 | ## [0.1.6] - 2024-01-15 20 | - Renamed `ActiveResource::Collection#refresh` to `#reload` to match Rails ORM naming convention. 21 | - Added a `ActiveResource::Collection.none` class method similar to Rails `ActiveRecord::QueryMethods.none` 22 | - Enhanced `ActiveResource::Collection#where` so that it returns `ActiveResource::Collection.none` if `resource_class` is missing. 23 | - This is useful for when `where` clauses are chained on an empty `ActiveResource::Collection` 24 | 25 | ## [0.1.5] - 2024-01-09 26 | - Added callbacks to cache: 27 | - Create/POST, refresh cache after successful POST request 28 | - Update/PUT, refresh cache after successful PUT request 29 | - Destroy/DELETE, invalidate cache after successful DELETE request 30 | - Fixed issue with generator for SQLCache strategy tables 31 | 32 | ## [0.1.4] - 2024-12-20 33 | - CI Improvements 34 | - Added annotations 35 | - Runs tests from ActiveResource 36 | - Separated Collection caching logic into ActiveCachedResource::Collection as opposed to monkey patching ActiveResource::Collection 37 | - Changed name of SQL adapter from `active_record` to `active_record_sql` 38 | - Changed method name of ActiveCachedResource::Model from `clear` to `clear_cache` 39 | 40 | ## [0.1.3] - 2024-12-19 41 | - Minor patch on ActiveResource. Removed deprecator log. 42 | 43 | ## [0.1.2] - 2024-12-19 44 | - Minor patch on ActiveResource::Collection initializing attributes. 45 | 46 | ## [0.1.1] - 2024-12-17 47 | 48 | - Added ruby yard documentation 49 | - Added LICENSE 50 | - Improved gemspec 51 | - Changed name of ActiveSupport::Cache adapter from `active_support` to `active_support_cache` 52 | 53 | 54 | ## [0.1.0] - 2024-12-16 55 | 56 | - Initial release 57 | -------------------------------------------------------------------------------- /lib/active_cached_resource/model.rb: -------------------------------------------------------------------------------- 1 | require "active_support/concern" 2 | 3 | require_relative "caching" 4 | require_relative "configuration" 5 | 6 | module ActiveCachedResource 7 | module Model 8 | extend ActiveSupport::Concern 9 | 10 | included do 11 | before_save :invalidate_cache 12 | 13 | after_save :save_to_cache 14 | after_destroy :invalidate_cache 15 | 16 | class << self 17 | attr_accessor :cached_resource 18 | 19 | # Sets up caching for an ActiveResource model. 20 | # 21 | # @param options [Hash] A hash of options to customize the configuration. 22 | # @option options [Symbol] :cache_store The cache store to be used. Must be a CachingStrategies::Base instance. 23 | # @option options [Symbol] :cache_strategy The cache strategy to be used. One of :active_record_sql or :active_support_cache. 24 | # @option options [String] :cache_key_prefix The prefix for cache keys (default: model name underscored). 25 | # @option options [Logger] :logger The logger instance to be used (default: ActiveCachedResource::Logger). 26 | # @option options [Boolean] :enabled Whether caching is enabled (default: true). 27 | # @option options [Integer] :ttl The time-to-live for cache entries in seconds (default: 86400). 28 | # 29 | # @return [ActiveCachedResource::Configuration] The configuration instance. 30 | # 31 | # @note If `cache_store` is provided and is a CachingStrategies::Base instance, it will be used as the cache strategy. 32 | # Otherwise, `cache_strategy` must be provided to determine the cache strategy. 33 | def cached_resource(options = {}) 34 | @cached_resource || setup_cached_resource!(options) 35 | end 36 | 37 | # :nodoc: 38 | def setup_cached_resource!(options) 39 | @cached_resource = ActiveCachedResource::Configuration.new(self, options) 40 | include ActiveCachedResource::Caching 41 | @cached_resource 42 | end 43 | # :nodoc: 44 | end 45 | end 46 | 47 | # :nodoc: 48 | module ClassMethods 49 | def inherited(child) 50 | child.cached_resource = cached_resource if defined?(@cached_resource) 51 | super 52 | end 53 | end 54 | # :nodoc: 55 | 56 | private 57 | 58 | def invalidate_cache 59 | self.class.delete_from_cache(id.to_s) 60 | end 61 | 62 | def save_to_cache 63 | self.class.send(:cache_write, self, id) 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/threadsafe_attributes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "active_support/core_ext/object/duplicable" 4 | 5 | module ThreadsafeAttributes 6 | def self.included(klass) 7 | klass.extend(ClassMethods) 8 | end 9 | 10 | module ClassMethods 11 | def threadsafe_attribute(*attrs) 12 | main_thread = Thread.main # remember this, because it could change after forking 13 | 14 | attrs.each do |attr| 15 | define_method attr do 16 | get_threadsafe_attribute(attr, main_thread) 17 | end 18 | 19 | define_method "#{attr}=" do |value| 20 | set_threadsafe_attribute(attr, value, main_thread) 21 | end 22 | 23 | define_method "#{attr}_defined?" do 24 | threadsafe_attribute_defined?(attr, main_thread) 25 | end 26 | end 27 | end 28 | end 29 | 30 | private 31 | def get_threadsafe_attribute(name, main_thread) 32 | if threadsafe_attribute_defined_by_thread?(name, Thread.current) 33 | get_threadsafe_attribute_by_thread(name, Thread.current) 34 | elsif threadsafe_attribute_defined_by_thread?(name, main_thread) 35 | value = get_threadsafe_attribute_by_thread(name, main_thread) 36 | value = value.dup if value.duplicable? 37 | set_threadsafe_attribute_by_thread(name, value, Thread.current) 38 | value 39 | end 40 | end 41 | 42 | def set_threadsafe_attribute(name, value, main_thread) 43 | set_threadsafe_attribute_by_thread(name, value, Thread.current) 44 | unless threadsafe_attribute_defined_by_thread?(name, main_thread) 45 | set_threadsafe_attribute_by_thread(name, value, main_thread) 46 | end 47 | end 48 | 49 | def threadsafe_attribute_defined?(name, main_thread) 50 | threadsafe_attribute_defined_by_thread?(name, Thread.current) || ((Thread.current != main_thread) && threadsafe_attribute_defined_by_thread?(name, main_thread)) 51 | end 52 | 53 | def get_threadsafe_attribute_by_thread(name, thread) 54 | thread.thread_variable_get "active.resource.#{name}.#{self.object_id}" 55 | end 56 | 57 | def set_threadsafe_attribute_by_thread(name, value, thread) 58 | thread.thread_variable_set "active.resource.#{name}.#{self.object_id}.defined", true 59 | thread.thread_variable_set "active.resource.#{name}.#{self.object_id}", value 60 | end 61 | 62 | def threadsafe_attribute_defined_by_thread?(name, thread) 63 | thread.thread_variable_get "active.resource.#{name}.#{self.object_id}.defined" 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/activeresource/lib/active_resource/exceptions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveResource 4 | class ConnectionError < StandardError # :nodoc: 5 | attr_reader :response 6 | 7 | def initialize(response, message = nil) 8 | @response = response 9 | @message = message 10 | end 11 | 12 | def to_s 13 | return @message if @message 14 | 15 | message = +"Failed." 16 | message << " Response code = #{response.code}." if response.respond_to?(:code) 17 | message << " Response message = #{response.message}." if response.respond_to?(:message) 18 | message 19 | end 20 | end 21 | 22 | # Raised when a Timeout::Error occurs. 23 | class TimeoutError < ConnectionError 24 | def initialize(message) 25 | @message = message 26 | end 27 | def to_s; @message ; end 28 | end 29 | 30 | # Raised when a OpenSSL::SSL::SSLError occurs. 31 | class SSLError < ConnectionError 32 | def initialize(message) 33 | @message = message 34 | end 35 | def to_s; @message ; end 36 | end 37 | 38 | # 3xx Redirection 39 | class Redirection < ConnectionError # :nodoc: 40 | def to_s 41 | response["Location"] ? "#{super} => #{response['Location']}" : super 42 | end 43 | end 44 | 45 | class MissingPrefixParam < ArgumentError # :nodoc: 46 | end 47 | 48 | # 4xx Client Error 49 | class ClientError < ConnectionError # :nodoc: 50 | end 51 | 52 | # 400 Bad Request 53 | class BadRequest < ClientError # :nodoc: 54 | end 55 | 56 | # 401 Unauthorized 57 | class UnauthorizedAccess < ClientError # :nodoc: 58 | end 59 | 60 | # 402 Payment Required 61 | class PaymentRequired < ClientError # :nodoc: 62 | end 63 | 64 | # 403 Forbidden 65 | class ForbiddenAccess < ClientError # :nodoc: 66 | end 67 | 68 | # 404 Not Found 69 | class ResourceNotFound < ClientError # :nodoc: 70 | end 71 | 72 | # 409 Conflict 73 | class ResourceConflict < ClientError # :nodoc: 74 | end 75 | 76 | # 410 Gone 77 | class ResourceGone < ClientError # :nodoc: 78 | end 79 | 80 | # 412 Precondition Failed 81 | class PreconditionFailed < ClientError # :nodoc: 82 | end 83 | 84 | # 429 Too Many Requests 85 | class TooManyRequests < ClientError # :nodoc: 86 | end 87 | 88 | # 5xx Server Error 89 | class ServerError < ConnectionError # :nodoc: 90 | end 91 | 92 | # 405 Method Not Allowed 93 | class MethodNotAllowed < ClientError # :nodoc: 94 | def allowed_methods 95 | @response["Allow"].split(",").map { |verb| verb.strip.downcase.to_sym } 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /example/consumer/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Make code changes take effect immediately without server restart. 7 | config.enable_reloading = true 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable server timing. 16 | config.server_timing = true 17 | 18 | # Enable/disable Action Controller caching. By default Action Controller caching is disabled. 19 | # Run rails dev:cache to toggle Action Controller caching. 20 | if Rails.root.join("tmp/caching-dev.txt").exist? 21 | config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" } 22 | else 23 | config.action_controller.perform_caching = false 24 | end 25 | 26 | # Change to :null_store to avoid any caching. 27 | config.cache_store = :memory_store 28 | 29 | # Don't care if the mailer can't send. 30 | config.action_mailer.raise_delivery_errors = false 31 | 32 | # Make template changes take effect immediately. 33 | config.action_mailer.perform_caching = false 34 | 35 | # Set localhost to be used by links generated in mailer templates. 36 | config.action_mailer.default_url_options = { host: "localhost", port: 3000 } 37 | 38 | # Print deprecation notices to the Rails logger. 39 | config.active_support.deprecation = :log 40 | 41 | # Raise an error on page load if there are pending migrations. 42 | config.active_record.migration_error = :page_load 43 | 44 | # Highlight code that triggered database queries in logs. 45 | config.active_record.verbose_query_logs = true 46 | 47 | # Append comments with runtime information tags to SQL queries in logs. 48 | config.active_record.query_log_tags_enabled = true 49 | 50 | # Highlight code that enqueued background job in logs. 51 | config.active_job.verbose_enqueue_logs = true 52 | 53 | # Raises error for missing translations. 54 | # config.i18n.raise_on_missing_translations = true 55 | 56 | # Annotate rendered view with file names. 57 | config.action_view.annotate_rendered_view_with_filenames = true 58 | 59 | # Raise error when a before_action's only/except options reference missing actions. 60 | config.action_controller.raise_on_missing_callback_actions = true 61 | 62 | # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. 63 | # config.generators.apply_rubocop_autocorrect_after_generate! 64 | end 65 | -------------------------------------------------------------------------------- /example/provider/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Make code changes take effect immediately without server restart. 7 | config.enable_reloading = true 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable server timing. 16 | config.server_timing = true 17 | 18 | # Enable/disable Action Controller caching. By default Action Controller caching is disabled. 19 | # Run rails dev:cache to toggle Action Controller caching. 20 | if Rails.root.join("tmp/caching-dev.txt").exist? 21 | config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" } 22 | else 23 | config.action_controller.perform_caching = false 24 | end 25 | 26 | # Change to :null_store to avoid any caching. 27 | config.cache_store = :memory_store 28 | 29 | # Don't care if the mailer can't send. 30 | config.action_mailer.raise_delivery_errors = false 31 | 32 | # Make template changes take effect immediately. 33 | config.action_mailer.perform_caching = false 34 | 35 | # Set localhost to be used by links generated in mailer templates. 36 | config.action_mailer.default_url_options = { host: "localhost", port: 3000 } 37 | 38 | # Print deprecation notices to the Rails logger. 39 | config.active_support.deprecation = :log 40 | 41 | # Raise an error on page load if there are pending migrations. 42 | config.active_record.migration_error = :page_load 43 | 44 | # Highlight code that triggered database queries in logs. 45 | config.active_record.verbose_query_logs = true 46 | 47 | # Append comments with runtime information tags to SQL queries in logs. 48 | config.active_record.query_log_tags_enabled = true 49 | 50 | # Highlight code that enqueued background job in logs. 51 | config.active_job.verbose_enqueue_logs = true 52 | 53 | # Raises error for missing translations. 54 | # config.i18n.raise_on_missing_translations = true 55 | 56 | # Annotate rendered view with file names. 57 | config.action_view.annotate_rendered_view_with_filenames = true 58 | 59 | # Raise error when a before_action's only/except options reference missing actions. 60 | config.action_controller.raise_on_missing_callback_actions = true 61 | 62 | # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. 63 | # config.generators.apply_rubocop_autocorrect_after_generate! 64 | end 65 | -------------------------------------------------------------------------------- /lib/activeresource/test/cases/reflection_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "abstract_unit" 4 | 5 | require "fixtures/person" 6 | require "fixtures/customer" 7 | 8 | 9 | 10 | class ReflectionTest < ActiveSupport::TestCase 11 | def test_correct_class_attributes 12 | object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, {}) 13 | assert_equal :people, object.name 14 | assert_equal :test, object.macro 15 | assert_equal({}, object.options) 16 | end 17 | 18 | def test_correct_class_name_matching_without_class_name 19 | object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, {}) 20 | assert_equal Person, object.klass 21 | end 22 | 23 | def test_correct_class_name_matching_as_string 24 | object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, class_name: "Person") 25 | assert_equal Person, object.klass 26 | end 27 | 28 | def test_correct_class_name_matching_as_symbol 29 | object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, class_name: :person) 30 | assert_equal Person, object.klass 31 | end 32 | 33 | def test_correct_class_name_matching_as_class 34 | object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, class_name: Person) 35 | assert_equal Person, object.klass 36 | end 37 | 38 | def test_correct_class_name_matching_as_string_with_namespace 39 | object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, class_name: "external/person") 40 | assert_equal External::Person, object.klass 41 | end 42 | 43 | def test_correct_class_name_matching_as_plural_string_with_namespace 44 | object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, class_name: "external/profile_data") 45 | assert_equal External::ProfileData, object.klass 46 | end 47 | 48 | def test_foreign_key_method_with_no_foreign_key_option 49 | object = ActiveResource::Reflection::AssociationReflection.new(:test, :person, {}) 50 | assert_equal "person_id", object.foreign_key 51 | end 52 | 53 | def test_foreign_key_method_with_with_foreign_key_option 54 | object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, foreign_key: "client_id") 55 | assert_equal "client_id", object.foreign_key 56 | end 57 | 58 | def test_creation_of_reflection 59 | Person.reflections = {} 60 | object = Person.create_reflection(:test, :people, {}) 61 | assert_equal ActiveResource::Reflection::AssociationReflection, object.class 62 | assert_equal 1, Person.reflections.count 63 | assert_equal Person, Person.reflections[:people].klass 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/active_cached_resource/caching_strategies/base_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ActiveCachedResource::CachingStrategies::Base do 2 | let(:caching_strategy) { described_class.new } 3 | let(:key) { "test-key" } 4 | let(:value) { "test-value" } 5 | let(:options) { {expires_in: 60} } 6 | 7 | describe "#read" do 8 | it "will raise a NotImplementedError" do 9 | expect { 10 | caching_strategy.read("prefix#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}key") 11 | }.to raise_error(NotImplementedError, /must implement `read_raw`/) 12 | end 13 | end 14 | 15 | describe "#write" do 16 | it "will raise a NotImplementedError" do 17 | expect { 18 | caching_strategy.write("prefix#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}key", "", {expires_in: 3600}) 19 | }.to raise_error(NotImplementedError, /must implement `write_raw`/) 20 | end 21 | end 22 | 23 | describe "#clear" do 24 | it "will raise a NotImplementedError" do 25 | expect { caching_strategy.clear("") }.to raise_error(NotImplementedError, /must implement `clear_raw`/) 26 | end 27 | end 28 | 29 | describe "#hash_key" do 30 | it "generates a hashed key with a prefix and SHA256 digest" do 31 | key = "prefix#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}mykey" 32 | hashed_key = caching_strategy.send(:hash_key, key) 33 | 34 | prefix, digest = hashed_key.split(ActiveCachedResource::Constants::PREFIX_SEPARATOR) 35 | expect(prefix).to eq("prefix") 36 | expect(digest).to eq(Digest::MD5.hexdigest("mykey")) 37 | end 38 | 39 | it "handles keys with multiple dashes" do 40 | key = "prefix#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}part1#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}part2" 41 | hashed_key = caching_strategy.send(:hash_key, key) 42 | 43 | prefix, digest = hashed_key.split(ActiveCachedResource::Constants::PREFIX_SEPARATOR) 44 | expect(prefix).to eq("prefix") 45 | expect(digest).to eq(Digest::MD5.hexdigest("part1#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}part2")) 46 | end 47 | 48 | context "Invalid keys" do 49 | it "handles keys without a prefix gracefully" do 50 | key = "mykey" 51 | 52 | expect { 53 | caching_strategy.send(:hash_key, key) 54 | }.to raise_error(ArgumentError, "Key must have a prefix and a key separated by a dash") 55 | end 56 | 57 | it "raises error on an empty key" do 58 | key = "" 59 | expect { 60 | caching_strategy.send(:hash_key, key) 61 | }.to raise_error(ArgumentError, "Key must have a prefix and a key separated by a dash") 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/active_cached_resource/collection.rb: -------------------------------------------------------------------------------- 1 | module ActiveCachedResource 2 | class Collection < ActiveResource::Collection 3 | class_attribute :virtual_persisted_attributes, default: [] 4 | 5 | # This method dynamically creates accessor methods for the specified attributes 6 | # and adds them to a list of virtual persisted attributes to keep track of 7 | # attributes that should be persisted to cache. 8 | # 9 | # @param args [Array] A list of attribute names to be persisted. 10 | # @return [void] 11 | def self.persisted_attribute(*args) 12 | attr_accessor(*args) 13 | self.virtual_persisted_attributes += args 14 | end 15 | 16 | def virtual_persistable_attributes 17 | self.class.virtual_persisted_attributes.each_with_object({}) do |attribute, hash| 18 | hash[attribute] = public_send(attribute) 19 | end 20 | end 21 | 22 | # Reload the collection by re-fetching the resources from the API. 23 | # 24 | # @return Returns [Array] The collection of resources retrieved from the API. 25 | def reload 26 | query_params[Constants::RELOAD_PARAM] = true 27 | super 28 | end 29 | 30 | private 31 | 32 | def request_resources! 33 | return @elements if requested? || resource_class.nil? 34 | 35 | # Delete the reload param from query params. 36 | # This is drilled down via `params` option to determine if the collection should be reloaded 37 | should_reload = query_params.delete(Constants::RELOAD_PARAM) 38 | if !should_reload 39 | from_cache = resource_class.send(:cache_read, from, path_params, query_params, prefix_options) 40 | if from_cache 41 | update_self!(from_cache) 42 | return @elements if @elements 43 | end 44 | end 45 | 46 | super # This sets @elements 47 | 48 | if resource_class.send(:should_cache?, @elements) 49 | resource_class.send(:cache_write, self, from, path_params, query_params, prefix_options) 50 | end 51 | 52 | @elements 53 | ensure 54 | @requested = true 55 | end 56 | 57 | def update_self!(other_collection) 58 | # Ensure that the virtual persisted attributes are also updated 59 | self.class.virtual_persisted_attributes.each do |attribute| 60 | public_send(:"#{attribute}=", other_collection.public_send(attribute)) 61 | end 62 | 63 | @elements = other_collection.instance_variable_get(:@elements) 64 | @from = other_collection.instance_variable_get(:@from) 65 | @query_params = other_collection.query_params 66 | @path_params = other_collection.path_params 67 | @prefix_options = other_collection.prefix_options 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | concurrency: 3 | group: ${{ github.ref }}-${{ github.workflow }}-release 4 | cancel-in-progress: true 5 | on: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | ensure_main_branch: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check if running on main branch 13 | run: | 14 | if [ "${GITHUB_REF}" != "refs/heads/main" ]; then 15 | echo "This workflow can only be run from the main branch." 16 | exit 1 17 | fi 18 | lint: 19 | needs: [ensure_main_branch] 20 | permissions: 21 | checks: write 22 | contents: write 23 | uses: ./.github/workflows/lint.yml 24 | test: 25 | permissions: 26 | contents: read 27 | checks: write 28 | needs: [ensure_main_branch] 29 | uses: ./.github/workflows/test.yml 30 | version: 31 | permissions: 32 | contents: write 33 | runs-on: ubuntu-latest 34 | env: 35 | BUNDLE_ONLY: versioning 36 | needs: [lint, test] 37 | steps: 38 | - uses: actions/checkout@v4 39 | with: 40 | ref: main 41 | ssh-key: ${{ secrets.DEPLOY_KEY }} 42 | - name: Set up Ruby 43 | uses: ruby/setup-ruby@v1.197.0 44 | with: 45 | ruby-version: 3.2.2 46 | bundler-cache: true 47 | - name: Get changed files 48 | id: changed-files 49 | uses: tj-actions/changed-files@v46 50 | with: 51 | files: "lib/active_cached_resource/version.rb" 52 | - name: Bump Patch Version 53 | id: semver 54 | if: steps.changed-files.outputs.any_changed == 'false' 55 | run: | 56 | bundle exec bump patch --no-commit 57 | echo "semver=$(bundle exec bump current)" >> $GITHUB_OUTPUT 58 | - name: Commit Version Bump 59 | if: steps.changed-files.outputs.any_changed == 'false' 60 | uses: EndBug/add-and-commit@v9 61 | with: 62 | add: "lib/active_cached_resource/version.rb" 63 | default_author: github_actions 64 | message: "Bump version to ${{ steps.semver.outputs.semver }}" 65 | release: 66 | name: Release gem to RubyGems.org 67 | needs: [version] 68 | runs-on: ubuntu-latest 69 | env: 70 | BUNDLE_WITHOUT: 'versioning:development:test' 71 | permissions: 72 | id-token: write 73 | contents: write 74 | steps: 75 | - name: Checkout 76 | uses: actions/checkout@v4 77 | with: 78 | ref: main 79 | ssh-key: ${{ secrets.DEPLOY_KEY }} 80 | - name: Set up Ruby 81 | uses: ruby/setup-ruby@v1.197.0 82 | with: 83 | ruby-version: 3.2.2 84 | bundler-cache: true 85 | - name: Update RubyGems 86 | run: gem update --system 87 | - uses: rubygems/release-gem@v1 88 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/activejob@8.0.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `activejob` gem. 5 | # Please instead update this file by running `bin/tapioca gem activejob`. 6 | 7 | 8 | # :markup: markdown 9 | # :include: ../README.md 10 | # 11 | # source://activejob//lib/active_job/serializers/object_serializer.rb#5 12 | module ActiveJob; end 13 | 14 | # = Active Job \Serializers 15 | # 16 | # The +ActiveJob::Serializers+ module is used to store a list of known serializers 17 | # and to add new ones. It also has helpers to serialize/deserialize objects. 18 | # 19 | # source://activejob//lib/active_job/serializers/object_serializer.rb#6 20 | module ActiveJob::Serializers; end 21 | 22 | # Base class for serializing and deserializing custom objects. 23 | # 24 | # Example: 25 | # 26 | # class MoneySerializer < ActiveJob::Serializers::ObjectSerializer 27 | # def serialize(money) 28 | # super("amount" => money.amount, "currency" => money.currency) 29 | # end 30 | # 31 | # def deserialize(hash) 32 | # Money.new(hash["amount"], hash["currency"]) 33 | # end 34 | # 35 | # private 36 | # 37 | # def klass 38 | # Money 39 | # end 40 | # end 41 | # 42 | # source://activejob//lib/active_job/serializers/object_serializer.rb#26 43 | class ActiveJob::Serializers::ObjectSerializer 44 | include ::Singleton 45 | extend ::Singleton::SingletonClassMethods 46 | 47 | # Deserializes an argument from a JSON primitive type. 48 | # 49 | # @raise [NotImplementedError] 50 | # 51 | # source://activejob//lib/active_job/serializers/object_serializer.rb#44 52 | def deserialize(json); end 53 | 54 | # Serializes an argument to a JSON primitive type. 55 | # 56 | # source://activejob//lib/active_job/serializers/object_serializer.rb#39 57 | def serialize(hash); end 58 | 59 | # Determines if an argument should be serialized by a serializer. 60 | # 61 | # @return [Boolean] 62 | # 63 | # source://activejob//lib/active_job/serializers/object_serializer.rb#34 64 | def serialize?(argument); end 65 | 66 | private 67 | 68 | # The class of the object that will be serialized. 69 | # 70 | # @raise [NotImplementedError] 71 | # 72 | # source://activejob//lib/active_job/serializers/object_serializer.rb#50 73 | def klass; end 74 | 75 | class << self 76 | # source://activejob//lib/active_job/serializers/object_serializer.rb#30 77 | def deserialize(*_arg0, **_arg1, &_arg2); end 78 | 79 | # source://activejob//lib/active_job/serializers/object_serializer.rb#30 80 | def serialize(*_arg0, **_arg1, &_arg2); end 81 | 82 | # source://activejob//lib/active_job/serializers/object_serializer.rb#30 83 | def serialize?(*_arg0, **_arg1, &_arg2); end 84 | 85 | private 86 | 87 | def allocate; end 88 | def new(*_arg0); end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/support/shared_examples/caching_strategies_examples.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples "a caching strategy" do 2 | let(:cache_instance) { described_class.new(*constructor_args) } 3 | 4 | describe "#read" do 5 | context "when the key exists in the cache" do 6 | it "compresses when writing and decompresses when retrieving" do 7 | value = {"name" => "test"} 8 | key = "test#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}key" 9 | 10 | cache_instance.write(key, value, expires_in: 3600) 11 | expect(cache_instance.read(key)).to eq(value) 12 | end 13 | end 14 | 15 | context "when the key does not exist in the cache" do 16 | it "returns nil" do 17 | expect(cache_instance.read("missing#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}key")).to be_nil 18 | end 19 | end 20 | 21 | context "when decompression fails" do 22 | before do 23 | allow(JSON).to receive(:parse).and_raise(JSON::ParserError) 24 | end 25 | 26 | it "returns nil" do 27 | raw_key = cache_instance.send(:hash_key, "invalid#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}key") 28 | cache_instance.send(:write_raw, raw_key, "invalid data", {expires_in: 3600}) 29 | 30 | expect(cache_instance.read("invalid#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}key")).to be_nil 31 | end 32 | end 33 | end 34 | 35 | describe "#write" do 36 | context "when expires_in option is missing" do 37 | it "raises an ArgumentError" do 38 | expect { 39 | cache_instance.write("test#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}key", "value", {}) 40 | }.to raise_error(ArgumentError, "`expires_in` option is required") 41 | end 42 | end 43 | 44 | context "when write succeeds" do 45 | it "stores the compressed value in the cache" do 46 | value = {"name" => "test"} 47 | key = "test#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}key" 48 | expect(cache_instance.write(key, value, expires_in: 3600)).to be_truthy 49 | end 50 | end 51 | 52 | context "when write fails" do 53 | it "returns false" do 54 | allow(cache_instance).to receive(:write_raw).and_return(false) 55 | expect( 56 | cache_instance.write("test#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}key", "value", expires_in: 3600) 57 | ).to be false 58 | end 59 | end 60 | end 61 | 62 | describe "#clear" do 63 | before do 64 | cache_instance.write("test#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}key", "value", expires_in: 3600) 65 | cache_instance.write("another#{ActiveCachedResource::Constants::PREFIX_SEPARATOR}key", "value", expires_in: 3600) 66 | end 67 | 68 | it "removes matching keys from the cache" do 69 | expect { cache_instance.clear("test") }.to_not raise_error 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/activeresource/test/cases/validations_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "abstract_unit" 4 | require "fixtures/project" 5 | require "active_support/core_ext/hash/conversions" 6 | 7 | # The validations are tested thoroughly under ActiveModel::Validations 8 | # This test case simply makes sure that they are all accessible by 9 | # Active Resource objects. 10 | class ValidationsTest < ActiveSupport::TestCase 11 | VALID_PROJECT_HASH = { name: "My Project", description: "A project" } 12 | def setup 13 | @my_proj = { "person" => VALID_PROJECT_HASH }.to_json 14 | ActiveResource::HttpMock.respond_to do |mock| 15 | mock.post "/projects.json", {}, @my_proj, 201, "Location" => "/projects/5.json" 16 | end 17 | end 18 | 19 | def test_validates_presence_of 20 | p = new_project(name: nil) 21 | assert_not p.valid?, "should not be a valid record without name" 22 | assert_not p.save, "should not have saved an invalid record" 23 | assert_equal ["can't be blank"], p.errors[:name], "should have an error on name" 24 | 25 | p.name = "something" 26 | 27 | assert p.save, "should have saved after fixing the validation, but had: #{p.errors.inspect}" 28 | end 29 | 30 | def test_fails_save! 31 | p = new_project(name: nil) 32 | assert_raise(ActiveResource::ResourceInvalid) { p.save! } 33 | end 34 | 35 | def test_save_without_validation 36 | p = new_project(name: nil) 37 | assert_not p.save 38 | assert p.save(validate: false) 39 | end 40 | 41 | def test_validate_callback 42 | # we have a callback ensuring the description is longer than three letters 43 | p = new_project(description: "a") 44 | assert_not p.valid?, "should not be a valid record when it fails a validation callback" 45 | assert_not p.save, "should not have saved an invalid record" 46 | assert_equal ["must be greater than three letters long"], p.errors[:description], "should be an error on description" 47 | 48 | # should now allow this description 49 | p.description = "abcd" 50 | assert p.save, "should have saved after fixing the validation, but had: #{p.errors.inspect}" 51 | end 52 | 53 | def test_client_side_validation_maximum 54 | project = Project.new(description: "123456789012345") 55 | assert_not project.valid? 56 | assert_equal ["is too long (maximum is 10 characters)"], project.errors[:description] 57 | end 58 | 59 | def test_invalid_method 60 | p = new_project 61 | 62 | assert_not p.invalid? 63 | end 64 | 65 | def test_validate_bang_method 66 | p = new_project(name: nil) 67 | 68 | assert_raise(ActiveModel::ValidationError) { p.validate! } 69 | end 70 | 71 | protected 72 | # quickie helper to create a new project with all the required 73 | # attributes. 74 | # Pass in any params you specifically want to override 75 | def new_project(opts = {}) 76 | Project.new(VALID_PROJECT_HASH.merge(opts)) 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/rspec@3.13.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `rspec` gem. 5 | # Please instead update this file by running `bin/tapioca gem rspec`. 6 | 7 | 8 | # source://rspec//lib/rspec/version.rb#1 9 | module RSpec 10 | class << self 11 | # source://rspec-core/3.13.2/lib/rspec/core.rb#70 12 | def clear_examples; end 13 | 14 | # source://rspec-core/3.13.2/lib/rspec/core.rb#85 15 | def configuration; end 16 | 17 | # source://rspec-core/3.13.2/lib/rspec/core.rb#49 18 | def configuration=(_arg0); end 19 | 20 | # source://rspec-core/3.13.2/lib/rspec/core.rb#97 21 | def configure; end 22 | 23 | # source://rspec-core/3.13.2/lib/rspec/core.rb#194 24 | def const_missing(name); end 25 | 26 | # source://rspec-core/3.13.2/lib/rspec/core/dsl.rb#42 27 | def context(*args, &example_group_block); end 28 | 29 | # source://rspec-core/3.13.2/lib/rspec/core.rb#122 30 | def current_example; end 31 | 32 | # source://rspec-core/3.13.2/lib/rspec/core.rb#128 33 | def current_example=(example); end 34 | 35 | # source://rspec-core/3.13.2/lib/rspec/core.rb#154 36 | def current_scope; end 37 | 38 | # source://rspec-core/3.13.2/lib/rspec/core.rb#134 39 | def current_scope=(scope); end 40 | 41 | # source://rspec-core/3.13.2/lib/rspec/core/dsl.rb#42 42 | def describe(*args, &example_group_block); end 43 | 44 | # source://rspec-core/3.13.2/lib/rspec/core/dsl.rb#42 45 | def example_group(*args, &example_group_block); end 46 | 47 | # source://rspec-core/3.13.2/lib/rspec/core/dsl.rb#42 48 | def fcontext(*args, &example_group_block); end 49 | 50 | # source://rspec-core/3.13.2/lib/rspec/core/dsl.rb#42 51 | def fdescribe(*args, &example_group_block); end 52 | 53 | # source://rspec-core/3.13.2/lib/rspec/core.rb#58 54 | def reset; end 55 | 56 | # source://rspec-core/3.13.2/lib/rspec/core/shared_example_group.rb#110 57 | def shared_context(name, *args, &block); end 58 | 59 | # source://rspec-core/3.13.2/lib/rspec/core/shared_example_group.rb#110 60 | def shared_examples(name, *args, &block); end 61 | 62 | # source://rspec-core/3.13.2/lib/rspec/core/shared_example_group.rb#110 63 | def shared_examples_for(name, *args, &block); end 64 | 65 | # source://rspec-core/3.13.2/lib/rspec/core.rb#160 66 | def world; end 67 | 68 | # source://rspec-core/3.13.2/lib/rspec/core.rb#49 69 | def world=(_arg0); end 70 | 71 | # source://rspec-core/3.13.2/lib/rspec/core/dsl.rb#42 72 | def xcontext(*args, &example_group_block); end 73 | 74 | # source://rspec-core/3.13.2/lib/rspec/core/dsl.rb#42 75 | def xdescribe(*args, &example_group_block); end 76 | end 77 | end 78 | 79 | # source://rspec//lib/rspec/version.rb#2 80 | module RSpec::Version; end 81 | 82 | # source://rspec//lib/rspec/version.rb#3 83 | RSpec::Version::STRING = T.let(T.unsafe(nil), String) 84 | -------------------------------------------------------------------------------- /lib/activeresource/test/threadsafe_attributes_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "abstract_unit" 4 | 5 | class ThreadsafeAttributesTest < ActiveSupport::TestCase 6 | class TestClass 7 | include ThreadsafeAttributes 8 | threadsafe_attribute :safeattr 9 | end 10 | 11 | setup do 12 | @tester = TestClass.new 13 | end 14 | 15 | test "#threadsafe attributes work in a single thread" do 16 | assert_not @tester.safeattr_defined? 17 | assert_nil @tester.safeattr 18 | @tester.safeattr = "a value" 19 | assert @tester.safeattr_defined? 20 | assert_equal "a value", @tester.safeattr 21 | end 22 | 23 | test "#threadsafe attributes inherit the value of the main thread" do 24 | @tester.safeattr = "a value" 25 | Thread.new do 26 | assert @tester.safeattr_defined? 27 | assert_equal "a value", @tester.safeattr 28 | end.join 29 | assert_equal "a value", @tester.safeattr 30 | end 31 | 32 | test "#changing a threadsafe attribute in a thread does not affect the main thread" do 33 | @tester.safeattr = "a value" 34 | Thread.new do 35 | @tester.safeattr = "a new value" 36 | assert_equal "a new value", @tester.safeattr 37 | end.join 38 | assert_equal "a value", @tester.safeattr 39 | end 40 | 41 | test "#threadsafe attributes inherit the value of the main thread when value is nil/false" do 42 | @tester.safeattr = false 43 | Thread.new do 44 | assert @tester.safeattr_defined? 45 | assert_equal false, @tester.safeattr 46 | end.join 47 | assert_equal false, @tester.safeattr 48 | end 49 | 50 | test "#changing a threadsafe attribute in a thread sets an equal value for the main thread, if no value has been set" do 51 | assert_not @tester.safeattr_defined? 52 | assert_nil @tester.safeattr 53 | Thread.new do 54 | @tester.safeattr = "value from child" 55 | assert_equal "value from child", @tester.safeattr 56 | end.join 57 | assert @tester.safeattr_defined? 58 | assert_equal "value from child", @tester.safeattr 59 | end 60 | 61 | test "#threadsafe attributes can retrieve non-duplicable from main thread" do 62 | @tester.safeattr = :symbol_1 63 | Thread.new do 64 | assert_equal :symbol_1, @tester.safeattr 65 | end.join 66 | end 67 | 68 | test "#threadsafe attributes work in fibers" do 69 | @tester.safeattr = :symbol_1 70 | Fiber.new do 71 | assert_equal :symbol_1, @tester.safeattr 72 | end.resume 73 | end 74 | 75 | unless RUBY_PLATFORM == "java" 76 | test "threadsafe attributes can be accessed after forking within a thread" do 77 | reader, writer = IO.pipe 78 | @tester.safeattr = "a value" 79 | Thread.new do 80 | fork do 81 | reader.close 82 | writer.print(@tester.safeattr) 83 | writer.close 84 | end 85 | end.join 86 | writer.close 87 | assert_equal "a value", reader.read 88 | reader.close 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/active_cached_resource/configuration.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | require_relative "logger" 3 | require_relative "caching_strategies/sql_cache" 4 | require_relative "caching_strategies/active_support_cache" 5 | require_relative "caching_strategies/base" 6 | 7 | module ActiveCachedResource 8 | class Configuration < OpenStruct 9 | CACHING_STRATEGIES = { 10 | active_record_sql: ActiveCachedResource::CachingStrategies::SQLCache, 11 | active_support_cache: ActiveCachedResource::CachingStrategies::ActiveSupportCache 12 | } 13 | 14 | OPTIONS = %i[cache_key_prefix logger enabled ttl] 15 | 16 | # Initializes a new configuration for the given model with the specified options. 17 | # 18 | # @param model [Class] The model class for which the configuration is being set. 19 | # @param options [Hash] A hash of options to customize the configuration. 20 | # @option options [Symbol] :cache_store The cache store to be used. 21 | # @option options [Symbol] :cache_strategy The cache strategy to be used. One of :active_record_sql or :active_support_cache. 22 | # @option options [String] :cache_key_prefix The prefix for cache keys (default: model name underscored). 23 | # @option options [Logger] :logger The logger instance to be used (default: ActiveCachedResource::Logger). 24 | # @option options [Boolean] :enabled Whether caching is enabled (default: true). 25 | # @option options [Integer] :ttl The time-to-live for cache entries in seconds (default: 86400). 26 | # 27 | # @return [ActiveCachedResource::Configuration] The configuration instance. 28 | # 29 | # @note If `cache_store` is provided and is a CachingStrategies::Base instance, it will be used as the cache strategy. 30 | # Otherwise, `cache_strategy` must be provided to determine the cache strategy. 31 | def initialize(model, options = {}) 32 | super( 33 | { 34 | cache: determine_cache_strategy(options[:cache_store], options[:cache_strategy]), 35 | cache_key_prefix: model.name.underscore, 36 | logger: ActiveCachedResource::Logger.new(model.name), 37 | enabled: true, 38 | ttl: 86400 39 | }.merge(options.slice(*OPTIONS)) 40 | ) 41 | end 42 | 43 | # Enables caching. 44 | # 45 | # @return [void] 46 | def on! 47 | self.enabled = true 48 | end 49 | 50 | # Disables caching. 51 | # 52 | # @return [void] 53 | def off! 54 | self.enabled = false 55 | end 56 | 57 | private 58 | 59 | def determine_cache_strategy(cache_store, cache_strategy) 60 | if cache_store.is_a?(CachingStrategies::Base) 61 | cache_store 62 | elsif cache_strategy 63 | CACHING_STRATEGIES.fetch(cache_strategy).new(cache_store) 64 | else 65 | raise ArgumentError, "cache_store must be a CachingStrategies::Base or cache_strategy must be provided" 66 | end 67 | rescue KeyError 68 | raise ArgumentError, "Invalid cache strategy: #{cache_strategy}" 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /example/consumer/.kamal/hooks/pre-deploy.sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # A sample pre-deploy hook 4 | # 5 | # Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds. 6 | # 7 | # Fails unless the combined status is "success" 8 | # 9 | # These environment variables are available: 10 | # KAMAL_RECORDED_AT 11 | # KAMAL_PERFORMER 12 | # KAMAL_VERSION 13 | # KAMAL_HOSTS 14 | # KAMAL_COMMAND 15 | # KAMAL_SUBCOMMAND 16 | # KAMAL_ROLE (if set) 17 | # KAMAL_DESTINATION (if set) 18 | 19 | # Only check the build status for production deployments 20 | if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production" 21 | exit 0 22 | end 23 | 24 | require "bundler/inline" 25 | 26 | # true = install gems so this is fast on repeat invocations 27 | gemfile(true, quiet: true) do 28 | source "https://rubygems.org" 29 | 30 | gem "octokit" 31 | gem "faraday-retry" 32 | end 33 | 34 | MAX_ATTEMPTS = 72 35 | ATTEMPTS_GAP = 10 36 | 37 | def exit_with_error(message) 38 | $stderr.puts message 39 | exit 1 40 | end 41 | 42 | class GithubStatusChecks 43 | attr_reader :remote_url, :git_sha, :github_client, :combined_status 44 | 45 | def initialize 46 | @remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/") 47 | @git_sha = `git rev-parse HEAD`.strip 48 | @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"]) 49 | refresh! 50 | end 51 | 52 | def refresh! 53 | @combined_status = github_client.combined_status(remote_url, git_sha) 54 | end 55 | 56 | def state 57 | combined_status[:state] 58 | end 59 | 60 | def first_status_url 61 | first_status = combined_status[:statuses].find { |status| status[:state] == state } 62 | first_status && first_status[:target_url] 63 | end 64 | 65 | def complete_count 66 | combined_status[:statuses].count { |status| status[:state] != "pending"} 67 | end 68 | 69 | def total_count 70 | combined_status[:statuses].count 71 | end 72 | 73 | def current_status 74 | if total_count > 0 75 | "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..." 76 | else 77 | "Build not started..." 78 | end 79 | end 80 | end 81 | 82 | 83 | $stdout.sync = true 84 | 85 | puts "Checking build status..." 86 | attempts = 0 87 | checks = GithubStatusChecks.new 88 | 89 | begin 90 | loop do 91 | case checks.state 92 | when "success" 93 | puts "Checks passed, see #{checks.first_status_url}" 94 | exit 0 95 | when "failure" 96 | exit_with_error "Checks failed, see #{checks.first_status_url}" 97 | when "pending" 98 | attempts += 1 99 | end 100 | 101 | exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS 102 | 103 | puts checks.current_status 104 | sleep(ATTEMPTS_GAP) 105 | checks.refresh! 106 | end 107 | rescue Octokit::NotFound 108 | exit_with_error "Build status could not be found" 109 | end 110 | -------------------------------------------------------------------------------- /example/provider/.kamal/hooks/pre-deploy.sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # A sample pre-deploy hook 4 | # 5 | # Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds. 6 | # 7 | # Fails unless the combined status is "success" 8 | # 9 | # These environment variables are available: 10 | # KAMAL_RECORDED_AT 11 | # KAMAL_PERFORMER 12 | # KAMAL_VERSION 13 | # KAMAL_HOSTS 14 | # KAMAL_COMMAND 15 | # KAMAL_SUBCOMMAND 16 | # KAMAL_ROLE (if set) 17 | # KAMAL_DESTINATION (if set) 18 | 19 | # Only check the build status for production deployments 20 | if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production" 21 | exit 0 22 | end 23 | 24 | require "bundler/inline" 25 | 26 | # true = install gems so this is fast on repeat invocations 27 | gemfile(true, quiet: true) do 28 | source "https://rubygems.org" 29 | 30 | gem "octokit" 31 | gem "faraday-retry" 32 | end 33 | 34 | MAX_ATTEMPTS = 72 35 | ATTEMPTS_GAP = 10 36 | 37 | def exit_with_error(message) 38 | $stderr.puts message 39 | exit 1 40 | end 41 | 42 | class GithubStatusChecks 43 | attr_reader :remote_url, :git_sha, :github_client, :combined_status 44 | 45 | def initialize 46 | @remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/") 47 | @git_sha = `git rev-parse HEAD`.strip 48 | @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"]) 49 | refresh! 50 | end 51 | 52 | def refresh! 53 | @combined_status = github_client.combined_status(remote_url, git_sha) 54 | end 55 | 56 | def state 57 | combined_status[:state] 58 | end 59 | 60 | def first_status_url 61 | first_status = combined_status[:statuses].find { |status| status[:state] == state } 62 | first_status && first_status[:target_url] 63 | end 64 | 65 | def complete_count 66 | combined_status[:statuses].count { |status| status[:state] != "pending"} 67 | end 68 | 69 | def total_count 70 | combined_status[:statuses].count 71 | end 72 | 73 | def current_status 74 | if total_count > 0 75 | "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..." 76 | else 77 | "Build not started..." 78 | end 79 | end 80 | end 81 | 82 | 83 | $stdout.sync = true 84 | 85 | puts "Checking build status..." 86 | attempts = 0 87 | checks = GithubStatusChecks.new 88 | 89 | begin 90 | loop do 91 | case checks.state 92 | when "success" 93 | puts "Checks passed, see #{checks.first_status_url}" 94 | exit 0 95 | when "failure" 96 | exit_with_error "Checks failed, see #{checks.first_status_url}" 97 | when "pending" 98 | attempts += 1 99 | end 100 | 101 | exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS 102 | 103 | puts checks.current_status 104 | sleep(ATTEMPTS_GAP) 105 | checks.refresh! 106 | end 107 | rescue Octokit::NotFound 108 | exit_with_error "Build status could not be found" 109 | end 110 | -------------------------------------------------------------------------------- /spec/active_cached_resource/configuration_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ActiveCachedResource::Configuration do 2 | let(:model) { double("Model", name: "TestResource") } 3 | let(:cache_store) { double("CacheStore") } 4 | let(:logger) { instance_double(ActiveCachedResource::Logger) } 5 | 6 | before do 7 | allow(ActiveCachedResource::Logger).to receive(:new).with("TestResource").and_return(logger) 8 | end 9 | 10 | describe "#initialize" do 11 | context "with a valid cache_store and cache_strategy" do 12 | it "initializes with the specified caching strategy" do 13 | config = described_class.new(model, cache_store: cache_store, cache_strategy: :active_support_cache) 14 | 15 | expect(config.cache).to be_a(ActiveCachedResource::CachingStrategies::ActiveSupportCache) 16 | expect(config.cache.instance_variable_get(:@cache_store)).to eq(cache_store) 17 | expect(config.cache_key_prefix).to eq("test_resource") 18 | expect(config.logger).to eq(logger) 19 | expect(config.enabled).to be true 20 | expect(config.ttl).to eq(86400) 21 | end 22 | end 23 | 24 | context "with a custom cache_store inheriting from Base" do 25 | let(:custom_cache) { Class.new(ActiveCachedResource::CachingStrategies::Base).new } 26 | 27 | it "uses the custom cache store directly" do 28 | config = described_class.new(model, cache_store: custom_cache) 29 | 30 | expect(config.cache).to eq(custom_cache) 31 | end 32 | end 33 | 34 | context "with an invalid cache_store and cache_strategy" do 35 | it "raises an ArgumentError when neither cache_store nor cache_strategy is provided" do 36 | expect { described_class.new(model) }.to raise_error(ArgumentError, "cache_store must be a CachingStrategies::Base or cache_strategy must be provided") 37 | end 38 | 39 | it "raises an ArgumentError for an invalid cache_strategy" do 40 | expect { 41 | described_class.new(model, cache_store: cache_store, cache_strategy: :invalid_strategy) 42 | }.to raise_error(ArgumentError, "Invalid cache strategy: invalid_strategy") 43 | end 44 | end 45 | 46 | context "with custom options" do 47 | it "overrides default options with provided values" do 48 | config = described_class.new( 49 | model, 50 | cache_store: cache_store, 51 | cache_strategy: :active_support_cache, 52 | cache_key_prefix: "custom_prefix", 53 | enabled: false, 54 | ttl: 3600 55 | ) 56 | 57 | expect(config.cache_key_prefix).to eq("custom_prefix") 58 | expect(config.enabled).to be false 59 | expect(config.ttl).to eq(3600) 60 | end 61 | end 62 | end 63 | 64 | describe "#on!" do 65 | it "enables caching" do 66 | config = described_class.new(model, cache_store: cache_store, cache_strategy: :active_support_cache) 67 | config.off! 68 | 69 | expect(config.enabled).to be false 70 | 71 | config.on! 72 | expect(config.enabled).to be true 73 | end 74 | end 75 | 76 | describe "#off!" do 77 | it "disables caching" do 78 | config = described_class.new(model, cache_store: cache_store, cache_strategy: :active_support_cache) 79 | 80 | config.off! 81 | expect(config.enabled).to be false 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /example/consumer/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../Gemfile", __dir__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_requirement 64 | @bundler_requirement ||= 65 | env_var_version || 66 | cli_arg_version || 67 | bundler_requirement_for(lockfile_version) 68 | end 69 | 70 | def bundler_requirement_for(version) 71 | return "#{Gem::Requirement.default}.a" unless version 72 | 73 | bundler_gem_version = Gem::Version.new(version) 74 | 75 | bundler_gem_version.approximate_recommendation 76 | end 77 | 78 | def load_bundler! 79 | ENV["BUNDLE_GEMFILE"] ||= gemfile 80 | 81 | activate_bundler 82 | end 83 | 84 | def activate_bundler 85 | gem_error = activation_error_handling do 86 | gem "bundler", bundler_requirement 87 | end 88 | return if gem_error.nil? 89 | require_error = activation_error_handling do 90 | require "bundler/version" 91 | end 92 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 93 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 94 | exit 42 95 | end 96 | 97 | def activation_error_handling 98 | yield 99 | nil 100 | rescue StandardError, LoadError => e 101 | e 102 | end 103 | end 104 | 105 | m.load_bundler! 106 | 107 | if m.invoked_as_script? 108 | load Gem.bin_path("bundler", "bundle") 109 | end 110 | --------------------------------------------------------------------------------