From 280dfc2b940af3ab5ec7563e699c9e48066cc2d8 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Tue, 23 Dec 2025 01:37:40 +0900 Subject: [PATCH 1/3] Created a dummy app for testing --- .github/workflows/ci.yml | 38 ++ .gitignore | 3 + .rubocop.yml | 64 ++++ Appraisals | 13 + Gemfile | 14 + Gemfile.lock | 228 +++++++++++- Rakefile | 12 + examples/rails_sample/.gitignore | 19 + examples/rails_sample/README.md | 106 ++++++ examples/rails_sample/Rakefile | 5 + .../app/controllers/game_controller.rb | 82 +++++ .../app/models/application_master.rb | 18 + .../app/models/application_record.rb | 6 + examples/rails_sample/app/models/armor.rb | 17 + .../app/models/concerns/item_receivable.rb | 44 +++ examples/rails_sample/app/models/enemy.rb | 57 +++ examples/rails_sample/app/models/level.rb | 29 ++ examples/rails_sample/app/models/player.rb | 251 +++++++++++++ .../rails_sample/app/models/player_item.rb | 6 + examples/rails_sample/app/models/potion.rb | 14 + examples/rails_sample/app/models/reward.rb | 23 ++ examples/rails_sample/app/models/weapon.rb | 44 +++ .../rails_sample/app/views/game/show.html.erb | 340 ++++++++++++++++++ examples/rails_sample/bin/rails | 6 + examples/rails_sample/config.ru | 5 + examples/rails_sample/config/application.rb | 20 ++ examples/rails_sample/config/boot.rb | 5 + examples/rails_sample/config/database.yml | 16 + examples/rails_sample/config/environment.rb | 5 + .../config/initializers/simple_master.rb | 41 +++ examples/rails_sample/config/routes.rb | 6 + examples/rails_sample/db/schema.rb | 73 ++++ .../rails_sample/fixtures/masters/armors.json | 5 + .../fixtures/masters/enemies.json | 122 +++++++ .../rails_sample/fixtures/masters/levels.json | 112 ++++++ .../fixtures/masters/potions.json | 20 ++ .../fixtures/masters/rewards.json | 16 + .../fixtures/masters/weapons.json | 46 +++ .../rails_sample/fixtures/translations.yml | 17 + examples/rails_sample/lib/json_loader.rb | 30 ++ examples/rails_sample/spec/enemy_spec.rb | 43 +++ .../spec/factories/armor_factory.rb | 9 + .../spec/factories/enemy_factory.rb | 15 + .../spec/factories/gun_factory.rb | 12 + .../spec/factories/level_factory.rb | 14 + .../spec/factories/player_factory.rb | 13 + .../spec/factories/player_item_factory.rb | 10 + .../spec/factories/potion_factory.rb | 8 + .../spec/factories/reward_factory.rb | 9 + .../spec/factories/weapon_factory.rb | 14 + examples/rails_sample/spec/player_spec.rb | 126 +++++++ examples/rails_sample/spec/spec_helper.rb | 43 +++ gemfiles/rails_7_0.gemfile | 21 ++ gemfiles/rails_7_0.gemfile.lock | 293 +++++++++++++++ gemfiles/rails_7_1.gemfile | 21 ++ gemfiles/rails_7_1.gemfile.lock | 293 +++++++++++++++ gemfiles/rails_7_2.gemfile | 21 ++ gemfiles/rails_7_2.gemfile.lock | 293 +++++++++++++++ gemfiles/rails_8_0.gemfile | 21 ++ gemfiles/rails_8_0.gemfile.lock | 292 +++++++++++++++ gemfiles/rails_8_1.gemfile | 21 ++ gemfiles/rails_8_1.gemfile.lock | 292 +++++++++++++++ lib/simple_master.rb | 1 + simple_master.gemspec | 2 +- .../active_record/extension_spec.rb | 40 +++ .../loader/marshal_loader_spec.rb | 26 ++ .../simple_master/loader/query_loader_spec.rb | 48 +++ .../simple_master/master/associations_spec.rb | 40 +++ spec/simple_master/master/cache_spec.rb | 38 ++ spec/simple_master/master/columns_spec.rb | 172 +++++++++ spec/simple_master/master/filterable_spec.rb | 36 ++ spec/simple_master/master/model_spec.rb | 57 +++ spec/simple_master/master/queryable_spec.rb | 76 ++++ spec/simple_master/master/validatable_spec.rb | 10 + spec/simple_master/storage/dataset_spec.rb | 28 ++ spec/simple_master/storage/loader_spec.rb | 64 ++++ spec/spec_helper.rb | 53 +++ spec/support/dataset_helper.rb | 26 ++ 78 files changed, 4568 insertions(+), 11 deletions(-) create mode 100644 .gitignore create mode 100644 Appraisals create mode 100644 Rakefile create mode 100644 examples/rails_sample/.gitignore create mode 100644 examples/rails_sample/README.md create mode 100644 examples/rails_sample/Rakefile create mode 100644 examples/rails_sample/app/controllers/game_controller.rb create mode 100644 examples/rails_sample/app/models/application_master.rb create mode 100644 examples/rails_sample/app/models/application_record.rb create mode 100644 examples/rails_sample/app/models/armor.rb create mode 100644 examples/rails_sample/app/models/concerns/item_receivable.rb create mode 100644 examples/rails_sample/app/models/enemy.rb create mode 100644 examples/rails_sample/app/models/level.rb create mode 100644 examples/rails_sample/app/models/player.rb create mode 100644 examples/rails_sample/app/models/player_item.rb create mode 100644 examples/rails_sample/app/models/potion.rb create mode 100644 examples/rails_sample/app/models/reward.rb create mode 100644 examples/rails_sample/app/models/weapon.rb create mode 100644 examples/rails_sample/app/views/game/show.html.erb create mode 100755 examples/rails_sample/bin/rails create mode 100644 examples/rails_sample/config.ru create mode 100644 examples/rails_sample/config/application.rb create mode 100644 examples/rails_sample/config/boot.rb create mode 100644 examples/rails_sample/config/database.yml create mode 100644 examples/rails_sample/config/environment.rb create mode 100644 examples/rails_sample/config/initializers/simple_master.rb create mode 100644 examples/rails_sample/config/routes.rb create mode 100644 examples/rails_sample/db/schema.rb create mode 100644 examples/rails_sample/fixtures/masters/armors.json create mode 100644 examples/rails_sample/fixtures/masters/enemies.json create mode 100644 examples/rails_sample/fixtures/masters/levels.json create mode 100644 examples/rails_sample/fixtures/masters/potions.json create mode 100644 examples/rails_sample/fixtures/masters/rewards.json create mode 100644 examples/rails_sample/fixtures/masters/weapons.json create mode 100644 examples/rails_sample/fixtures/translations.yml create mode 100644 examples/rails_sample/lib/json_loader.rb create mode 100644 examples/rails_sample/spec/enemy_spec.rb create mode 100644 examples/rails_sample/spec/factories/armor_factory.rb create mode 100644 examples/rails_sample/spec/factories/enemy_factory.rb create mode 100644 examples/rails_sample/spec/factories/gun_factory.rb create mode 100644 examples/rails_sample/spec/factories/level_factory.rb create mode 100644 examples/rails_sample/spec/factories/player_factory.rb create mode 100644 examples/rails_sample/spec/factories/player_item_factory.rb create mode 100644 examples/rails_sample/spec/factories/potion_factory.rb create mode 100644 examples/rails_sample/spec/factories/reward_factory.rb create mode 100644 examples/rails_sample/spec/factories/weapon_factory.rb create mode 100644 examples/rails_sample/spec/player_spec.rb create mode 100644 examples/rails_sample/spec/spec_helper.rb create mode 100644 gemfiles/rails_7_0.gemfile create mode 100644 gemfiles/rails_7_0.gemfile.lock create mode 100644 gemfiles/rails_7_1.gemfile create mode 100644 gemfiles/rails_7_1.gemfile.lock create mode 100644 gemfiles/rails_7_2.gemfile create mode 100644 gemfiles/rails_7_2.gemfile.lock create mode 100644 gemfiles/rails_8_0.gemfile create mode 100644 gemfiles/rails_8_0.gemfile.lock create mode 100644 gemfiles/rails_8_1.gemfile create mode 100644 gemfiles/rails_8_1.gemfile.lock create mode 100644 spec/simple_master/active_record/extension_spec.rb create mode 100644 spec/simple_master/loader/marshal_loader_spec.rb create mode 100644 spec/simple_master/loader/query_loader_spec.rb create mode 100644 spec/simple_master/master/associations_spec.rb create mode 100644 spec/simple_master/master/cache_spec.rb create mode 100644 spec/simple_master/master/columns_spec.rb create mode 100644 spec/simple_master/master/filterable_spec.rb create mode 100644 spec/simple_master/master/model_spec.rb create mode 100644 spec/simple_master/master/queryable_spec.rb create mode 100644 spec/simple_master/master/validatable_spec.rb create mode 100644 spec/simple_master/storage/dataset_spec.rb create mode 100644 spec/simple_master/storage/loader_spec.rb create mode 100644 spec/spec_helper.rb create mode 100644 spec/support/dataset_helper.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f43c1b..a5569ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,3 +15,41 @@ jobs: bundler-cache: true - name: Run RuboCop run: bundle exec rubocop + test: + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + BUNDLE_PATH: vendor/bundle + strategy: + fail-fast: false + matrix: + ruby: + - "3.2" + - "3.3" + - "3.4" + appraisal: + - rails-7_0 + - rails-7_1 + - rails-7_2 + - rails-8_0 + - rails-8_1 + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + + - name: Install system deps + run: sudo apt-get update && sudo apt-get install -y libsqlite3-dev pkg-config + + - name: Install Appraisal gemfiles + run: bundle exec appraisal install + + - name: Run specs (${{ matrix.appraisal }}) + run: bundle exec appraisal ${{ matrix.appraisal }} rspec spec + + - name: Run rails sample specs (${{ matrix.appraisal }}) + run: bundle exec appraisal ${{ matrix.appraisal }} rake rails_sample_spec diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f02cc6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/tmp/ +/.bundle/ +/vendor/bundle/ diff --git a/.rubocop.yml b/.rubocop.yml index 70ade74..0c2a8fd 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,8 +1,14 @@ AllCops: DisplayCopNames: true NewCops: enable + SuggestExtensions: false plugins: + - rubocop-rspec + - rubocop-performance + - rubocop-rails + - rubocop-rspec_rails + - rubocop-factory_bot ### Lint ### Lint/AmbiguousOperatorPrecedence: @@ -143,3 +149,61 @@ Metrics/ModuleLength: Metrics/PerceivedComplexity: Enabled: false + +### Rails ### +Rails: + Enabled: true + +Rails/Blank: + Enabled: false + +Rails/Delegate: + Enabled: false + +Rails/Output: + Enabled: false + +Rails/SquishedSQLHeredocs: + Enabled: false + +Rails/RedundantActiveRecordAllMethod: + Enabled: false + +Rails/DynamicFindBy: + AllowedMethods: + - find_by_id + - find_by_id! + - find_by_ids + - find_by_ids! + +Rails/InverseOf: + Enabled: false + +Rails/HasManyOrHasOneDependent: + Enabled: false + +Rails/I18nLocaleTexts: + Enabled: false + +Rails/FindEach: + Enabled: false + +### Performance ### +Performance/CollectionLiteralInLoop: + Enabled: false + +### RSpec ### +RSpec/MultipleExpectations: + Enabled: false + +RSpec/ExampleLength: + Enabled: false + +RSpec/DescribeClass: + Enabled: false + +RSpec/MultipleMemoizedHelpers: + Enabled: false + +RSpec/LetSetup: + Enabled: false diff --git a/Appraisals b/Appraisals new file mode 100644 index 0000000..b6cf8d4 --- /dev/null +++ b/Appraisals @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +rails_versions = ENV.fetch("RAILS_VERSIONS", "7.0 7.1 7.2 8.0 8.1").split + +rails_versions.each do |version| + appraise "rails-#{version.tr('.', '_')}" do + gem "rails", "~> #{version}" + gem "sqlite3", "~> 2.1" + gem "rspec" + gem "factory_bot" + gem "puma" + end +end diff --git a/Gemfile b/Gemfile index 2160871..e895375 100644 --- a/Gemfile +++ b/Gemfile @@ -4,4 +4,18 @@ source "https://rubygems.org" gemspec +gem "factory_bot", group: :test +gem "puma", group: :development +gem "rails", ENV.fetch("RAILS_VERSION", "~> 7.2") + +gem "rspec", group: :test gem "rubocop", group: :development +gem 'rubocop-factory_bot', group: :development +gem 'rubocop-performance', group: :development +gem 'rubocop-rails', group: :development +gem 'rubocop-rspec', group: :development +gem 'rubocop-rspec_rails', group: :development +gem "sqlite3", "~> 2.1" + +gem "appraisal", group: :development +gem "rake", group: :development diff --git a/Gemfile.lock b/Gemfile.lock index a272c17..5cdfdfb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,50 +9,216 @@ PATH GEM remote: https://rubygems.org/ specs: - activemodel (8.1.1) - activesupport (= 8.1.1) - activerecord (8.1.1) - activemodel (= 8.1.1) - activesupport (= 8.1.1) + actioncable (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + actionmailer (7.2.3) + actionpack (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (7.2.3) + actionview (= 7.2.3) + activesupport (= 7.2.3) + cgi + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4, < 3.3) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (7.2.3) + actionpack (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (7.2.3) + activesupport (= 7.2.3) + builder (~> 3.1) + cgi + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.2.3) + activesupport (= 7.2.3) + globalid (>= 0.3.6) + activemodel (7.2.3) + activesupport (= 7.2.3) + activerecord (7.2.3) + activemodel (= 7.2.3) + activesupport (= 7.2.3) timeout (>= 0.4.0) - activesupport (8.1.1) + activestorage (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activesupport (= 7.2.3) + marcel (~> 1.0) + activesupport (7.2.3) base64 + benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) - json logger (>= 1.4.2) minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) - uri (>= 0.13.1) + appraisal (2.5.0) + bundler + rake + thor (>= 0.14.0) ast (2.4.3) base64 (0.3.0) + benchmark (0.5.0) bigdecimal (4.0.1) + builder (3.3.0) + cgi (0.5.1) concurrent-ruby (1.3.6) connection_pool (3.0.2) + crass (1.0.6) + date (3.5.1) + diff-lcs (1.6.2) drb (2.2.3) - i18n (1.14.8) + erb (6.0.1) + erubi (1.13.1) + factory_bot (6.5.6) + activesupport (>= 6.1.0) + globalid (1.3.0) + activesupport (>= 6.1) + i18n (1.14.7) concurrent-ruby (~> 1.0) + io-console (0.8.2) + irb (1.16.0) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) json (2.18.0) language_server-protocol (3.17.0.5) lint_roller (1.1.0) logger (1.7.0) + loofah (2.25.0) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + mail (2.9.0) + logger + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.1.0) + mini_mime (1.1.5) minitest (6.0.0) prism (~> 1.5) + net-imap (0.6.2) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.5) + nokogiri (1.18.10-arm64-darwin) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-gnu) + racc (~> 1.4) parallel (1.27.0) parser (3.3.10.0) ast (~> 2.4.1) racc + pp (0.6.3) + prettyprint + prettyprint (0.2.0) prism (1.7.0) + psych (5.3.1) + date + stringio + puma (7.1.0) + nio4r (~> 2.0) racc (1.8.1) rack (3.2.4) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.3.1) + rack (>= 3) + rails (7.2.3) + actioncable (= 7.2.3) + actionmailbox (= 7.2.3) + actionmailer (= 7.2.3) + actionpack (= 7.2.3) + actiontext (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activemodel (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + bundler (>= 1.15.0) + railties (= 7.2.3) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) + cgi + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) rainbow (3.1.1) + rake (13.3.1) + rdoc (7.0.1) + erb + psych (>= 4.0.0) + tsort regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.6) rubocop (1.82.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) @@ -67,23 +233,65 @@ GEM rubocop-ast (1.48.0) parser (>= 3.3.7.2) prism (~> 1.4) + rubocop-factory_bot (2.28.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.2) + activesupport (>= 4.2.0) + lint_roller (~> 1.1) + rack (>= 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rspec (3.8.0) + lint_roller (~> 1.1) + rubocop (~> 1.81) + rubocop-rspec_rails (2.32.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rspec (~> 3.5) ruby-progressbar (1.13.0) securerandom (0.4.1) + sqlite3 (2.8.1-arm64-darwin) + sqlite3 (2.8.1-x86_64-linux-gnu) + stringio (3.2.0) + thor (1.4.0) timeout (0.6.0) + tsort (0.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.2.0) - uri (1.1.1) + useragent (0.16.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + zeitwerk (2.7.4) PLATFORMS arm64-darwin-24 x86_64-linux DEPENDENCIES + appraisal + factory_bot + puma + rails (~> 7.2) + rake + rspec rubocop + rubocop-factory_bot + rubocop-performance + rubocop-rails + rubocop-rspec + rubocop-rspec_rails simple_master! + sqlite3 (~> 2.1) BUNDLED WITH 2.7.1 diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..63bb6d1 --- /dev/null +++ b/Rakefile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "bundler/setup" +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) +RSpec::Core::RakeTask.new(:rails_sample_spec) do |task| + task.pattern = "examples/rails_sample/spec/**/*_spec.rb" + task.rspec_opts = "--require ./examples/rails_sample/spec/spec_helper --default-path examples/rails_sample/spec" +end + +task default: :spec diff --git a/examples/rails_sample/.gitignore b/examples/rails_sample/.gitignore new file mode 100644 index 0000000..f88a268 --- /dev/null +++ b/examples/rails_sample/.gitignore @@ -0,0 +1,19 @@ +/log/ +/tmp/ +/db/*.sqlite3 +/db/*.sqlite3-* +/db/*.sqlite3-journal +/db/*.sqlite3-wal +/db/*.sqlite3-shm +/storage/ +/node_modules/ +/vendor/bundle/ +/.bundle/ +/.byebug_history +/coverage/ +/public/assets/ +/public/packs/ +/public/packs-test/ +/public/webpack/ +/yarn-error.log +/npm-debug.log diff --git a/examples/rails_sample/README.md b/examples/rails_sample/README.md new file mode 100644 index 0000000..6fe5d94 --- /dev/null +++ b/examples/rails_sample/README.md @@ -0,0 +1,106 @@ +# Rails Sample App + +This sample app demonstrates SimpleMaster in a compact, game-like domain. +It is used to exercise column casting and association patterns (STI, polymorphic +`belongs_to`, `has_many`). + +## Development Setup +```bash +bundle install +cd examples/rails_sample +bundle exec rails db:prepare +bundle exec rails s +``` + +## Database Configuration +Database settings live in `examples/rails_sample/config/database.yml`. + +- `development`: sqlite3 at `examples/rails_sample/db/development.sqlite3` +- `test`: sqlite3 in-memory (`:memory:`) +- `production`: sqlite3 at `examples/rails_sample/db/production.sqlite3` + +If you need a different DB, edit `config/database.yml` or set `DATABASE_URL`. + +## Domain Model +``` +SimpleMaster (masters) ActiveRecord + +[Weapon] (STI: Gun, Blade) [Player] --< player_items >-- (polymorphic to Weapon/Armor/Potion) +[Armor] PlayerItem: belongs_to :item, polymorphic +[Potion] +[Level] --< players (lv) >-- [Player] +[Enemy] --< rewards >-- [Reward] (reward_type/reward_id -> Weapon/Armor/Potion) +``` + +## Masters +- **Weapon** (`Gun`, `Blade`) + - `id` + - `type` + - `name` + - `attack` (float) + - `info` (json, symbolize_names: true) + - `metadata` (json, symbolize_names: false) + - `rarity` (enum) + - `flags` (bitmask) + - Notes: polymorphic target (PlayerItem, Reward) +- **Armor** + - `id` + - `name` + - `defence` (float) + - Notes: polymorphic target +- **Potion** + - `id` + - `name` + - `hp` (float) + - Notes: polymorphic target +- **Level** + - `id` + - `lv` (unique) + - `attack` (float) + - `defence` (float) + - `hp` (float) + - Associations: `has_many :players` (lv) +- **Enemy** + - `id` + - `name` + - `is_boss` (boolean) + - `start_at` (time) + - `end_at` (time) + - `attack` + - `defence` + - `hp` + - Associations: `has_many :rewards` +- **Reward** + - `id` + - `enemy_id` + - `reward_type` + - `reward_id` + - Associations: `belongs_to :enemy`; polymorphic `belongs_to` Weapon/Armor/Potion + +## ActiveRecord +- **Player** + - `id` + - `name` + - `lv` + - Associations: `belongs_to :level` (lv); `has_many :player_items`; `has_many :items, through: :player_items` +- **PlayerItem** + - `player_id` + - `item_type` + - `item_id` + - Associations: `belongs_to :player`; polymorphic `belongs_to :item` + +## Fixtures +Fixtures live in `examples/rails_sample/fixtures/masters`. + +- `weapons.json` (STI Gun/Blade), `armors.json`, `potions.json`, `levels.json` (uses `lv` as unique key), + `enemies.json`, `rewards.json` +- Aim to include representative casts (float/json/globalize where useful) and polymorphic/has_many links. + +## Related Specs +- `simple_master/active_record/extension_spec.rb`: AR↔master (`belongs_to_master`) +- `simple_master/master/item_spec.rb`: column casting and master associations +- `simple_master/master/filterable_spec.rb`: find/find_by/all_by/all_in +- `simple_master/master/cache_spec.rb`: cache_method, cache_class_method +- `simple_master/storage/loader_spec.rb`: STI instantiation, diff application +- `simple_master/loader/marshal_loader_spec.rb`: Marshal dump/load roundtrip +- `simple_master/storage/dataset_spec.rb`: dataset cache/diff duplication diff --git a/examples/rails_sample/Rakefile b/examples/rails_sample/Rakefile new file mode 100644 index 0000000..c4f9523 --- /dev/null +++ b/examples/rails_sample/Rakefile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/examples/rails_sample/app/controllers/game_controller.rb b/examples/rails_sample/app/controllers/game_controller.rb new file mode 100644 index 0000000..3f82d94 --- /dev/null +++ b/examples/rails_sample/app/controllers/game_controller.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +class GameController < ActionController::Base # rubocop:disable Rails/ApplicationController + skip_forgery_protection + + def show + @player = current_player + @now = Time.current + @enemies = + Enemy.available_at(@now) + .sort_by { |enemy| [enemy.is_boss? ? 1 : 0, enemy.attack.to_f + enemy.defence.to_f + enemy.hp.to_f] } + @potions = Potion.all + end + + def play + @player = current_player + + case params[:op] + when "create_player" + @player = Player.create!(name: player_name, lv: player_level) + session[:player_id] = @player.id + flash.now[:notice] = "Player created." + when "challenge" + enemy = Enemy.find_by_id(params[:enemy_id].to_i) + if @player && enemy + result = @player.challenge_enemy(enemy, at: Time.current) + flash[:notice] = battle_message(result) + else + flash[:notice] = "Missing player or enemy." + end + when "potion" + potion = Potion.find_by_id(params[:potion_id].to_i) + flash.now[:notice] = if @player && potion + @player.use_potion(potion, at: Time.current) ? "Healed." : "Potion failed." + else + "Missing player or potion." + end + when "equip_weapon" + weapon = Weapon.find_by_id(params[:weapon_id].to_i) + flash.now[:notice] = if @player && weapon + @player.equip_weapon(weapon) ? "Weapon equipped." : "Cannot equip weapon." + else + "Missing player or weapon." + end + when "equip_armor" + armor = Armor.find_by_id(params[:armor_id].to_i) + flash.now[:notice] = if @player && armor + @player.equip_armor(armor) ? "Armor equipped." : "Cannot equip armor." + else + "Missing player or armor." + end + end + + redirect_to root_path + end + + private + + def current_player + player_id = session[:player_id] + return unless player_id + + Player.find_by(id: player_id) + end + + def player_name + name = params[:name].to_s.strip + name.empty? ? "Hero" : name + end + + def player_level + 1 + end + + def battle_message(result) + return "No player." if result.nil? + return "Win! Level up." if result[:ok] && result[:leveled_up] + return "Win! Rewards #{result[:rewards].size}" if result[:ok] + + "Lost: #{result[:reason]}" + end +end diff --git a/examples/rails_sample/app/models/application_master.rb b/examples/rails_sample/app/models/application_master.rb new file mode 100644 index 0000000..26af50c --- /dev/null +++ b/examples/rails_sample/app/models/application_master.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class ApplicationMaster < SimpleMaster::Master + self.abstract_class = true + + def self.validate_all_records + Thread.current[:errors] = {} + + classes = descendants.reject(&:abstract_class).select(&:base_class?) + classes.each do |klass| + klass.all.each(&:valid?) + end + + Thread.current[:errors] + ensure + Thread.current[:errors] = {} + end +end diff --git a/examples/rails_sample/app/models/application_record.rb b/examples/rails_sample/app/models/application_record.rb new file mode 100644 index 0000000..9ada3ce --- /dev/null +++ b/examples/rails_sample/app/models/application_record.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + include SimpleMaster::ActiveRecord::Extension +end diff --git a/examples/rails_sample/app/models/armor.rb b/examples/rails_sample/app/models/armor.rb new file mode 100644 index 0000000..93d89fd --- /dev/null +++ b/examples/rails_sample/app/models/armor.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Armor < ApplicationMaster + include ItemReceivable + + def_column :id + def_column :icon, type: :string + def_column :name, type: :string + def_column :defence, type: :float + + validates :name, presence: true + validates :defence, numericality: { greater_than_or_equal_to: 0 } + + def self.max_quantity + 1 + end +end diff --git a/examples/rails_sample/app/models/concerns/item_receivable.rb b/examples/rails_sample/app/models/concerns/item_receivable.rb new file mode 100644 index 0000000..7f1a57e --- /dev/null +++ b/examples/rails_sample/app/models/concerns/item_receivable.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module ItemReceivable + extend ActiveSupport::Concern + + class_methods do + def max_quantity + nil + end + end + + included do + cache_class_method def self.receivable_sources + sources = {} + Reward.all.each do |reward| + reward_item = reward.reward + + klass = reward_item.class + while klass <= self + array = sources.fetch(reward_item.id) { sources[reward_item.id] = [] } + array << reward.enemy + + klass = klass.superclass + end + end + + sources.each_value do |array| + array.uniq! + array.freeze + end + + sources + end + + # This is an example. `self.class.receivable_sources[id]` may work better here. + cache_method def receivable_sources + self.class.receivable_sources.fetch(id) { [] } + end + end + + def self.receivable_item? + true + end +end diff --git a/examples/rails_sample/app/models/enemy.rb b/examples/rails_sample/app/models/enemy.rb new file mode 100644 index 0000000..29dcbac --- /dev/null +++ b/examples/rails_sample/app/models/enemy.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +class Enemy < ApplicationMaster + def_column :id + def_column :name, type: :string + def_column :is_boss, type: :boolean + def_column :start_at, type: :time + def_column :end_at, type: :time, group_key: true + def_column :attack, type: :float + def_column :defence, type: :float + def_column :hp, type: :float + def_column :exp, type: :integer + def_column :stamina_cost, type: :integer + + has_many :rewards + + validates :name, presence: true + validates :attack, :defence, :hp, numericality: { greater_than_or_equal_to: 0 } + validates :exp, :stamina_cost, numericality: { only_integer: true, greater_than_or_equal_to: 0 } + validate :end_after_start_at + + cache_class_method def self.sorted_end_ats + # end_atを降順でソートした配列 + [nil, *grouped_hash[:end_at].keys.compact.sort!.reverse!] + end + + def self.not_ended(time = Time.current) + sorted_end_ats + .take_while { |end_at| end_at.nil? || end_at > time } + .flat_map { |end_at| all_by(:end_at, end_at) } + end + + def self.available_at(time = Time.current) + not_ended(time).filter { _1.has_started?(time) } + end + + def has_started?(time = Time.current) + start_at.nil? || start_at <= time + end + + def not_ended?(time = Time.current) + end_at.nil? || end_at > time + end + + def available?(time = Time.current) + has_started?(time) && not_ended?(time) + end + + private + + def end_after_start_at + return if start_at.nil? || end_at.nil? + return if end_at > start_at + + errors.add(:end_at, :invalid) + end +end diff --git a/examples/rails_sample/app/models/level.rb b/examples/rails_sample/app/models/level.rb new file mode 100644 index 0000000..5a9406d --- /dev/null +++ b/examples/rails_sample/app/models/level.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Level < ApplicationMaster + def_column :id + def_column :lv, type: :integer, group_key: true + def_column :attack, type: :float + def_column :defence, type: :float + def_column :hp, type: :float + def_column :next_exp, type: :integer + def_column :hp_recovery_sec, type: :integer + def_column :stamina, type: :integer + def_column :stamina_recovery_sec, type: :integer + + has_many :players, foreign_key: :lv, primary_key: :lv + + validates :lv, presence: true, numericality: { only_integer: true, greater_than: 0 } + validates :attack, :defence, :hp, numericality: { greater_than_or_equal_to: 0 } + validates :next_exp, :hp_recovery_sec, :stamina, :stamina_recovery_sec, + numericality: { only_integer: true, greater_than_or_equal_to: 0 } + + cache_class_method def self.max_lv + lvs = all.filter_map(&:lv) + lvs.max || 0 + end + + def self.find_by_lv(lv) + find_by(:lv, lv) + end +end diff --git a/examples/rails_sample/app/models/player.rb b/examples/rails_sample/app/models/player.rb new file mode 100644 index 0000000..ef734c0 --- /dev/null +++ b/examples/rails_sample/app/models/player.rb @@ -0,0 +1,251 @@ +# frozen_string_literal: true + +class Player < ApplicationRecord + belongs_to :level, foreign_key: :lv, primary_key: :lv + belongs_to :weapon + belongs_to :armor + has_many :player_items + has_many :items, through: :player_items + + def items + player_items.flat_map do |player_item| + quantity = player_item.quantity.to_i + next [] if quantity <= 0 + next [] unless player_item.item + + Array.new(quantity, player_item.item) + end + end + + def equip_weapon(weapon) + return false unless weapon.is_a?(Weapon) + + record = find_item_record(weapon) + return false unless record&.quantity.to_i.positive? + + update!(weapon_id: weapon.id) + true + end + + def equip_armor(armor) + return false unless armor.is_a?(Armor) + + record = find_item_record(armor) + return false unless record&.quantity.to_i.positive? + + update!(armor_id: armor.id) + true + end + + def attack_power + base = level.attack + bonus = (weapon&.attack || 0).to_f + base + bonus + end + + def defence_power + base = level.defence + bonus = (armor&.defence || 0).to_f + base + bonus + end + + def challenge_enemy(enemy, at: Time.current) + return { ok: false, reason: :out_of_period } unless enemy.available?(at) + player_attack = attack_power + return { ok: false, reason: :attack_too_low } if player_attack <= enemy.defence.to_f + + cost = stamina_cost_for(enemy) + return { ok: false, reason: :not_enough_stamina } unless consume_stamina(cost, at: at) + + apply_hp_regen!(at) + + player_hp = hp.to_f + enemy_hp = enemy.hp.to_f + player_damage = scaled_damage(player_attack, enemy.defence.to_f) + enemy_damage = scaled_damage(enemy.attack.to_f, defence_power) + + while enemy_hp > 0 && player_hp > 0 + enemy_hp -= player_damage + break if enemy_hp <= 0 + + player_hp -= enemy_damage + end + + self.hp = [player_hp, 0.0].max + self.hp_updated_at = at + + if enemy_hp > 0 + save! + return { ok: false, reason: :defeated } + end + + gain_exp(enemy.exp.to_i) + rewards = receive_rewards(enemy) + leveled_up = consume_exp_for_level_up(at: at) + cap_resources!(at) + save! + + { ok: true, leveled_up: leveled_up, rewards: rewards } + end + + def use_potion(potion, at: Time.current) + return false unless potion + item_record = find_item_record(potion) + return false unless item_record + + apply_hp_regen!(at) + heal_amount = potion.hp.to_f + return false if heal_amount <= 0 + + self.hp = [hp.to_f + heal_amount, max_hp].min + self.hp_updated_at = at + if item_record.quantity.to_i > 1 + item_record.update!(quantity: item_record.quantity.to_i - 1) + else + item_record.destroy! + end + save! + true + end + + def current_hp(at: Time.current) + max = max_hp + return 0.0 if max <= 0.0 + + base = hp.nil? ? max : hp + last = hp_updated_at || at + recovered = recovered_amount(base, max, last, at, level.hp_recovery_sec) + + [base + recovered, max].min + end + + def current_stamina(at: Time.current) + max = max_stamina + return 0 if max <= 0 + + base = stamina.nil? ? max : stamina + last = stamina_updated_at || at + recovered = recovered_amount(base, max, last, at, level.stamina_recovery_sec) + + [(base + recovered).to_i, max].min + end + + def max_hp + level.hp + end + + def max_stamina + level.stamina + end + + def self.find_by_lv(lv) + find_by(lv: lv) + end + + private + + def scaled_damage(attack, defence) + return 0.0 if attack.to_f <= 0.0 + + attack.to_f * 100.0 / (100.0 + defence.to_f) + end + + def stamina_cost_for(enemy) + [enemy.stamina_cost.to_i, 1].max + end + + def apply_hp_regen!(at) + self.hp = current_hp(at: at) + self.hp_updated_at = at + end + + def apply_stamina_regen!(at) + self.stamina = current_stamina(at: at) + self.stamina_updated_at = at + end + + def recovered_amount(base, max, from_time, to_time, recovery_sec) + return 0.0 if max <= base + interval = recovery_sec.to_i + return 0.0 if interval <= 0 + + elapsed = (to_time - from_time).to_i + return 0.0 if elapsed <= 0 + + steps = elapsed / interval + [steps.to_f, max - base].min + end + + def consume_stamina(cost, at:) + apply_stamina_regen!(at) + return false if stamina.to_i < cost + + self.stamina = stamina.to_i - cost + self.stamina_updated_at = at + true + end + + def gain_exp(amount) + self.exp = exp.to_i + amount + end + + def consume_exp_for_level_up(at:) + leveled_up = false + + loop do + current_level = level + required = current_level.next_exp.to_i + break if required <= 0 + break if exp.to_i < required + + next_level = Level.find_by(:lv, lv + 1) + break unless next_level + + self.exp = exp.to_i - required + self.lv = next_level.lv + leveled_up = true + end + + if leveled_up + self.stamina = max_stamina + self.stamina_updated_at = at + end + + leveled_up + end + + def cap_resources!(at) + apply_hp_regen!(at) + apply_stamina_regen!(at) + + self.hp = [hp.to_f, max_hp].min if max_hp.positive? + self.stamina = [stamina.to_i, max_stamina].min if max_stamina.positive? + end + + def receive_rewards(enemy) + enemy.rewards.filter_map do |reward| + next if reward.reward_type.nil? || reward.reward_id.nil? + + add_item(reward.reward_type, reward.reward_id) + end + end + + def find_item_record(item) + player_items.find_by(item: item) + end + + def add_item(item_type, item_id, amount = 1) + item_class = item_type.safe_constantize + unless item_class && item_class <= ItemReceivable + fail ArgumentError, "Unsupported item_type: #{item_type}" + end + + record = player_items.find_or_initialize_by(item_type: item_type, item_id: item_id) + max_quantity = item_class.max_quantity + quantity = record.quantity.to_i + amount + quantity = [quantity, max_quantity].min if max_quantity + record.quantity = quantity + record.save! + record + end +end diff --git a/examples/rails_sample/app/models/player_item.rb b/examples/rails_sample/app/models/player_item.rb new file mode 100644 index 0000000..b4e058d --- /dev/null +++ b/examples/rails_sample/app/models/player_item.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class PlayerItem < ApplicationRecord + belongs_to :player + belongs_to :item, polymorphic: true +end diff --git a/examples/rails_sample/app/models/potion.rb b/examples/rails_sample/app/models/potion.rb new file mode 100644 index 0000000..8c05e1f --- /dev/null +++ b/examples/rails_sample/app/models/potion.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class Potion < ApplicationMaster + include ItemReceivable + + def_column :id + def_column :name + def_column :hp, type: :float + + globalize :name + + validates :name, presence: true + validates :hp, numericality: { greater_than_or_equal_to: 0 } +end diff --git a/examples/rails_sample/app/models/reward.rb b/examples/rails_sample/app/models/reward.rb new file mode 100644 index 0000000..8df4981 --- /dev/null +++ b/examples/rails_sample/app/models/reward.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class Reward < ApplicationMaster + def_column :id + def_column :enemy_id, type: :integer, group_key: true + def_column :reward_type, polymorphic_type: true, group_key: true + def_column :reward_id, type: :integer + + belongs_to :enemy + belongs_to :reward, polymorphic: true + + validates :reward_type, presence: true + validate :reward_type_receivable + + private + + def reward_type_receivable + klass = reward_type.safe_constantize + return if klass && klass <= ItemReceivable + + errors.add(:reward_type, :invalid) + end +end diff --git a/examples/rails_sample/app/models/weapon.rb b/examples/rails_sample/app/models/weapon.rb new file mode 100644 index 0000000..7a769bd --- /dev/null +++ b/examples/rails_sample/app/models/weapon.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +class Weapon < ApplicationMaster + include ItemReceivable + + RARITY = { + common: 0, + rare: 1, + epic: 2, + }.freeze + + def_column :id + def_column :type, sti: true + def_column :icon, type: :string + def_column :name, type: :string + def_column :attack, type: :float + def_column :info, type: :json, symbolize_names: true + def_column :metadata, type: :json, symbolize_names: false + def_column :rarity, type: :integer + def_column :flags, type: :integer + + globalize :name + + enum :rarity, RARITY + bitmask :flags, as: [:tradeable, :soulbound, :limited] + + validates :name, presence: true + validates :attack, numericality: { greater_than_or_equal_to: 0 } + validates :rarity, inclusion: { in: RARITY.keys } + + cache_method def cached_signature + "#{name}-#{rarity}" + end + + def self.max_quantity + 1 + end +end + +class Gun < Weapon +end + +class Blade < Weapon +end diff --git a/examples/rails_sample/app/views/game/show.html.erb b/examples/rails_sample/app/views/game/show.html.erb new file mode 100644 index 0000000..0bd9e25 --- /dev/null +++ b/examples/rails_sample/app/views/game/show.html.erb @@ -0,0 +1,340 @@ + + + +
+
+
+

Dummy Game

+
Fight, earn exp, and grab rewards.
+
+
+ + + +
+ <% if flash[:notice] %> +
<%= flash[:notice] %>
+ <% end %> +
+ +
+
+

Player

+ <% if @player %> + <% equipped_weapon = @player.weapon %> + <% equipped_armor = @player.armor %> + <% weapon_icon = equipped_weapon&.icon.to_s.strip %> + <% armor_icon = equipped_armor&.icon.to_s.strip %> +
Name<%= @player.name %>
+
LevelLv <%= @player.lv %> (EXP <%= @player.exp %>)
+
ATK / DEF<%= @player.attack_power %> / <%= @player.defence_power %>
+
+ Weapon + + <% if equipped_weapon %> + <% if weapon_icon != "" %><% end %> + <%= equipped_weapon.name %> (ATK +<%= equipped_weapon.attack.to_f %>) + <% else %> + none + <% end %> + +
+
+ Armor + + <% if equipped_armor %> + <% if armor_icon != "" %><% end %> + <%= equipped_armor.name %> (DEF +<%= equipped_armor.defence.to_f %>) + <% else %> + none + <% end %> + +
+ <% hp_now = @player.current_hp(at: @now).round(1) %> + <% hp_max = @player.max_hp %> + <% hp_rate = hp_max.to_f.zero? ? 0 : ((hp_now / hp_max.to_f) * 100).round %> +
HP<%= hp_now %> / <%= hp_max %>
+
+ <% st_now = @player.current_stamina(at: @now) %> + <% st_max = @player.max_stamina %> + <% st_rate = st_max.to_i.zero? ? 0 : ((st_now.to_f / st_max.to_f) * 100).round %> +
Stamina<%= st_now %> / <%= st_max %>
+
+
Items:
+ <% if @player.player_items.any? %> + <% equipment_items = @player.player_items.select { |pi| pi.item.is_a?(Weapon) || pi.item.is_a?(Armor) } %> + <% potion_items = @player.player_items.select { |pi| pi.item.is_a?(Potion) } %> + <% other_items = @player.player_items.reject { |pi| pi.item.is_a?(Weapon) || pi.item.is_a?(Armor) || pi.item.is_a?(Potion) } %> + +
+ <% if equipment_items.any? %> +
Equipment
+ <% equipment_items.each do |player_item| %> + <% item = player_item.item %> + <% next unless item %> + <% icon_label = item.respond_to?(:icon) ? item.icon.to_s.strip : "" %> +
+ <% if icon_label != "" %> + + <% end %> + <%= item.name %> + x<%= player_item.quantity %> + <% if item.is_a?(Weapon) %> + ATK +<%= item.attack.to_f %> + <% if @player.weapon_id == item.id %> + Equipped + <% else %> +
+ + + +
+ <% end %> + <% elsif item.is_a?(Armor) %> + DEF +<%= item.defence.to_f %> + <% if @player.armor_id == item.id %> + Equipped + <% else %> +
+ + + +
+ <% end %> + <% end %> +
+ <% end %> + <% end %> + + <% if potion_items.any? %> +
Potions
+ <% potion_items.each do |player_item| %> + <% item = player_item.item %> + <% next unless item %> +
+ <%= item.name %> + x<%= player_item.quantity %> + Potion +<%= item.hp %> HP +
+ + + +
+
+ <% end %> + <% end %> + + <% if other_items.any? %> +
Other
+ <% other_items.each do |player_item| %> + <% item = player_item.item %> + <% next unless item %> + <% icon_label = item.respond_to?(:icon) ? item.icon.to_s.strip : "" %> +
+ <% if icon_label != "" %> + + <% end %> + <%= item.name %> + x<%= player_item.quantity %> +
+ <% end %> + <% end %> +
+ <% else %> +
none
+ <% end %> + <% else %> +
No player yet.
+ <% end %> +
+ +
+

Enemies

+
+ <% @enemies.each do |enemy| %> +
+ <%= enemy.name %> + <% if enemy.is_boss? %> + BOSS + <% end %> + EXP <%= enemy.exp.to_i %> +
ATK <%= enemy.attack %> / DEF <%= enemy.defence %> / HP <%= enemy.hp %> / Stamina <%= enemy.stamina_cost.to_i %>
+ <% if enemy.rewards.any? %> +
Rewards: <%= enemy.rewards.map { |reward| reward.reward&.name }.compact.join(", ") %>
+ <% end %> + <% if @player %> +
+ + + +
+ <% else %> +
Create a player to fight.
+ <% end %> +
+ <% end %> +
+
+
+ +
diff --git a/examples/rails_sample/bin/rails b/examples/rails_sample/bin/rails new file mode 100755 index 0000000..22f2d8d --- /dev/null +++ b/examples/rails_sample/bin/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/examples/rails_sample/config.ru b/examples/rails_sample/config.ru new file mode 100644 index 0000000..7e804ae --- /dev/null +++ b/examples/rails_sample/config.ru @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require_relative "config/environment" + +run Rails.application diff --git a/examples/rails_sample/config/application.rb b/examples/rails_sample/config/application.rb new file mode 100644 index 0000000..ec826dc --- /dev/null +++ b/examples/rails_sample/config/application.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require_relative "boot" + +require "rails" +require "active_record/railtie" +require "action_controller/railtie" +require "simple_master" + +module Dummy + class Application < Rails::Application + config.root = File.expand_path("..", __dir__) + config.eager_load = false + config.logger = Logger.new($stdout) + config.logger.level = Logger::WARN + config.active_support.test_order = :random + config.hosts = nil + config.autoload_paths << Rails.root.join("lib") + end +end diff --git a/examples/rails_sample/config/boot.rb b/examples/rails_sample/config/boot.rb new file mode 100644 index 0000000..5b865f6 --- /dev/null +++ b/examples/rails_sample/config/boot.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __dir__) + +require "bundler/setup" diff --git a/examples/rails_sample/config/database.yml b/examples/rails_sample/config/database.yml new file mode 100644 index 0000000..7e9c547 --- /dev/null +++ b/examples/rails_sample/config/database.yml @@ -0,0 +1,16 @@ +default: &default + adapter: sqlite3 + pool: 5 + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +test: + <<: *default + database: ":memory:" + +production: + <<: *default + database: db/production.sqlite3 diff --git a/examples/rails_sample/config/environment.rb b/examples/rails_sample/config/environment.rb new file mode 100644 index 0000000..fe49ece --- /dev/null +++ b/examples/rails_sample/config/environment.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require_relative "application" + +Dummy::Application.initialize! diff --git a/examples/rails_sample/config/initializers/simple_master.rb b/examples/rails_sample/config/initializers/simple_master.rb new file mode 100644 index 0000000..40df7f8 --- /dev/null +++ b/examples/rails_sample/config/initializers/simple_master.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "json_loader" +require "yaml" + +Rails.application.config.after_initialize do + Rails.application.eager_load! + + SimpleMaster.init(for_test: Rails.env.test?) + + translations = + begin + YAML.load_file(Rails.root.join("fixtures/translations.yml")) || {} + rescue Errno::ENOENT + {} + end + + globalize_proc = lambda do |klass, records| + table_translation = translations[klass.table_name] + column_names = klass.all_columns.filter_map { |column| column.name.to_s if column.options[:globalize] } + return records if table_translation.blank? || column_names.empty? + + records.map do |record| + record_translation = table_translation[record.id] + next record unless record_translation + + record = record.dup if record.frozen? + column_names.each do |column_name| + translation = record_translation[column_name] + next unless translation + + record.public_send(:"_globalized_#{column_name}=", translation.symbolize_keys) + end + record + end + end + + loader = JsonLoader.new(globalize_proc: globalize_proc) + $current_dataset = SimpleMaster::Storage::Dataset.new(loader: loader) + $current_dataset.load +end diff --git a/examples/rails_sample/config/routes.rb b/examples/rails_sample/config/routes.rb new file mode 100644 index 0000000..0184269 --- /dev/null +++ b/examples/rails_sample/config/routes.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +Rails.application.routes.draw do + root "game#show" + post "/play", to: "game#play" +end diff --git a/examples/rails_sample/db/schema.rb b/examples/rails_sample/db/schema.rb new file mode 100644 index 0000000..b3e0de4 --- /dev/null +++ b/examples/rails_sample/db/schema.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +ActiveRecord::Schema.define do + create_table :weapons, force: true do |t| + t.string :type + t.string :icon + t.string :name + t.float :attack + t.json :info + t.json :metadata + t.integer :rarity + t.integer :flags + end + + create_table :armors, force: true do |t| + t.string :icon + t.string :name + t.float :defence + end + + create_table :potions, force: true do |t| + t.string :name + t.float :hp + end + + create_table :levels, force: true do |t| + t.integer :lv + t.float :attack + t.float :defence + t.float :hp + t.integer :next_exp + t.integer :hp_recovery_sec + t.integer :stamina + t.integer :stamina_recovery_sec + end + + create_table :enemies, force: true do |t| + t.string :name + t.boolean :is_boss + t.datetime :start_at + t.datetime :end_at + t.float :attack + t.float :defence + t.float :hp + t.integer :exp + t.integer :stamina_cost + end + + create_table :rewards, force: true do |t| + t.integer :enemy_id + t.string :reward_type + t.integer :reward_id + end + + create_table :players, force: true do |t| + t.string :name, null: false + t.integer :lv, null: false + t.integer :exp, null: false, default: 0 + t.integer :weapon_id + t.integer :armor_id + t.float :hp + t.datetime :hp_updated_at + t.integer :stamina + t.datetime :stamina_updated_at + end + + create_table :player_items, force: true do |t| + t.integer :player_id, null: false + t.string :item_type, null: false + t.integer :item_id, null: false + t.integer :quantity, null: false, default: 0 + end +end diff --git a/examples/rails_sample/fixtures/masters/armors.json b/examples/rails_sample/fixtures/masters/armors.json new file mode 100644 index 0000000..a9a6787 --- /dev/null +++ b/examples/rails_sample/fixtures/masters/armors.json @@ -0,0 +1,5 @@ +[ + { "id": 1, "icon": "fa-solid fa-shirt", "name": "Leather Vest", "defence": 5.0 }, + { "id": 2, "icon": "fa-solid fa-shield-halved", "name": "Chain Mail", "defence": 12.5 }, + { "id": 3, "icon": "fa-solid fa-shield", "name": "Dragon Plate", "defence": 28.0 } +] diff --git a/examples/rails_sample/fixtures/masters/enemies.json b/examples/rails_sample/fixtures/masters/enemies.json new file mode 100644 index 0000000..dc62ea2 --- /dev/null +++ b/examples/rails_sample/fixtures/masters/enemies.json @@ -0,0 +1,122 @@ +[ + { + "id": 1, + "name": "Goblin Scout", + "is_boss": false, + "start_at": "2024-05-01T10:00:00Z", + "end_at": "2024-05-01T18:00:00Z", + "attack": 5.5, + "defence": 2.5, + "hp": 18.0, + "exp": 8, + "stamina_cost": 2 + }, + { + "id": 2, + "name": "Ogre Chief", + "is_boss": true, + "start_at": "2024-05-02T12:00:00Z", + "end_at": "2024-05-02T23:00:00Z", + "attack": 14.0, + "defence": 8.5, + "hp": 52.0, + "exp": 26, + "stamina_cost": 6 + }, + { + "id": 3, + "name": "Clockwork Rat", + "is_boss": false, + "start_at": null, + "end_at": null, + "attack": 3.5, + "defence": 1.5, + "hp": 12.0, + "exp": 5, + "stamina_cost": 1 + }, + { + "id": 4, + "name": "Sand Wyrm", + "is_boss": false, + "start_at": "2020-01-01T00:00:00Z", + "end_at": "2030-01-01T00:00:00Z", + "attack": 8.5, + "defence": 4.0, + "hp": 30.0, + "exp": 14, + "stamina_cost": 3 + }, + { + "id": 5, + "name": "Frost Knight", + "is_boss": false, + "start_at": null, + "end_at": null, + "attack": 10.5, + "defence": 6.5, + "hp": 38.0, + "exp": 18, + "stamina_cost": 4 + }, + { + "id": 6, + "name": "Moss Golem", + "is_boss": false, + "start_at": null, + "end_at": null, + "attack": 7.0, + "defence": 4.5, + "hp": 26.0, + "exp": 12, + "stamina_cost": 3 + }, + { + "id": 7, + "name": "Sky Harrier", + "is_boss": false, + "start_at": "2020-01-01T00:00:00Z", + "end_at": "2030-01-01T00:00:00Z", + "attack": 4.5, + "defence": 2.0, + "hp": 14.0, + "exp": 6, + "stamina_cost": 2 + }, + { + "id": 8, + "name": "Iron Crab", + "is_boss": false, + "start_at": null, + "end_at": null, + "attack": 7.5, + "defence": 6.0, + "hp": 28.0, + "exp": 13, + "stamina_cost": 3 + }, + { + "id": 9, + "name": "Nightshade", + "is_boss": true, + "start_at": "2020-01-01T00:00:00Z", + "end_at": "2030-01-01T00:00:00Z", + "attack": 12.0, + "defence": 7.5, + "hp": 45.0, + "exp": 24, + "stamina_cost": 5 + }, + { + "id": 10, + "name": "Ancient Dragon", + "is_boss": true, + "start_at": null, + "end_at": null, + "attack": 34.0, + "defence": 59.0, + "hp": 180.0, + "exp": 80, + "stamina_cost": 10 + } +] diff --git a/examples/rails_sample/fixtures/masters/levels.json b/examples/rails_sample/fixtures/masters/levels.json new file mode 100644 index 0000000..05557dd --- /dev/null +++ b/examples/rails_sample/fixtures/masters/levels.json @@ -0,0 +1,112 @@ +[ + { + "id": 1, + "lv": 1, + "attack": 5.0, + "defence": 2.0, + "hp": 18.0, + "next_exp": 12, + "hp_recovery_sec": 55, + "stamina": 12, + "stamina_recovery_sec": 28 + }, + { + "id": 2, + "lv": 2, + "attack": 6.5, + "defence": 3.0, + "hp": 24.0, + "next_exp": 24, + "hp_recovery_sec": 50, + "stamina": 14, + "stamina_recovery_sec": 24 + }, + { + "id": 3, + "lv": 3, + "attack": 8.5, + "defence": 4.5, + "hp": 32.0, + "next_exp": 40, + "hp_recovery_sec": 45, + "stamina": 16, + "stamina_recovery_sec": 22 + }, + { + "id": 4, + "lv": 4, + "attack": 11.0, + "defence": 6.0, + "hp": 40.0, + "next_exp": 60, + "hp_recovery_sec": 40, + "stamina": 18, + "stamina_recovery_sec": 20 + }, + { + "id": 5, + "lv": 5, + "attack": 13.5, + "defence": 7.5, + "hp": 48.0, + "next_exp": 85, + "hp_recovery_sec": 36, + "stamina": 21, + "stamina_recovery_sec": 18 + }, + { + "id": 6, + "lv": 6, + "attack": 16.0, + "defence": 9.0, + "hp": 58.0, + "next_exp": 120, + "hp_recovery_sec": 32, + "stamina": 24, + "stamina_recovery_sec": 16 + }, + { + "id": 7, + "lv": 7, + "attack": 18.5, + "defence": 10.5, + "hp": 68.0, + "next_exp": 160, + "hp_recovery_sec": 28, + "stamina": 27, + "stamina_recovery_sec": 14 + }, + { + "id": 8, + "lv": 8, + "attack": 21.0, + "defence": 12.0, + "hp": 79.0, + "next_exp": 210, + "hp_recovery_sec": 24, + "stamina": 30, + "stamina_recovery_sec": 12 + }, + { + "id": 9, + "lv": 9, + "attack": 24.0, + "defence": 13.5, + "hp": 90.0, + "next_exp": 270, + "hp_recovery_sec": 21, + "stamina": 33, + "stamina_recovery_sec": 10 + }, + { + "id": 10, + "lv": 10, + "attack": 27.0, + "defence": 15.5, + "hp": 104.0, + "next_exp": 0, + "hp_recovery_sec": 18, + "stamina": 36, + "stamina_recovery_sec": 9 + } +] diff --git a/examples/rails_sample/fixtures/masters/potions.json b/examples/rails_sample/fixtures/masters/potions.json new file mode 100644 index 0000000..a5d73c0 --- /dev/null +++ b/examples/rails_sample/fixtures/masters/potions.json @@ -0,0 +1,20 @@ +[ + { + "id": 1, + "name": "Minor Heal", + "_globalized_name": "{\"en\":\"Minor Heal\",\"ja\":\"マイナーヒール\"}", + "hp": 25.0 + }, + { + "id": 2, + "name": "Major Heal", + "_globalized_name": "{\"en\":\"Major Heal\",\"ja\":\"メジャーヒール\"}", + "hp": 60.0 + }, + { + "id": 3, + "name": "Elixir", + "_globalized_name": "{\"en\":\"Elixir\",\"ja\":\"エリクサー\"}", + "hp": 120.0 + } +] diff --git a/examples/rails_sample/fixtures/masters/rewards.json b/examples/rails_sample/fixtures/masters/rewards.json new file mode 100644 index 0000000..54ebbab --- /dev/null +++ b/examples/rails_sample/fixtures/masters/rewards.json @@ -0,0 +1,16 @@ +[ + { "id": 1, "enemy_id": 1, "reward_type": "Weapon", "reward_id": 1 }, + { "id": 2, "enemy_id": 1, "reward_type": "Potion", "reward_id": 1 }, + { "id": 3, "enemy_id": 2, "reward_type": "Potion", "reward_id": 2 }, + { "id": 4, "enemy_id": 2, "reward_type": "Weapon", "reward_id": 3 }, + { "id": 5, "enemy_id": 3, "reward_type": "Potion", "reward_id": 1 }, + { "id": 6, "enemy_id": 4, "reward_type": "Weapon", "reward_id": 2 }, + { "id": 7, "enemy_id": 4, "reward_type": "Armor", "reward_id": 2 }, + { "id": 8, "enemy_id": 5, "reward_type": "Armor", "reward_id": 3 }, + { "id": 9, "enemy_id": 6, "reward_type": "Potion", "reward_id": 3 }, + { "id": 10, "enemy_id": 7, "reward_type": "Armor", "reward_id": 1 }, + { "id": 11, "enemy_id": 8, "reward_type": "Potion", "reward_id": 2 }, + { "id": 12, "enemy_id": 9, "reward_type": "Weapon", "reward_id": 3 }, + { "id": 13, "enemy_id": 10, "reward_type": "Weapon", "reward_id": 3 }, + { "id": 14, "enemy_id": 10, "reward_type": "Potion", "reward_id": 3 } +] diff --git a/examples/rails_sample/fixtures/masters/weapons.json b/examples/rails_sample/fixtures/masters/weapons.json new file mode 100644 index 0000000..e2e2ede --- /dev/null +++ b/examples/rails_sample/fixtures/masters/weapons.json @@ -0,0 +1,46 @@ +[ + { + "id": 1, + "type": "Gun", + "icon": "fa-solid fa-gun", + "name": "Bronze Pistol", + "attack": 12.5, + "info": "{\"slots\":1,\"origin\":\"forge\"}", + "metadata": "{\"source\":\"archive\",\"tags\":[\"starter\",\"event\"],\"stats\":{\"calibration\":0.92,\"batch\":\"A1\"},\"notes\":\"legacy\"}", + "rarity": "common", + "flags": 1 + }, + { + "id": 2, + "type": "Blade", + "icon": "fa-solid fa-sword", + "name": "Silver Saber", + "attack": 20.0, + "info": "{\"slots\":2,\"element\":\"wind\"}", + "metadata": "{\"source\":\"drop\",\"tags\":[\"mid\",\"wind\"],\"tuning\":{\"sharpness\":7,\"balance\":0.4},\"deprecated\":false}", + "rarity": "rare", + "flags": 3 + }, + { + "id": 3, + "type": "Gun", + "icon": "fa-solid fa-crosshairs", + "name": "Crimson Rifle", + "attack": 35.75, + "info": "{\"slots\":3,\"element\":\"fire\"}", + "metadata": "{\"source\":\"boss\",\"tags\":[\"rare\",\"fire\"],\"mods\":[{\"name\":\"burst\",\"level\":2},{\"name\":\"overheat\",\"level\":1}],\"extra\":null}", + "rarity": "epic", + "flags": 6 + }, + { + "id": 4, + "type": "Blade", + "icon": "fa-solid fa-bolt", + "name": "Storm Edge", + "attack": 28.0, + "info": "{\"slots\":2,\"element\":\"lightning\"}", + "metadata": "{\"source\":\"event\",\"tags\":[\"storm\",\"limited\"],\"tuning\":{\"sharpness\":8,\"balance\":0.6},\"deprecated\":false}", + "rarity": "rare", + "flags": 5 + } +] diff --git a/examples/rails_sample/fixtures/translations.yml b/examples/rails_sample/fixtures/translations.yml new file mode 100644 index 0000000..6a690cd --- /dev/null +++ b/examples/rails_sample/fixtures/translations.yml @@ -0,0 +1,17 @@ +weapons: + 1: + name: + en: "Bronze Pistol" + ja: "ブロンズピストル" + 2: + name: + en: "Silver Saber" + ja: "シルバーセイバー" + 3: + name: + en: "Crimson Rifle" + ja: "クリムゾンライフル" + 4: + name: + en: "Storm Edge" + ja: "ストームエッジ" diff --git a/examples/rails_sample/lib/json_loader.rb b/examples/rails_sample/lib/json_loader.rb new file mode 100644 index 0000000..4777c6f --- /dev/null +++ b/examples/rails_sample/lib/json_loader.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "json" + +# Loads master data from JSON files under examples/rails_sample/fixtures/masters/.json +class JsonLoader < SimpleMaster::Loader + FIXTURE_DIR = File.expand_path("../fixtures/masters", __dir__) + + def read_raw(table) + path = File.join(FIXTURE_DIR, "#{table.klass.table_name}.json") + File.read(path) + end + + def build_records(klass, raw) + record_hashes = JSON.parse(raw) + + if klass.sti_class? + sti_column_name = klass.sti_column.to_s + + record_hashes.map do |record_hash| + sti_klass = record_hash[sti_column_name].constantize + sti_klass.new(record_hash) + end + else + record_hashes.map do |record_hash| + klass.new(record_hash) + end + end + end +end diff --git a/examples/rails_sample/spec/enemy_spec.rb b/examples/rails_sample/spec/enemy_spec.rb new file mode 100644 index 0000000..fdf499f --- /dev/null +++ b/examples/rails_sample/spec/enemy_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +RSpec.describe Enemy do + describe "availability" do + let!(:now) { Time.current } + let(:within_window) { now } + let(:before_window) { now - 2.hours } + let(:after_window) { now + 2.hours } + let!(:bounded_enemy) do + create(:enemy, name: "Bounded Enemy", start_at: now - 1.hour, end_at: now + 1.hour) + end + let!(:timeless_enemy) do + create(:enemy, name: "Unbounded Enemy", start_at: nil, end_at: nil) + end + + it "checks availability per enemy" do + expect(bounded_enemy.available?(within_window)).to be(true) + expect(bounded_enemy.available?(before_window)).to be(false) + expect(bounded_enemy.available?(after_window)).to be(false) + expect(timeless_enemy.available?(within_window)).to be(true) + expect(timeless_enemy.available?(before_window)).to be(true) + expect(timeless_enemy.available?(after_window)).to be(true) + end + + it "filters enemies available at the time" do + enemies = described_class.available_at(within_window) + + expect(enemies).to include(bounded_enemy, timeless_enemy) + + earlier_enemies = described_class.available_at(before_window) + + expect(earlier_enemies).to include(timeless_enemy) + expect(earlier_enemies).not_to include(bounded_enemy) + + later_enemies = described_class.available_at(after_window) + + expect(later_enemies).to include(timeless_enemy) + expect(later_enemies).not_to include(bounded_enemy) + end + end +end diff --git a/examples/rails_sample/spec/factories/armor_factory.rb b/examples/rails_sample/spec/factories/armor_factory.rb new file mode 100644 index 0000000..de72fe9 --- /dev/null +++ b/examples/rails_sample/spec/factories/armor_factory.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :armor do + icon { "fa-solid fa-shield" } + name { "Factory Armor" } + defence { 8.0 } + end +end diff --git a/examples/rails_sample/spec/factories/enemy_factory.rb b/examples/rails_sample/spec/factories/enemy_factory.rb new file mode 100644 index 0000000..da42315 --- /dev/null +++ b/examples/rails_sample/spec/factories/enemy_factory.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :enemy do + name { "Factory Enemy" } + is_boss { false } + start_at { Time.utc(2024, 5, 1, 10, 0, 0) } + end_at { Time.utc(2024, 5, 1, 18, 0, 0) } + attack { 6.0 } + defence { 3.0 } + hp { 18.0 } + exp { 8 } + stamina_cost { 2 } + end +end diff --git a/examples/rails_sample/spec/factories/gun_factory.rb b/examples/rails_sample/spec/factories/gun_factory.rb new file mode 100644 index 0000000..c4f7cec --- /dev/null +++ b/examples/rails_sample/spec/factories/gun_factory.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :gun, class: "Gun" do + name { "Factory Gun" } + attack { 12.0 } + info { { slots: 2, origin: "factory" } } + metadata { { "source" => "factory", "tags" => ["gun"] } } + rarity { :rare } + flags { [:tradeable, :limited] } + end +end diff --git a/examples/rails_sample/spec/factories/level_factory.rb b/examples/rails_sample/spec/factories/level_factory.rb new file mode 100644 index 0000000..daab7ad --- /dev/null +++ b/examples/rails_sample/spec/factories/level_factory.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :level do + lv { 1 } + attack { 2.0 } + defence { 1.0 } + hp { 10.0 } + next_exp { 10 } + hp_recovery_sec { 60 } + stamina { 10 } + stamina_recovery_sec { 30 } + end +end diff --git a/examples/rails_sample/spec/factories/player_factory.rb b/examples/rails_sample/spec/factories/player_factory.rb new file mode 100644 index 0000000..185b8e3 --- /dev/null +++ b/examples/rails_sample/spec/factories/player_factory.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :player do + name { "Factory Player" } + lv { 1 } + association :level, strategy: :build + + after(:build) do |player, evaluator| + player.level = build(:level, lv: evaluator.lv) + end + end +end diff --git a/examples/rails_sample/spec/factories/player_item_factory.rb b/examples/rails_sample/spec/factories/player_item_factory.rb new file mode 100644 index 0000000..033076d --- /dev/null +++ b/examples/rails_sample/spec/factories/player_item_factory.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :player_item do + association :player, strategy: :build + item_type { "Weapon" } + item_id { 1 } + quantity { 1 } + end +end diff --git a/examples/rails_sample/spec/factories/potion_factory.rb b/examples/rails_sample/spec/factories/potion_factory.rb new file mode 100644 index 0000000..927e4ee --- /dev/null +++ b/examples/rails_sample/spec/factories/potion_factory.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :potion do + name { "Factory Potion" } + hp { 25.0 } + end +end diff --git a/examples/rails_sample/spec/factories/reward_factory.rb b/examples/rails_sample/spec/factories/reward_factory.rb new file mode 100644 index 0000000..ae2ebfd --- /dev/null +++ b/examples/rails_sample/spec/factories/reward_factory.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :reward do + enemy_id { 1 } + association :enemy, strategy: :build + association :reward, factory: :weapon, strategy: :build + end +end diff --git a/examples/rails_sample/spec/factories/weapon_factory.rb b/examples/rails_sample/spec/factories/weapon_factory.rb new file mode 100644 index 0000000..6591c23 --- /dev/null +++ b/examples/rails_sample/spec/factories/weapon_factory.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :weapon do + type { "Gun" } + icon { "fa-solid fa-gun" } + name { "Factory Weapon" } + attack { 10.0 } + info { { slots: 1, origin: "factory" } } + metadata { { "source" => "factory", "tags" => ["default"] } } + rarity { :common } + flags { [:tradeable] } + end +end diff --git a/examples/rails_sample/spec/player_spec.rb b/examples/rails_sample/spec/player_spec.rb new file mode 100644 index 0000000..32317f8 --- /dev/null +++ b/examples/rails_sample/spec/player_spec.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +RSpec.describe Player do + before do + PlayerItem.delete_all + described_class.delete_all + end + + describe "creation" do + context "when creating a player" do + let!(:level1) do + create(:level, lv: 1, attack: 5.0, defence: 2.0, hp: 18.0, next_exp: 10, hp_recovery_sec: 60, stamina: 5, stamina_recovery_sec: 30) + end + let!(:player) { described_class.create!(name: "Nova", lv: level1.lv) } + + it "creates a player with the matching level" do + expect(player).to be_a(described_class) + expect(player.level).to be_a(Level) + expect(player.exp).to eq(0) + end + end + end + + describe "#challenge_enemy" do + context "when the player wins" do + let!(:now) { Time.current } + let!(:battle_enemy) do + create(:enemy, name: "Clockwork Rat", start_at: now - 1.hour, end_at: now + 1.hour, attack: 7.0, defence: 3.0, hp: 10.0, exp: 12, stamina_cost: 4) + end + let!(:battle_level_one) do + create(:level, lv: 1, attack: 8.0, defence: 2.0, hp: 20.0, next_exp: 10, hp_recovery_sec: 60, stamina: 6, stamina_recovery_sec: 30) + end + let!(:battle_level_two) do + create(:level, lv: 2, attack: 12.0, defence: 4.0, hp: 26.0, next_exp: 20, hp_recovery_sec: 50, stamina: 8, stamina_recovery_sec: 25) + end + let!(:battle_reward) { create(:weapon, name: "Victory Blade") } + let!(:battle_reward_entry) do + create(:reward, enemy_id: battle_enemy.id, reward_type: "Weapon", reward_id: battle_reward.id) + end + let!(:battle_player) { described_class.create!(name: "Raider", lv: 1) } + let!(:existing_item) do + create(:player_item, player: battle_player, item_type: "Weapon", item_id: battle_reward.id, quantity: 1) + end + + it "levels up and receives rewards after a win" do + result = battle_player.challenge_enemy(battle_enemy, at: now) + + expect(result[:ok]).to be(true) + expect(battle_player.reload.lv).to eq(2) + expect(battle_player.exp).to eq(2) + expect(battle_player.hp).to be_within(0.01).of(13.1373) + expect(battle_player.stamina).to eq(8) + expect(battle_player.items.map(&:id)).to include(battle_reward.id) + expect(battle_player.player_items.find_by(item_type: "Weapon", item_id: battle_reward.id).quantity).to eq(1) + end + end + + context "when the player loses" do + let!(:now) { Time.current } + let!(:battle_enemy) do + create(:enemy, name: "Stone Golem", start_at: now - 1.hour, end_at: now + 1.hour, attack: 5.0, defence: 50.0, hp: 30.0, exp: 1, stamina_cost: 1) + end + let!(:battle_level_one) do + create(:level, lv: 1, attack: 3.0, defence: 1.0, hp: 12.0, next_exp: 10, hp_recovery_sec: 60, stamina: 4, stamina_recovery_sec: 30) + end + let!(:battle_player) { described_class.create!(name: "Hero", lv: 1) } + + it "fails with an attack_too_low reason" do + result = battle_player.challenge_enemy(battle_enemy, at: now) + + expect(result[:ok]).to be(false) + expect(result[:reason]).to eq(:attack_too_low) + expect(battle_player.reload.lv).to eq(1) + end + end + end + + describe "#use_potion" do + context "when a potion is used" do + let!(:now) { Time.current } + let!(:potion_level) do + create(:level, lv: 1, attack: 5.0, defence: 2.0, hp: 18.0, next_exp: 10, hp_recovery_sec: 60, stamina: 5, stamina_recovery_sec: 30) + end + let!(:potion) { create(:potion, hp: 8.0) } + let!(:healing_player) { described_class.create!(name: "Cleric", lv: 1, hp: 6.0, hp_updated_at: now) } + let!(:potion_item) { create(:player_item, player: healing_player, item_type: "Potion", item_id: potion.id, quantity: 1) } + + it "heals instantly with a potion" do + expect(healing_player.use_potion(potion, at: now)).to be(true) + expect(healing_player.reload.hp).to eq(14.0) + expect(healing_player.player_items.where(item_type: "Potion", item_id: potion.id)).to be_empty + end + end + end + + describe "equipment" do + context "when equipping a weapon" do + let!(:level1) do + create(:level, lv: 1, attack: 5.0, defence: 2.0, hp: 18.0, next_exp: 10, hp_recovery_sec: 60, stamina: 5, stamina_recovery_sec: 30) + end + let!(:weapon) { create(:weapon, name: "Starter Gun") } + let!(:player) { described_class.create!(name: "Hero", lv: 1) } + let!(:player_item) { create(:player_item, player: player, item_type: "Weapon", item_id: weapon.id, quantity: 1) } + + it "equips the weapon" do + expect(player.equip_weapon(weapon)).to be(true) + expect(player.reload.weapon_id).to eq(weapon.id) + end + end + + context "when equipping armor without owning it" do + let!(:level1) do + create(:level, lv: 1, attack: 5.0, defence: 2.0, hp: 18.0, next_exp: 10, hp_recovery_sec: 60, stamina: 5, stamina_recovery_sec: 30) + end + let!(:armor) { create(:armor, name: "Starter Armor") } + let!(:player) { described_class.create!(name: "Hero", lv: 1) } + + it "rejects equipping armor" do + expect(player.equip_armor(armor)).to be(false) + expect(player.reload.armor_id).to be_nil + end + end + end +end diff --git a/examples/rails_sample/spec/spec_helper.rb b/examples/rails_sample/spec/spec_helper.rb new file mode 100644 index 0000000..e2c1417 --- /dev/null +++ b/examples/rails_sample/spec/spec_helper.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +ENV["RAILS_ENV"] = "test" +ENV["DATABASE_URL"] ||= "sqlite3::memory:" + +require "bundler/setup" +require "rspec" +require "rails" +require "active_record/railtie" +require "active_support/time" +require "factory_bot" +require "logger" +require "simple_master" + +require_relative "../config/environment" + +ApplicationMaster.prepend(SimpleMaster::Master::Editable) + +ActiveRecord::Base.establish_connection(:test) +ActiveRecord::Base.logger = Rails.logger +ActiveRecord::Base.logger.level = Logger::WARN +ActiveRecord::Migration.verbose = false + +load File.expand_path("../db/schema.rb", __dir__) +FactoryBot.definition_file_paths = [ + File.expand_path("factories", __dir__), +] +FactoryBot.find_definitions + +RSpec.configure do |config| + config.order = :random + config.expect_with :rspec do |c| + c.syntax = :expect + end + config.include FactoryBot::Syntax::Methods + config.around do |example| + dataset = SimpleMaster::Storage::Dataset.new(table_class: SimpleMaster::Storage::TestTable) + SimpleMaster.use_dataset(dataset) do + example.run + end + RequestStore.clear! + end +end diff --git a/gemfiles/rails_7_0.gemfile b/gemfiles/rails_7_0.gemfile new file mode 100644 index 0000000..be65741 --- /dev/null +++ b/gemfiles/rails_7_0.gemfile @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal", group: :development +gem "factory_bot" +gem "puma" +gem "rails", "~> 7.0" +gem "rake", group: :development +gem "rspec" +gem "rubocop", group: :development +gem "rubocop-factory_bot", group: :development +gem "rubocop-performance", group: :development +gem "rubocop-rails", group: :development +gem "rubocop-rspec", group: :development +gem "rubocop-rspec_rails", group: :development +gem "sqlite3", "~> 2.1" + +gemspec path: "../" diff --git a/gemfiles/rails_7_0.gemfile.lock b/gemfiles/rails_7_0.gemfile.lock new file mode 100644 index 0000000..bd69f57 --- /dev/null +++ b/gemfiles/rails_7_0.gemfile.lock @@ -0,0 +1,293 @@ +PATH + remote: .. + specs: + simple_master (0.1.0) + activerecord (>= 7.0) + activesupport (>= 7.0) + request_store (>= 1.0) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + actionmailer (7.2.3) + actionpack (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (7.2.3) + actionview (= 7.2.3) + activesupport (= 7.2.3) + cgi + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4, < 3.3) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (7.2.3) + actionpack (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (7.2.3) + activesupport (= 7.2.3) + builder (~> 3.1) + cgi + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.2.3) + activesupport (= 7.2.3) + globalid (>= 0.3.6) + activemodel (7.2.3) + activesupport (= 7.2.3) + activerecord (7.2.3) + activemodel (= 7.2.3) + activesupport (= 7.2.3) + timeout (>= 0.4.0) + activestorage (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activesupport (= 7.2.3) + marcel (~> 1.0) + activesupport (7.2.3) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + appraisal (2.5.0) + bundler + rake + thor (>= 0.14.0) + ast (2.4.3) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.0.1) + builder (3.3.0) + cgi (0.5.1) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + crass (1.0.6) + date (3.5.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (6.0.1) + erubi (1.13.1) + factory_bot (6.5.6) + activesupport (>= 6.1.0) + globalid (1.3.0) + activesupport (>= 6.1) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + io-console (0.8.2) + irb (1.16.0) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.18.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.7.0) + loofah (2.25.0) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + mail (2.9.0) + logger + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.1.0) + mini_mime (1.1.5) + minitest (6.0.0) + prism (~> 1.5) + net-imap (0.6.2) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.5) + nokogiri (1.18.10-arm64-darwin) + racc (~> 1.4) + parallel (1.27.0) + parser (3.3.10.0) + ast (~> 2.4.1) + racc + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.7.0) + psych (5.3.1) + date + stringio + puma (7.1.0) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.4) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.3.1) + rack (>= 3) + rails (7.2.3) + actioncable (= 7.2.3) + actionmailbox (= 7.2.3) + actionmailer (= 7.2.3) + actionpack (= 7.2.3) + actiontext (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activemodel (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + bundler (>= 1.15.0) + railties (= 7.2.3) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) + cgi + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.1) + rdoc (7.0.1) + erb + psych (>= 4.0.0) + tsort + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + request_store (1.7.0) + rack (>= 1.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.6) + rubocop (1.82.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.48.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.48.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-factory_bot (2.28.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.2) + activesupport (>= 4.2.0) + lint_roller (~> 1.1) + rack (>= 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rspec (3.8.0) + lint_roller (~> 1.1) + rubocop (~> 1.81) + rubocop-rspec_rails (2.32.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rspec (~> 3.5) + ruby-progressbar (1.13.0) + securerandom (0.4.1) + sqlite3 (2.8.1-arm64-darwin) + stringio (3.2.0) + thor (1.4.0) + timeout (0.6.0) + tsort (0.2.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + useragent (0.16.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + zeitwerk (2.7.4) + +PLATFORMS + arm64-darwin-24 + +DEPENDENCIES + appraisal + factory_bot + puma + rails (~> 7.0) + rake + rspec + rubocop + rubocop-factory_bot + rubocop-performance + rubocop-rails + rubocop-rspec + rubocop-rspec_rails + simple_master! + sqlite3 (~> 2.1) + +BUNDLED WITH + 2.7.1 diff --git a/gemfiles/rails_7_1.gemfile b/gemfiles/rails_7_1.gemfile new file mode 100644 index 0000000..bf2a088 --- /dev/null +++ b/gemfiles/rails_7_1.gemfile @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal", group: :development +gem "factory_bot" +gem "puma" +gem "rails", "~> 7.1" +gem "rake", group: :development +gem "rspec" +gem "rubocop", group: :development +gem "rubocop-factory_bot", group: :development +gem "rubocop-performance", group: :development +gem "rubocop-rails", group: :development +gem "rubocop-rspec", group: :development +gem "rubocop-rspec_rails", group: :development +gem "sqlite3", "~> 2.1" + +gemspec path: "../" diff --git a/gemfiles/rails_7_1.gemfile.lock b/gemfiles/rails_7_1.gemfile.lock new file mode 100644 index 0000000..1d2dcdf --- /dev/null +++ b/gemfiles/rails_7_1.gemfile.lock @@ -0,0 +1,293 @@ +PATH + remote: .. + specs: + simple_master (0.1.0) + activerecord (>= 7.0) + activesupport (>= 7.0) + request_store (>= 1.0) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + actionmailer (7.2.3) + actionpack (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (7.2.3) + actionview (= 7.2.3) + activesupport (= 7.2.3) + cgi + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4, < 3.3) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (7.2.3) + actionpack (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (7.2.3) + activesupport (= 7.2.3) + builder (~> 3.1) + cgi + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.2.3) + activesupport (= 7.2.3) + globalid (>= 0.3.6) + activemodel (7.2.3) + activesupport (= 7.2.3) + activerecord (7.2.3) + activemodel (= 7.2.3) + activesupport (= 7.2.3) + timeout (>= 0.4.0) + activestorage (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activesupport (= 7.2.3) + marcel (~> 1.0) + activesupport (7.2.3) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + appraisal (2.5.0) + bundler + rake + thor (>= 0.14.0) + ast (2.4.3) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.0.1) + builder (3.3.0) + cgi (0.5.1) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + crass (1.0.6) + date (3.5.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (6.0.1) + erubi (1.13.1) + factory_bot (6.5.6) + activesupport (>= 6.1.0) + globalid (1.3.0) + activesupport (>= 6.1) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + io-console (0.8.2) + irb (1.16.0) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.18.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.7.0) + loofah (2.25.0) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + mail (2.9.0) + logger + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.1.0) + mini_mime (1.1.5) + minitest (6.0.0) + prism (~> 1.5) + net-imap (0.6.2) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.5) + nokogiri (1.18.10-arm64-darwin) + racc (~> 1.4) + parallel (1.27.0) + parser (3.3.10.0) + ast (~> 2.4.1) + racc + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.7.0) + psych (5.3.1) + date + stringio + puma (7.1.0) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.4) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.3.1) + rack (>= 3) + rails (7.2.3) + actioncable (= 7.2.3) + actionmailbox (= 7.2.3) + actionmailer (= 7.2.3) + actionpack (= 7.2.3) + actiontext (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activemodel (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + bundler (>= 1.15.0) + railties (= 7.2.3) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) + cgi + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.1) + rdoc (7.0.1) + erb + psych (>= 4.0.0) + tsort + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + request_store (1.7.0) + rack (>= 1.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.6) + rubocop (1.82.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.48.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.48.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-factory_bot (2.28.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.2) + activesupport (>= 4.2.0) + lint_roller (~> 1.1) + rack (>= 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rspec (3.8.0) + lint_roller (~> 1.1) + rubocop (~> 1.81) + rubocop-rspec_rails (2.32.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rspec (~> 3.5) + ruby-progressbar (1.13.0) + securerandom (0.4.1) + sqlite3 (2.8.1-arm64-darwin) + stringio (3.2.0) + thor (1.4.0) + timeout (0.6.0) + tsort (0.2.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + useragent (0.16.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + zeitwerk (2.7.4) + +PLATFORMS + arm64-darwin-24 + +DEPENDENCIES + appraisal + factory_bot + puma + rails (~> 7.1) + rake + rspec + rubocop + rubocop-factory_bot + rubocop-performance + rubocop-rails + rubocop-rspec + rubocop-rspec_rails + simple_master! + sqlite3 (~> 2.1) + +BUNDLED WITH + 2.7.1 diff --git a/gemfiles/rails_7_2.gemfile b/gemfiles/rails_7_2.gemfile new file mode 100644 index 0000000..386ea51 --- /dev/null +++ b/gemfiles/rails_7_2.gemfile @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal", group: :development +gem "factory_bot" +gem "puma" +gem "rails", "~> 7.2" +gem "rake", group: :development +gem "rspec" +gem "rubocop", group: :development +gem "rubocop-factory_bot", group: :development +gem "rubocop-performance", group: :development +gem "rubocop-rails", group: :development +gem "rubocop-rspec", group: :development +gem "rubocop-rspec_rails", group: :development +gem "sqlite3", "~> 2.1" + +gemspec path: "../" diff --git a/gemfiles/rails_7_2.gemfile.lock b/gemfiles/rails_7_2.gemfile.lock new file mode 100644 index 0000000..c298d3d --- /dev/null +++ b/gemfiles/rails_7_2.gemfile.lock @@ -0,0 +1,293 @@ +PATH + remote: .. + specs: + simple_master (0.1.0) + activerecord (>= 7.0) + activesupport (>= 7.0) + request_store (>= 1.0) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + actionmailer (7.2.3) + actionpack (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (7.2.3) + actionview (= 7.2.3) + activesupport (= 7.2.3) + cgi + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4, < 3.3) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (7.2.3) + actionpack (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (7.2.3) + activesupport (= 7.2.3) + builder (~> 3.1) + cgi + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.2.3) + activesupport (= 7.2.3) + globalid (>= 0.3.6) + activemodel (7.2.3) + activesupport (= 7.2.3) + activerecord (7.2.3) + activemodel (= 7.2.3) + activesupport (= 7.2.3) + timeout (>= 0.4.0) + activestorage (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activesupport (= 7.2.3) + marcel (~> 1.0) + activesupport (7.2.3) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + appraisal (2.5.0) + bundler + rake + thor (>= 0.14.0) + ast (2.4.3) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.0.1) + builder (3.3.0) + cgi (0.5.1) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + crass (1.0.6) + date (3.5.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (6.0.1) + erubi (1.13.1) + factory_bot (6.5.6) + activesupport (>= 6.1.0) + globalid (1.3.0) + activesupport (>= 6.1) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + io-console (0.8.2) + irb (1.16.0) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.18.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.7.0) + loofah (2.25.0) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + mail (2.9.0) + logger + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.1.0) + mini_mime (1.1.5) + minitest (6.0.0) + prism (~> 1.5) + net-imap (0.6.2) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.5) + nokogiri (1.18.10-arm64-darwin) + racc (~> 1.4) + parallel (1.27.0) + parser (3.3.10.0) + ast (~> 2.4.1) + racc + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.7.0) + psych (5.3.1) + date + stringio + puma (7.1.0) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.4) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.3.1) + rack (>= 3) + rails (7.2.3) + actioncable (= 7.2.3) + actionmailbox (= 7.2.3) + actionmailer (= 7.2.3) + actionpack (= 7.2.3) + actiontext (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activemodel (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + bundler (>= 1.15.0) + railties (= 7.2.3) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) + cgi + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.1) + rdoc (7.0.1) + erb + psych (>= 4.0.0) + tsort + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + request_store (1.7.0) + rack (>= 1.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.6) + rubocop (1.82.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.48.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.48.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-factory_bot (2.28.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.2) + activesupport (>= 4.2.0) + lint_roller (~> 1.1) + rack (>= 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rspec (3.8.0) + lint_roller (~> 1.1) + rubocop (~> 1.81) + rubocop-rspec_rails (2.32.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rspec (~> 3.5) + ruby-progressbar (1.13.0) + securerandom (0.4.1) + sqlite3 (2.8.1-arm64-darwin) + stringio (3.2.0) + thor (1.4.0) + timeout (0.6.0) + tsort (0.2.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + useragent (0.16.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + zeitwerk (2.7.4) + +PLATFORMS + arm64-darwin-24 + +DEPENDENCIES + appraisal + factory_bot + puma + rails (~> 7.2) + rake + rspec + rubocop + rubocop-factory_bot + rubocop-performance + rubocop-rails + rubocop-rspec + rubocop-rspec_rails + simple_master! + sqlite3 (~> 2.1) + +BUNDLED WITH + 2.7.1 diff --git a/gemfiles/rails_8_0.gemfile b/gemfiles/rails_8_0.gemfile new file mode 100644 index 0000000..5b34199 --- /dev/null +++ b/gemfiles/rails_8_0.gemfile @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal", group: :development +gem "factory_bot" +gem "puma" +gem "rails", "~> 8.0" +gem "rake", group: :development +gem "rspec" +gem "rubocop", group: :development +gem "rubocop-factory_bot", group: :development +gem "rubocop-performance", group: :development +gem "rubocop-rails", group: :development +gem "rubocop-rspec", group: :development +gem "rubocop-rspec_rails", group: :development +gem "sqlite3", "~> 2.1" + +gemspec path: "../" diff --git a/gemfiles/rails_8_0.gemfile.lock b/gemfiles/rails_8_0.gemfile.lock new file mode 100644 index 0000000..5a25c24 --- /dev/null +++ b/gemfiles/rails_8_0.gemfile.lock @@ -0,0 +1,292 @@ +PATH + remote: .. + specs: + simple_master (0.1.0) + activerecord (>= 7.0) + activesupport (>= 7.0) + request_store (>= 1.0) + +GEM + remote: https://rubygems.org/ + specs: + action_text-trix (2.1.15) + railties + actioncable (8.1.1) + actionpack (= 8.1.1) + activesupport (= 8.1.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.1.1) + actionpack (= 8.1.1) + activejob (= 8.1.1) + activerecord (= 8.1.1) + activestorage (= 8.1.1) + activesupport (= 8.1.1) + mail (>= 2.8.0) + actionmailer (8.1.1) + actionpack (= 8.1.1) + actionview (= 8.1.1) + activejob (= 8.1.1) + activesupport (= 8.1.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.1.1) + actionview (= 8.1.1) + activesupport (= 8.1.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.1.1) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.1) + activerecord (= 8.1.1) + activestorage (= 8.1.1) + activesupport (= 8.1.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.1.1) + activesupport (= 8.1.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.1.1) + activesupport (= 8.1.1) + globalid (>= 0.3.6) + activemodel (8.1.1) + activesupport (= 8.1.1) + activerecord (8.1.1) + activemodel (= 8.1.1) + activesupport (= 8.1.1) + timeout (>= 0.4.0) + activestorage (8.1.1) + actionpack (= 8.1.1) + activejob (= 8.1.1) + activerecord (= 8.1.1) + activesupport (= 8.1.1) + marcel (~> 1.0) + activesupport (8.1.1) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + json + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + appraisal (2.5.0) + bundler + rake + thor (>= 0.14.0) + ast (2.4.3) + base64 (0.3.0) + bigdecimal (4.0.1) + builder (3.3.0) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + crass (1.0.6) + date (3.5.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (6.0.1) + erubi (1.13.1) + factory_bot (6.5.6) + activesupport (>= 6.1.0) + globalid (1.3.0) + activesupport (>= 6.1) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + io-console (0.8.2) + irb (1.16.0) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.18.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.7.0) + loofah (2.25.0) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + mail (2.9.0) + logger + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.1.0) + mini_mime (1.1.5) + minitest (6.0.0) + prism (~> 1.5) + net-imap (0.6.2) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.5) + nokogiri (1.18.10-arm64-darwin) + racc (~> 1.4) + parallel (1.27.0) + parser (3.3.10.0) + ast (~> 2.4.1) + racc + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.7.0) + psych (5.3.1) + date + stringio + puma (7.1.0) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.4) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.3.1) + rack (>= 3) + rails (8.1.1) + actioncable (= 8.1.1) + actionmailbox (= 8.1.1) + actionmailer (= 8.1.1) + actionpack (= 8.1.1) + actiontext (= 8.1.1) + actionview (= 8.1.1) + activejob (= 8.1.1) + activemodel (= 8.1.1) + activerecord (= 8.1.1) + activestorage (= 8.1.1) + activesupport (= 8.1.1) + bundler (>= 1.15.0) + railties (= 8.1.1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.1.1) + actionpack (= 8.1.1) + activesupport (= 8.1.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.1) + rdoc (7.0.1) + erb + psych (>= 4.0.0) + tsort + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + request_store (1.7.0) + rack (>= 1.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.6) + rubocop (1.82.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.48.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.48.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-factory_bot (2.28.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.2) + activesupport (>= 4.2.0) + lint_roller (~> 1.1) + rack (>= 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rspec (3.8.0) + lint_roller (~> 1.1) + rubocop (~> 1.81) + rubocop-rspec_rails (2.32.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rspec (~> 3.5) + ruby-progressbar (1.13.0) + securerandom (0.4.1) + sqlite3 (2.8.1-arm64-darwin) + stringio (3.2.0) + thor (1.4.0) + timeout (0.6.0) + tsort (0.2.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + uri (1.1.1) + useragent (0.16.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + zeitwerk (2.7.4) + +PLATFORMS + arm64-darwin-24 + +DEPENDENCIES + appraisal + factory_bot + puma + rails (~> 8.0) + rake + rspec + rubocop + rubocop-factory_bot + rubocop-performance + rubocop-rails + rubocop-rspec + rubocop-rspec_rails + simple_master! + sqlite3 (~> 2.1) + +BUNDLED WITH + 2.7.1 diff --git a/gemfiles/rails_8_1.gemfile b/gemfiles/rails_8_1.gemfile new file mode 100644 index 0000000..f857131 --- /dev/null +++ b/gemfiles/rails_8_1.gemfile @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal", group: :development +gem "factory_bot" +gem "puma" +gem "rails", "~> 8.1" +gem "rake", group: :development +gem "rspec" +gem "rubocop", group: :development +gem "rubocop-factory_bot", group: :development +gem "rubocop-performance", group: :development +gem "rubocop-rails", group: :development +gem "rubocop-rspec", group: :development +gem "rubocop-rspec_rails", group: :development +gem "sqlite3", "~> 2.1" + +gemspec path: "../" diff --git a/gemfiles/rails_8_1.gemfile.lock b/gemfiles/rails_8_1.gemfile.lock new file mode 100644 index 0000000..0a08924 --- /dev/null +++ b/gemfiles/rails_8_1.gemfile.lock @@ -0,0 +1,292 @@ +PATH + remote: .. + specs: + simple_master (0.1.0) + activerecord (>= 7.0) + activesupport (>= 7.0) + request_store (>= 1.0) + +GEM + remote: https://rubygems.org/ + specs: + action_text-trix (2.1.15) + railties + actioncable (8.1.1) + actionpack (= 8.1.1) + activesupport (= 8.1.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.1.1) + actionpack (= 8.1.1) + activejob (= 8.1.1) + activerecord (= 8.1.1) + activestorage (= 8.1.1) + activesupport (= 8.1.1) + mail (>= 2.8.0) + actionmailer (8.1.1) + actionpack (= 8.1.1) + actionview (= 8.1.1) + activejob (= 8.1.1) + activesupport (= 8.1.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.1.1) + actionview (= 8.1.1) + activesupport (= 8.1.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.1.1) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.1) + activerecord (= 8.1.1) + activestorage (= 8.1.1) + activesupport (= 8.1.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.1.1) + activesupport (= 8.1.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.1.1) + activesupport (= 8.1.1) + globalid (>= 0.3.6) + activemodel (8.1.1) + activesupport (= 8.1.1) + activerecord (8.1.1) + activemodel (= 8.1.1) + activesupport (= 8.1.1) + timeout (>= 0.4.0) + activestorage (8.1.1) + actionpack (= 8.1.1) + activejob (= 8.1.1) + activerecord (= 8.1.1) + activesupport (= 8.1.1) + marcel (~> 1.0) + activesupport (8.1.1) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + json + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + appraisal (2.5.0) + bundler + rake + thor (>= 0.14.0) + ast (2.4.3) + base64 (0.3.0) + bigdecimal (4.0.1) + builder (3.3.0) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + crass (1.0.6) + date (3.5.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (6.0.1) + erubi (1.13.1) + factory_bot (6.5.6) + activesupport (>= 6.1.0) + globalid (1.3.0) + activesupport (>= 6.1) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + io-console (0.8.2) + irb (1.16.0) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.18.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.7.0) + loofah (2.25.0) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + mail (2.9.0) + logger + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.1.0) + mini_mime (1.1.5) + minitest (6.0.0) + prism (~> 1.5) + net-imap (0.6.2) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.5) + nokogiri (1.18.10-arm64-darwin) + racc (~> 1.4) + parallel (1.27.0) + parser (3.3.10.0) + ast (~> 2.4.1) + racc + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.7.0) + psych (5.3.1) + date + stringio + puma (7.1.0) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.4) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.3.1) + rack (>= 3) + rails (8.1.1) + actioncable (= 8.1.1) + actionmailbox (= 8.1.1) + actionmailer (= 8.1.1) + actionpack (= 8.1.1) + actiontext (= 8.1.1) + actionview (= 8.1.1) + activejob (= 8.1.1) + activemodel (= 8.1.1) + activerecord (= 8.1.1) + activestorage (= 8.1.1) + activesupport (= 8.1.1) + bundler (>= 1.15.0) + railties (= 8.1.1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.1.1) + actionpack (= 8.1.1) + activesupport (= 8.1.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.1) + rdoc (7.0.1) + erb + psych (>= 4.0.0) + tsort + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + request_store (1.7.0) + rack (>= 1.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.6) + rubocop (1.82.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.48.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.48.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-factory_bot (2.28.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.2) + activesupport (>= 4.2.0) + lint_roller (~> 1.1) + rack (>= 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rspec (3.8.0) + lint_roller (~> 1.1) + rubocop (~> 1.81) + rubocop-rspec_rails (2.32.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rspec (~> 3.5) + ruby-progressbar (1.13.0) + securerandom (0.4.1) + sqlite3 (2.8.1-arm64-darwin) + stringio (3.2.0) + thor (1.4.0) + timeout (0.6.0) + tsort (0.2.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + uri (1.1.1) + useragent (0.16.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + zeitwerk (2.7.4) + +PLATFORMS + arm64-darwin-24 + +DEPENDENCIES + appraisal + factory_bot + puma + rails (~> 8.1) + rake + rspec + rubocop + rubocop-factory_bot + rubocop-performance + rubocop-rails + rubocop-rspec + rubocop-rspec_rails + simple_master! + sqlite3 (~> 2.1) + +BUNDLED WITH + 2.7.1 diff --git a/lib/simple_master.rb b/lib/simple_master.rb index 2be9d98..156c2b1 100644 --- a/lib/simple_master.rb +++ b/lib/simple_master.rb @@ -2,6 +2,7 @@ require "active_support/dependencies" require "active_record" +require "request_store" require "logger" require "simple_master/version" diff --git a/simple_master.gemspec b/simple_master.gemspec index a425d14..1a9bdfa 100644 --- a/simple_master.gemspec +++ b/simple_master.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |spec| spec.description = "SimpleMaster loads master tables into memory, builds associations, and offers a small DSL for master data models." spec.homepage = "https://github.com/aktsk/simple_master" spec.license = "MIT" - spec.required_ruby_version = ">= 3.1" + spec.required_ruby_version = ">= 3.2" spec.files = Dir.chdir(__dir__) { Dir["lib/**/*"] diff --git a/spec/simple_master/active_record/extension_spec.rb b/spec/simple_master/active_record/extension_spec.rb new file mode 100644 index 0000000..f7453c2 --- /dev/null +++ b/spec/simple_master/active_record/extension_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SimpleMaster::ActiveRecord::Extension do + before do + reset_active_record_tables + end + + it "connects ActiveRecord to master" do + player = Player.create!(name: "Hero", lv: 2) + level = Level.find_by(:lv, 2) + + expect(player.level).to eq(level) + end + + it "resolves polymorphic belongs_to for master records" do + player = Player.create!(name: "Hero", lv: 1) + player_item1 = PlayerItem.create!(player: player, item_type: "Weapon", item_id: 1) + + expect(player_item1.item).to be_a(Weapon) + expect(player_item1.item.name).to eq("Bronze Pistol") + + player_item2 = PlayerItem.create!(player: player, item: Weapon.find(1)) + + expect(player_item2.item_id).to eq(1) + expect(player_item2.item_type).to eq("Gun") + end + + it "aggregates items through player_items" do + player = Player.create!(name: "Hero", lv: 1) + PlayerItem.create!(player: player, item_type: "Weapon", item_id: 1, quantity: 1) + PlayerItem.create!(player: player, item_type: "Armor", item_id: 2, quantity: 1) + PlayerItem.create!(player: player, item_type: "Potion", item_id: 3, quantity: 1) + + expect(player.items.map(&:class)).to contain_exactly(Gun, Armor, Potion) + expect(player.items.map { |item| item.class.base_class }).to contain_exactly(Weapon, Armor, Potion) + expect(player.items.map(&:name)).to contain_exactly("Bronze Pistol", "Chain Mail", "Elixir") + end +end diff --git a/spec/simple_master/loader/marshal_loader_spec.rb b/spec/simple_master/loader/marshal_loader_spec.rb new file mode 100644 index 0000000..206df7a --- /dev/null +++ b/spec/simple_master/loader/marshal_loader_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "spec_helper" +require "tmpdir" + +RSpec.describe SimpleMaster::Loader::MarshalLoader do + it "loads marshaled records for all tables" do + Dir.mktmpdir do |dir| + # dump current dataset to marshal files + $current_dataset.tables.each do |klass, table| + path = File.join(dir, "#{klass.table_name}.marshal") + File.binwrite(path, Marshal.dump(table.all)) + end + + loader = described_class.new(path: dir) + dataset = SimpleMaster::Storage::Dataset.new(loader: loader) + dataset.load + + SimpleMaster.use_dataset(dataset) do + expect(Weapon.find(1).name).to eq("Bronze Pistol") + expect(Level.find(2).lv).to eq(2) + expect(Enemy.find(2).name).to eq("Ogre Chief") + end + end + end +end diff --git a/spec/simple_master/loader/query_loader_spec.rb b/spec/simple_master/loader/query_loader_spec.rb new file mode 100644 index 0000000..5650b92 --- /dev/null +++ b/spec/simple_master/loader/query_loader_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SimpleMaster::Loader::QueryLoader do + it "loads records from database tables" do + connection = ActiveRecord::Base.connection + + %w(weapons armors potions levels enemies rewards).each do |table| + connection.execute("DELETE FROM #{table}") + end + + info_json = connection.quote('{"slots":1,"origin":"db"}') + metadata_json = connection.quote('{"source":"query","tags":["loader"]}') + + connection.execute <<~SQL + INSERT INTO weapons (id, type, name, attack, info, metadata, rarity, flags) + VALUES (1, 'Gun', 'Query Pistol', 12.5, #{info_json}, #{metadata_json}, 1, 5) + SQL + + connection.execute <<~SQL + INSERT INTO enemies (id, name, is_boss, start_at, end_at, attack, defence, hp) + VALUES (1, 'DB Ogre', 1, '2024-05-01 10:00:00', '2024-05-01 18:00:00', 14.0, 8.0, 45.0) + SQL + + dataset = SimpleMaster::Storage::Dataset.new(loader: described_class.new) + dataset.load + + SimpleMaster.use_dataset(dataset) do + weapon = Weapon.find(1) + + expect(weapon).to be_a(Gun) + expect(weapon.name).to eq("Query Pistol") + expect(weapon.attack).to eq(12.5) + expect(weapon.rarity).to eq(:rare) + expect(weapon.flags).to eq([:tradeable, :limited]) + expect(weapon.info).to(satisfy { |value| + [{ slots: 1, origin: "db" }, { "slots" => 1, "origin" => "db" }].include?(value) + }) + expect(weapon.metadata).to eq({ "source" => "query", "tags" => ["loader"] }) + + enemy = Enemy.find(1) + expect(enemy.is_boss).to be(true) + expect(enemy.start_at).to eq(Time.utc(2024, 5, 1, 10, 0, 0)) + expect(enemy.end_at).to eq(Time.utc(2024, 5, 1, 18, 0, 0)) + end + end +end diff --git a/spec/simple_master/master/associations_spec.rb b/spec/simple_master/master/associations_spec.rb new file mode 100644 index 0000000..506093e --- /dev/null +++ b/spec/simple_master/master/associations_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "associations" do + before { reset_active_record_tables } + + describe "belongs_to (master)" do + it "works correctly" do + reward = Reward.find(1) + + expect(reward.enemy).to eq(Enemy.find(1)) + end + end + + describe "belongs_to (polymorphic master)" do + it "works correctly" do + reward = Reward.find(3) + + expect(reward.reward).to eq(Potion.find(2)) + end + end + + describe "has_many (master)" do + it "works correctly" do + enemy = Enemy.find(1) + + expect(enemy.rewards.map(&:id)).to eq([1, 2]) + end + end + + describe "has_many (ActiveRecord)" do + it "works correctly" do + player = Player.create!(name: "Hero", lv: 2) + level = Level.find_by(:lv, 2) + + expect(level.players).to contain_exactly(player) + end + end +end diff --git a/spec/simple_master/master/cache_spec.rb b/spec/simple_master/master/cache_spec.rb new file mode 100644 index 0000000..001a3e5 --- /dev/null +++ b/spec/simple_master/master/cache_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "cache" do + describe "cache_class_method" do + subject(:cached_result) { Weapon.receivable_sources } + + let!(:result) { Weapon.receivable_sources } + let(:enemies) { Enemy.id_hash } + + it "collects receivable sources per item" do + expect(cached_result).to match({ + 1 => contain_exactly(enemies[1]), + 2 => contain_exactly(enemies[4]), + 3 => contain_exactly(enemies[2], enemies[9], enemies[10]), + }) + expect(cached_result.object_id).to be(result.object_id) + end + + it "keeps STI sub-table caches separate" do + expect(Blade.receivable_sources.keys).to contain_exactly(2) + expect(Gun.receivable_sources.keys).to contain_exactly(1, 3) + end + end + + describe "cache_method" do + subject(:cached_result) { weapon.receivable_sources } + + let(:weapon) { Weapon.find(1) } + let!(:result) { weapon.receivable_sources } + + it "collects receivable sources per item" do + expect(cached_result.map(&:id)).to contain_exactly(1) + expect(cached_result.object_id).to be(result.object_id) + end + end +end diff --git a/spec/simple_master/master/columns_spec.rb b/spec/simple_master/master/columns_spec.rb new file mode 100644 index 0000000..17b1bfe --- /dev/null +++ b/spec/simple_master/master/columns_spec.rb @@ -0,0 +1,172 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "columns" do + before { reset_active_record_tables } + + describe "IntegerColumn" do + it "casts correctly" do + level = Level.new + + level.lv = "3" + expect(level.lv).to eq(3) + expect(level.lv_value_for_sql).to eq(3) + + level.lv = " " + expect(level.lv).to be_nil + expect(level.lv_value_for_sql).to eq("NULL") + + level.lv = 4 + expect(level.lv).to eq(4) + expect(level.lv_value_for_sql).to eq(4) + end + end + + describe "FloatColumn" do + it "casts correctly" do + weapon = Weapon.new + + weapon.attack = "12.5" + expect(weapon.attack).to eq(12.5) + expect(weapon.attack_value_for_sql).to eq(12.5) + + weapon.attack = 7 + expect(weapon.attack).to eq(7.0) + expect(weapon.attack_value_for_sql).to eq(7.0) + + weapon.attack = " " + expect(weapon.attack).to be_nil + expect(weapon.attack_value_for_sql).to eq("NULL") + end + end + + describe "BooleanColumn" do + it "casts correctly" do + enemy = Enemy.new + + enemy.is_boss = "true" + expect(enemy.is_boss).to be(true) + expect(enemy.is_boss?).to be(true) + expect(enemy.is_boss_value_for_sql).to eq(1) + + enemy.is_boss = "0" + expect(enemy.is_boss).to be(false) + expect(enemy.is_boss?).to be(false) + expect(enemy.is_boss_value_for_sql).to eq(0) + + enemy.is_boss = nil + expect(enemy.is_boss).to be_nil + expect(enemy.is_boss?).to be(false) + expect(enemy.is_boss_value_for_sql).to eq("NULL") + end + end + + describe "TimeColumn" do + it "casts correctly" do + enemy = Enemy.new + + enemy.start_at = "2024-05-01T10:00:00Z" + expect(enemy.start_at).to eq(Time.utc(2024, 5, 1, 10, 0, 0)) + expect(enemy.start_at_value_for_sql).to eq("'2024-05-01 10:00:00'") + + time_value = Time.utc(2024, 5, 2, 12, 30, 15) + enemy.end_at = time_value + expect(enemy.end_at).to eq(time_value) + + enemy.start_at = nil + expect(enemy.start_at_value_for_sql).to eq("NULL") + end + end + + describe "JsonColumn" do + it "(with symbolize_names: true) casts correctly" do + weapon = Weapon.new + + weapon.info = { slots: 2, origin: "ruins" } + expect(weapon.info).to eq({ slots: 2, origin: "ruins" }) + expect(weapon.info_value_for_sql).to eq("'{\"slots\":2,\"origin\":\"ruins\"}'") + + weapon.info = "{\"slots\":1,\"origin\":\"forge\"}" + expect(weapon.info).to eq({ slots: 1, origin: "forge" }) + expect(weapon.info_value_for_sql).to eq("'{\"slots\":1,\"origin\":\"forge\"}'") + + weapon.info = "null" + expect(weapon.info).to be_nil + expect(weapon.info_value_for_sql).to eq("NULL") + end + + it "(with symbolize_names: false) casts correctly" do + weapon = Weapon.new + + weapon.metadata = "{\"source\":\"archive\",\"tags\":[\"starter\"]}" + expect(weapon.metadata).to eq({ "source" => "archive", "tags" => ["starter"] }) + expect(weapon.metadata_value_for_sql).to eq("'{\"source\":\"archive\",\"tags\":[\"starter\"]}'") + + weapon.metadata = { "source" => "manual", "tags" => ["custom"] } + expect(weapon.metadata).to eq({ "source" => "manual", "tags" => ["custom"] }) + expect(weapon.metadata_value_for_sql).to eq("'{\"source\":\"manual\",\"tags\":[\"custom\"]}'") + + weapon.metadata = "null" + expect(weapon.metadata).to be_nil + expect(weapon.metadata_value_for_sql).to eq("NULL") + end + end + + describe "EnumColumn" do + it "casts correctly" do + weapon = Weapon.new + + weapon.rarity = "1" + expect(weapon.rarity).to eq(:rare) + expect(weapon.rarity_value_for_sql).to eq(1) + + weapon.rarity = 2 + expect(weapon.rarity).to eq(:epic) + expect(weapon.rarity_value_for_sql).to eq(2) + + weapon.rarity = nil + expect(weapon.rarity_value_for_sql).to eq("NULL") + end + end + + describe "BitmaskColumn" do + it "casts correctly" do + weapon = Weapon.new + + weapon.flags = [:tradeable, :limited] + expect(weapon.flags).to eq([:tradeable, :limited]) + expect(weapon.flags_value_for_sql).to eq(5) + + weapon.flags = :soulbound + expect(weapon.flags).to eq([:soulbound]) + + weapon.flags = nil + expect(weapon.flags_value_for_sql).to eq("NULL") + end + end + + describe "PolymorphicTypeColumn" do + it "casts correctly" do + reward = Reward.new + + reward.reward_type = :Potion + expect(reward.reward_type).to eq("Potion") + expect(reward.reward_type_class).to eq(Potion) + expect(reward.reward_type_value_for_sql).to eq("'Potion'") + + reward = Reward.find(3) + expect(reward.reward_type_class).to eq(Potion) + expect(reward.reward).to eq(Potion.find(2)) + end + end + + describe "StiTypeColumn" do + it "casts correctly" do + weapon = Gun.new + + expect(weapon.type).to eq("Gun") + expect(weapon.type_value_for_sql).to eq("'Gun'") + end + end +end diff --git a/spec/simple_master/master/filterable_spec.rb b/spec/simple_master/master/filterable_spec.rb new file mode 100644 index 0000000..c4e7eb0 --- /dev/null +++ b/spec/simple_master/master/filterable_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SimpleMaster::Master::Filterable do + it "supports id-based find helpers" do + expect(Weapon.find(1).name).to eq("Bronze Pistol") + expect(Weapon.find_by_id(99)).to be_nil + expect(Weapon.find_by_ids([1, 3]).map(&:id)).to eq([1, 3]) + expect { Weapon.find_by_ids!([1, 99]) }.to raise_error(KeyError) + end + + it "supports grouped lookups" do + expect(Reward.find_by(:enemy_id, 1).id).to eq(1) + expect(Reward.all_by(:enemy_id, 1).map(&:id)).to eq([1, 2]) + expect { Reward.all_by!(:enemy_id, 99) }.to raise_error(KeyError) + expect(Reward.all_in(:enemy_id, [1, 2]).map(&:id)).to eq([1, 2, 3, 4]) + expect(Reward.all_in(:enemy_id, [1, 2])).to be_frozen + end + + it "delegates collection helpers to all" do + expect(Weapon.pluck(:name)).to include("Bronze Pistol", "Silver Saber", "Crimson Rifle") + expect(Weapon.first).to be_a(Weapon) + end + + it "checks id existence" do + expect(Weapon.exists?(1)).to be(true) + expect(Weapon.exists?(99)).to be(false) + end + + it "raises on missing group key and handles empty all_in" do + expect { Reward.all_by(:unknown_key, 1) }.to raise_error(KeyError) + expect(Reward.all_in(:enemy_id, [])).to eq([]) + expect(Reward.all_in(:enemy_id, [])).to be_frozen + end +end diff --git a/spec/simple_master/master/model_spec.rb b/spec/simple_master/master/model_spec.rb new file mode 100644 index 0000000..5a2bf7c --- /dev/null +++ b/spec/simple_master/master/model_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "model" do + describe "sti_class?" do + it "returns true for STI base classes and subclasses" do + expect(Weapon.sti_class?).to be(true) + expect(Gun.sti_class?).to be(true) + expect(Armor.sti_class?).to be(false) + end + end + + describe "sti_base_class?" do + it "returns true only for STI base classes" do + expect(Weapon.sti_base_class?).to be(true) + expect(Gun.sti_base_class?).to be(false) + expect(Armor.sti_base_class?).to be(false) + end + end + + describe "sti_sub_class?" do + it "returns true only for STI subclasses" do + expect(Gun.sti_sub_class?).to be(true) + expect(Blade.sti_sub_class?).to be(true) + expect(Weapon.sti_sub_class?).to be(false) + expect(Armor.sti_sub_class?).to be(false) + end + end + + describe "base_class" do + it "returns the STI base class when present" do + expect(Weapon.base_class).to eq(Weapon) + expect(Gun.base_class).to eq(Weapon) + expect(Blade.base_class).to eq(Weapon) + expect(Armor.base_class).to eq(Armor) + end + end + + describe "base_class?" do + it "returns true for STI base classes and non-STI classes" do + expect(Weapon.base_class?).to be(true) + expect(Gun.base_class?).to be(false) + expect(Armor.base_class?).to be(true) + end + end + + describe "globalized?" do + it "returns true when any column is globalized" do + expect(Weapon.globalized?).to be(true) + end + + it "returns false when no globalized columns exist" do + expect(Armor.globalized?).to be(false) + end + end +end diff --git a/spec/simple_master/master/queryable_spec.rb b/spec/simple_master/master/queryable_spec.rb new file mode 100644 index 0000000..37bfaec --- /dev/null +++ b/spec/simple_master/master/queryable_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SimpleMaster::Master::Queryable do + let(:connection) { ActiveRecord::Base.connection } + + before do + %w(weapons levels).each do |table| + connection.execute("DELETE FROM #{table}") + end + end + + describe ".table_available?" do + it "returns true for existing tables" do + expect(Weapon.table_available?).to be(true) + end + + it "returns false for missing tables" do + allow(Weapon).to receive(:table_name).and_return("missing_table") + + expect(Weapon.table_available?).to be(false) + end + end + + describe ".query_select_all" do + it "returns rows from the backing table" do + connection.execute <<~SQL + INSERT INTO weapons (id, type, name, attack, rarity, flags) + VALUES (1, 'Gun', 'Queryable Sword', 9.5, 2, 1) + SQL + + result = Weapon.query_select_all + row = result.to_a.find { |record| record["id"] == 1 } + + expect(result.columns).to include("name", "attack") + expect(row["name"]).to eq("Queryable Sword") + expect(row["attack"]).to eq(9.5) + end + end + + describe ".sqlite_insert_query" do + it "builds SQL that can be executed" do + weapon = Weapon.new( + id: 1, + type: "Gun", + name: "SQL Pistol", + attack: 11.0, + info: { slots: 1 }, + metadata: { "source" => "sql" }, + rarity: :rare, + flags: [:tradeable], + ) + + sql = Weapon.sqlite_insert_query([weapon]) + connection.execute(sql) + + row = connection.select_all("SELECT name, rarity, flags FROM weapons WHERE id = 1").to_a.first + expect(row["name"]).to eq("SQL Pistol") + expect(row["rarity"].to_i).to eq(1) + expect(row["flags"].to_i).to eq(1) + end + end + + describe ".query_delete_all" do + it "removes all rows from the table" do + connection.execute("INSERT INTO levels (id, lv, attack) VALUES (1, 3, 2.5)") + + expect(connection.select_all("SELECT * FROM levels").to_a.size).to eq(1) + + Level.query_delete_all + + expect(connection.select_all("SELECT * FROM levels").to_a).to be_empty + end + end +end diff --git a/spec/simple_master/master/validatable_spec.rb b/spec/simple_master/master/validatable_spec.rb new file mode 100644 index 0000000..aebb779 --- /dev/null +++ b/spec/simple_master/master/validatable_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SimpleMaster::Master::Validatable do + it "validates all master records" do + errors = ApplicationMaster.validate_all_records + expect(errors).to be_empty + end +end diff --git a/spec/simple_master/storage/dataset_spec.rb b/spec/simple_master/storage/dataset_spec.rb new file mode 100644 index 0000000..e69602d --- /dev/null +++ b/spec/simple_master/storage/dataset_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SimpleMaster::Storage::Dataset do + it "keeps loaded records when digest is unchanged" do + weapon = Weapon.first + $current_dataset.load + + expect(Weapon.first.object_id).to equal(weapon.object_id) + end + + it "duplicates dataset without reloading tables" do + dup_dataset = $current_dataset.duplicate + dup_dataset.load + + expect(SimpleMaster.use_dataset(dup_dataset) { Weapon.first.object_id }).to equal(Weapon.first.object_id) + end + + it "memoizes dataset cache_fetch" do + dataset = described_class.new(loader: JsonLoader.new) + + first_time = dataset.cache_fetch(:foo) { Object.new } + second_time = dataset.cache_fetch(:foo) { Object.new } + + expect(first_time.object_id).to eq(second_time.object_id) + end +end diff --git a/spec/simple_master/storage/loader_spec.rb b/spec/simple_master/storage/loader_spec.rb new file mode 100644 index 0000000..9fab7c8 --- /dev/null +++ b/spec/simple_master/storage/loader_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "Loader" do + describe "loading" do + it "instantiates STI records as subclasses" do + pistol = Weapon.find(1) + + expect(pistol).to be_a(Gun) + expect(pistol.attack).to eq(12.5) + + expect(Gun.all).to include(pistol) + expect(Weapon.all).to include(pistol) + + expect(Blade.find_by_id(1)).to be_nil + expect(Gun.find(1)).to eq(pistol) + + expect(Weapon.all.map(&:id)).to contain_exactly(1, 2, 3, 4) + expect(Gun.all.map(&:id)).to contain_exactly(1, 3) + expect(Blade.all.map(&:id)).to contain_exactly(2, 4) + end + end + + describe "Globalization" do + let(:potion) { Potion.find(1) } + let(:weapon) { Weapon.find(1) } + + it "can be loaded by loader" do + I18n.with_locale(:ja) do + expect(potion.name).to eq("マイナーヒール") + end + end + + it "applies globalize_proc when provided" do + I18n.with_locale(:ja) do + expect(weapon.name).to eq("ブロンズピストル") + end + end + end + + it "applies diff json to dataset" do + diff = { + "weapons" => { + "2" => { "attack" => 42.0, "_globalized_name" => { en: "Gold Saber", ja: "ゴールドセイバー" } }, + "3" => nil, + }, + } + + dataset = $current_dataset.duplicate(diff: diff) + dataset.load + + SimpleMaster.use_dataset(dataset) do + weapon = Weapon.find(2) + + expect(weapon.attack).to eq(42.0) + expect(weapon.name).to eq("Gold Saber") + I18n.with_locale(:ja) do + expect(weapon.name).to eq("ゴールドセイバー") + end + expect(Weapon.id_hash).not_to have_key(3) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..5b8b7da --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +ENV["DATABASE_URL"] ||= "sqlite3::memory:" + +require "bundler/setup" +require "rspec" +require "rails" +require "active_record/railtie" +require "active_support/time" +require "factory_bot" +require "logger" +require "simple_master" + +# Boot sample Rails app +require_relative "../examples/rails_sample/lib/json_loader" +require_relative "../examples/rails_sample/config/environment" + +ActiveRecord::Base.establish_connection(:test) +ActiveRecord::Base.logger = Rails.logger +ActiveRecord::Base.logger.level = Logger::WARN +ActiveRecord::Migration.verbose = false + +# Load schema and models +load File.expand_path("../examples/rails_sample/db/schema.rb", __dir__) +%w( + application_record + player + player_item + weapon + armor + potion + level + enemy + reward +).each do |model| + require File.expand_path("../examples/rails_sample/app/models/#{model}", __dir__) +end + +# Test support helpers +require_relative "support/dataset_helper" if File.exist?(File.expand_path("support/dataset_helper.rb", __dir__)) +FactoryBot.find_definitions + +I18n.available_locales = [:en, :ja] + +RSpec.configure do |config| + config.order = :random + config.expect_with :rspec do |c| + c.syntax = :expect + end + config.include DatasetHelper + config.include FactoryBot::Syntax::Methods + config.after { RequestStore.clear! } +end diff --git a/spec/support/dataset_helper.rb b/spec/support/dataset_helper.rb new file mode 100644 index 0000000..267bbe6 --- /dev/null +++ b/spec/support/dataset_helper.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module DatasetHelper + def build_dataset(loader: JsonLoader.new, diff: nil) + dataset = SimpleMaster::Storage::Dataset.new(loader: loader) + dataset.diff = diff if diff + # dataset.load + dataset + end + + def with_dataset(loader: JsonLoader.new, diff: nil) + dataset = build_dataset(loader: loader, diff: diff) + SimpleMaster.use_dataset(dataset) do + yield dataset + end + end + + def reset_active_record_tables + PlayerItem.delete_all + Player.delete_all + end +end + +RSpec.configure do |config| + config.include DatasetHelper +end From f1c08f880e5b9b0670f1693fb8282b7bdba45d47 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Wed, 24 Dec 2025 11:42:24 +0900 Subject: [PATCH 2/3] Make empty options editable after def_column --- lib/simple_master/master/dsl.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/simple_master/master/dsl.rb b/lib/simple_master/master/dsl.rb index 4040b31..fcaa38b 100644 --- a/lib/simple_master/master/dsl.rb +++ b/lib/simple_master/master/dsl.rb @@ -11,7 +11,7 @@ module Dsl bitmask: Column::BitmaskColumn, }.freeze - def def_column(column_name, options = EMPTY_HASH) + def def_column(column_name, options = {}) column = column_type(column_name, options).new(column_name, options) columns << column From 485f85d5b57ab1022ef7262b4538e64f5fefedca Mon Sep 17 00:00:00 2001 From: Go Murakami Date: Thu, 25 Dec 2025 23:54:45 +0900 Subject: [PATCH 3/3] Ignore Gemfile.lock and tweak CI --- .github/workflows/ci.yml | 2 +- .gitignore | 6 + Gemfile | 4 +- Gemfile.lock | 297 -------------------------- gemfiles/rails_7_0.gemfile.lock | 293 ------------------------- gemfiles/rails_7_1.gemfile.lock | 293 ------------------------- gemfiles/rails_7_2.gemfile.lock | 293 ------------------------- gemfiles/rails_8_0.gemfile.lock | 292 ------------------------- gemfiles/rails_8_1.gemfile.lock | 292 ------------------------- lib/simple_master/master/queryable.rb | 10 +- 10 files changed, 14 insertions(+), 1768 deletions(-) delete mode 100644 Gemfile.lock delete mode 100644 gemfiles/rails_7_0.gemfile.lock delete mode 100644 gemfiles/rails_7_1.gemfile.lock delete mode 100644 gemfiles/rails_7_2.gemfile.lock delete mode 100644 gemfiles/rails_8_0.gemfile.lock delete mode 100644 gemfiles/rails_8_1.gemfile.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5569ff..0a513de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - rails-8_0 - rails-8_1 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.gitignore b/.gitignore index 4f02cc6..b350041 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ /tmp/ /.bundle/ /vendor/bundle/ +/Gemfile.lock +/gemfiles/*.gemfile.lock +/.idea/ +/pkg +*.lock +*.gem diff --git a/Gemfile b/Gemfile index e895375..2bd9e07 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ gemspec gem "factory_bot", group: :test gem "puma", group: :development -gem "rails", ENV.fetch("RAILS_VERSION", "~> 7.2") +gem "rails", ENV.fetch("RAILS_VERSION", "~> 7.2"), group: :development gem "rspec", group: :test gem "rubocop", group: :development @@ -15,7 +15,7 @@ gem 'rubocop-performance', group: :development gem 'rubocop-rails', group: :development gem 'rubocop-rspec', group: :development gem 'rubocop-rspec_rails', group: :development -gem "sqlite3", "~> 2.1" +gem "sqlite3", "~> 2.1", group: :development gem "appraisal", group: :development gem "rake", group: :development diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 5cdfdfb..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,297 +0,0 @@ -PATH - remote: . - specs: - simple_master (0.1.0) - activerecord (>= 7.0) - activesupport (>= 7.0) - request_store (>= 1.0) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (7.2.3) - actionpack (= 7.2.3) - activesupport (= 7.2.3) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - zeitwerk (~> 2.6) - actionmailbox (7.2.3) - actionpack (= 7.2.3) - activejob (= 7.2.3) - activerecord (= 7.2.3) - activestorage (= 7.2.3) - activesupport (= 7.2.3) - mail (>= 2.8.0) - actionmailer (7.2.3) - actionpack (= 7.2.3) - actionview (= 7.2.3) - activejob (= 7.2.3) - activesupport (= 7.2.3) - mail (>= 2.8.0) - rails-dom-testing (~> 2.2) - actionpack (7.2.3) - actionview (= 7.2.3) - activesupport (= 7.2.3) - cgi - nokogiri (>= 1.8.5) - racc - rack (>= 2.2.4, < 3.3) - rack-session (>= 1.0.1) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - useragent (~> 0.16) - actiontext (7.2.3) - actionpack (= 7.2.3) - activerecord (= 7.2.3) - activestorage (= 7.2.3) - activesupport (= 7.2.3) - globalid (>= 0.6.0) - nokogiri (>= 1.8.5) - actionview (7.2.3) - activesupport (= 7.2.3) - builder (~> 3.1) - cgi - erubi (~> 1.11) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - activejob (7.2.3) - activesupport (= 7.2.3) - globalid (>= 0.3.6) - activemodel (7.2.3) - activesupport (= 7.2.3) - activerecord (7.2.3) - activemodel (= 7.2.3) - activesupport (= 7.2.3) - timeout (>= 0.4.0) - activestorage (7.2.3) - actionpack (= 7.2.3) - activejob (= 7.2.3) - activerecord (= 7.2.3) - activesupport (= 7.2.3) - marcel (~> 1.0) - activesupport (7.2.3) - base64 - benchmark (>= 0.3) - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - appraisal (2.5.0) - bundler - rake - thor (>= 0.14.0) - ast (2.4.3) - base64 (0.3.0) - benchmark (0.5.0) - bigdecimal (4.0.1) - builder (3.3.0) - cgi (0.5.1) - concurrent-ruby (1.3.6) - connection_pool (3.0.2) - crass (1.0.6) - date (3.5.1) - diff-lcs (1.6.2) - drb (2.2.3) - erb (6.0.1) - erubi (1.13.1) - factory_bot (6.5.6) - activesupport (>= 6.1.0) - globalid (1.3.0) - activesupport (>= 6.1) - i18n (1.14.7) - concurrent-ruby (~> 1.0) - io-console (0.8.2) - irb (1.16.0) - pp (>= 0.6.0) - rdoc (>= 4.0.0) - reline (>= 0.4.2) - json (2.18.0) - language_server-protocol (3.17.0.5) - lint_roller (1.1.0) - logger (1.7.0) - loofah (2.25.0) - crass (~> 1.0.2) - nokogiri (>= 1.12.0) - mail (2.9.0) - logger - mini_mime (>= 0.1.1) - net-imap - net-pop - net-smtp - marcel (1.1.0) - mini_mime (1.1.5) - minitest (6.0.0) - prism (~> 1.5) - net-imap (0.6.2) - date - net-protocol - net-pop (0.1.2) - net-protocol - net-protocol (0.2.2) - timeout - net-smtp (0.5.1) - net-protocol - nio4r (2.7.5) - nokogiri (1.18.10-arm64-darwin) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-linux-gnu) - racc (~> 1.4) - parallel (1.27.0) - parser (3.3.10.0) - ast (~> 2.4.1) - racc - pp (0.6.3) - prettyprint - prettyprint (0.2.0) - prism (1.7.0) - psych (5.3.1) - date - stringio - puma (7.1.0) - nio4r (~> 2.0) - racc (1.8.1) - rack (3.2.4) - rack-session (2.1.1) - base64 (>= 0.1.0) - rack (>= 3.0.0) - rack-test (2.2.0) - rack (>= 1.3) - rackup (2.3.1) - rack (>= 3) - rails (7.2.3) - actioncable (= 7.2.3) - actionmailbox (= 7.2.3) - actionmailer (= 7.2.3) - actionpack (= 7.2.3) - actiontext (= 7.2.3) - actionview (= 7.2.3) - activejob (= 7.2.3) - activemodel (= 7.2.3) - activerecord (= 7.2.3) - activestorage (= 7.2.3) - activesupport (= 7.2.3) - bundler (>= 1.15.0) - railties (= 7.2.3) - rails-dom-testing (2.3.0) - activesupport (>= 5.0.0) - minitest - nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) - nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (7.2.3) - actionpack (= 7.2.3) - activesupport (= 7.2.3) - cgi - irb (~> 1.13) - rackup (>= 1.0.0) - rake (>= 12.2) - thor (~> 1.0, >= 1.2.2) - tsort (>= 0.2) - zeitwerk (~> 2.6) - rainbow (3.1.1) - rake (13.3.1) - rdoc (7.0.1) - erb - psych (>= 4.0.0) - tsort - regexp_parser (2.11.3) - reline (0.6.3) - io-console (~> 0.5) - request_store (1.7.0) - rack (>= 1.4) - rspec (3.13.2) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) - rspec-core (3.13.6) - rspec-support (~> 3.13.0) - rspec-expectations (3.13.5) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-mocks (3.13.7) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-support (3.13.6) - rubocop (1.82.0) - json (~> 2.3) - language_server-protocol (~> 3.17.0.2) - lint_roller (~> 1.1.0) - parallel (~> 1.10) - parser (>= 3.3.0.2) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.48.0, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.48.0) - parser (>= 3.3.7.2) - prism (~> 1.4) - rubocop-factory_bot (2.28.0) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-performance (1.26.1) - lint_roller (~> 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rails (2.34.2) - activesupport (>= 4.2.0) - lint_roller (~> 1.1) - rack (>= 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rspec (3.8.0) - lint_roller (~> 1.1) - rubocop (~> 1.81) - rubocop-rspec_rails (2.32.0) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-rspec (~> 3.5) - ruby-progressbar (1.13.0) - securerandom (0.4.1) - sqlite3 (2.8.1-arm64-darwin) - sqlite3 (2.8.1-x86_64-linux-gnu) - stringio (3.2.0) - thor (1.4.0) - timeout (0.6.0) - tsort (0.2.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (3.2.0) - unicode-emoji (~> 4.1) - unicode-emoji (4.2.0) - useragent (0.16.11) - websocket-driver (0.8.0) - base64 - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - zeitwerk (2.7.4) - -PLATFORMS - arm64-darwin-24 - x86_64-linux - -DEPENDENCIES - appraisal - factory_bot - puma - rails (~> 7.2) - rake - rspec - rubocop - rubocop-factory_bot - rubocop-performance - rubocop-rails - rubocop-rspec - rubocop-rspec_rails - simple_master! - sqlite3 (~> 2.1) - -BUNDLED WITH - 2.7.1 diff --git a/gemfiles/rails_7_0.gemfile.lock b/gemfiles/rails_7_0.gemfile.lock deleted file mode 100644 index bd69f57..0000000 --- a/gemfiles/rails_7_0.gemfile.lock +++ /dev/null @@ -1,293 +0,0 @@ -PATH - remote: .. - specs: - simple_master (0.1.0) - activerecord (>= 7.0) - activesupport (>= 7.0) - request_store (>= 1.0) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (7.2.3) - actionpack (= 7.2.3) - activesupport (= 7.2.3) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - zeitwerk (~> 2.6) - actionmailbox (7.2.3) - actionpack (= 7.2.3) - activejob (= 7.2.3) - activerecord (= 7.2.3) - activestorage (= 7.2.3) - activesupport (= 7.2.3) - mail (>= 2.8.0) - actionmailer (7.2.3) - actionpack (= 7.2.3) - actionview (= 7.2.3) - activejob (= 7.2.3) - activesupport (= 7.2.3) - mail (>= 2.8.0) - rails-dom-testing (~> 2.2) - actionpack (7.2.3) - actionview (= 7.2.3) - activesupport (= 7.2.3) - cgi - nokogiri (>= 1.8.5) - racc - rack (>= 2.2.4, < 3.3) - rack-session (>= 1.0.1) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - useragent (~> 0.16) - actiontext (7.2.3) - actionpack (= 7.2.3) - activerecord (= 7.2.3) - activestorage (= 7.2.3) - activesupport (= 7.2.3) - globalid (>= 0.6.0) - nokogiri (>= 1.8.5) - actionview (7.2.3) - activesupport (= 7.2.3) - builder (~> 3.1) - cgi - erubi (~> 1.11) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - activejob (7.2.3) - activesupport (= 7.2.3) - globalid (>= 0.3.6) - activemodel (7.2.3) - activesupport (= 7.2.3) - activerecord (7.2.3) - activemodel (= 7.2.3) - activesupport (= 7.2.3) - timeout (>= 0.4.0) - activestorage (7.2.3) - actionpack (= 7.2.3) - activejob (= 7.2.3) - activerecord (= 7.2.3) - activesupport (= 7.2.3) - marcel (~> 1.0) - activesupport (7.2.3) - base64 - benchmark (>= 0.3) - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - appraisal (2.5.0) - bundler - rake - thor (>= 0.14.0) - ast (2.4.3) - base64 (0.3.0) - benchmark (0.5.0) - bigdecimal (4.0.1) - builder (3.3.0) - cgi (0.5.1) - concurrent-ruby (1.3.6) - connection_pool (3.0.2) - crass (1.0.6) - date (3.5.1) - diff-lcs (1.6.2) - drb (2.2.3) - erb (6.0.1) - erubi (1.13.1) - factory_bot (6.5.6) - activesupport (>= 6.1.0) - globalid (1.3.0) - activesupport (>= 6.1) - i18n (1.14.7) - concurrent-ruby (~> 1.0) - io-console (0.8.2) - irb (1.16.0) - pp (>= 0.6.0) - rdoc (>= 4.0.0) - reline (>= 0.4.2) - json (2.18.0) - language_server-protocol (3.17.0.5) - lint_roller (1.1.0) - logger (1.7.0) - loofah (2.25.0) - crass (~> 1.0.2) - nokogiri (>= 1.12.0) - mail (2.9.0) - logger - mini_mime (>= 0.1.1) - net-imap - net-pop - net-smtp - marcel (1.1.0) - mini_mime (1.1.5) - minitest (6.0.0) - prism (~> 1.5) - net-imap (0.6.2) - date - net-protocol - net-pop (0.1.2) - net-protocol - net-protocol (0.2.2) - timeout - net-smtp (0.5.1) - net-protocol - nio4r (2.7.5) - nokogiri (1.18.10-arm64-darwin) - racc (~> 1.4) - parallel (1.27.0) - parser (3.3.10.0) - ast (~> 2.4.1) - racc - pp (0.6.3) - prettyprint - prettyprint (0.2.0) - prism (1.7.0) - psych (5.3.1) - date - stringio - puma (7.1.0) - nio4r (~> 2.0) - racc (1.8.1) - rack (3.2.4) - rack-session (2.1.1) - base64 (>= 0.1.0) - rack (>= 3.0.0) - rack-test (2.2.0) - rack (>= 1.3) - rackup (2.3.1) - rack (>= 3) - rails (7.2.3) - actioncable (= 7.2.3) - actionmailbox (= 7.2.3) - actionmailer (= 7.2.3) - actionpack (= 7.2.3) - actiontext (= 7.2.3) - actionview (= 7.2.3) - activejob (= 7.2.3) - activemodel (= 7.2.3) - activerecord (= 7.2.3) - activestorage (= 7.2.3) - activesupport (= 7.2.3) - bundler (>= 1.15.0) - railties (= 7.2.3) - rails-dom-testing (2.3.0) - activesupport (>= 5.0.0) - minitest - nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) - nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (7.2.3) - actionpack (= 7.2.3) - activesupport (= 7.2.3) - cgi - irb (~> 1.13) - rackup (>= 1.0.0) - rake (>= 12.2) - thor (~> 1.0, >= 1.2.2) - tsort (>= 0.2) - zeitwerk (~> 2.6) - rainbow (3.1.1) - rake (13.3.1) - rdoc (7.0.1) - erb - psych (>= 4.0.0) - tsort - regexp_parser (2.11.3) - reline (0.6.3) - io-console (~> 0.5) - request_store (1.7.0) - rack (>= 1.4) - rspec (3.13.2) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) - rspec-core (3.13.6) - rspec-support (~> 3.13.0) - rspec-expectations (3.13.5) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-mocks (3.13.7) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-support (3.13.6) - rubocop (1.82.1) - json (~> 2.3) - language_server-protocol (~> 3.17.0.2) - lint_roller (~> 1.1.0) - parallel (~> 1.10) - parser (>= 3.3.0.2) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.48.0, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.48.0) - parser (>= 3.3.7.2) - prism (~> 1.4) - rubocop-factory_bot (2.28.0) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-performance (1.26.1) - lint_roller (~> 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rails (2.34.2) - activesupport (>= 4.2.0) - lint_roller (~> 1.1) - rack (>= 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rspec (3.8.0) - lint_roller (~> 1.1) - rubocop (~> 1.81) - rubocop-rspec_rails (2.32.0) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-rspec (~> 3.5) - ruby-progressbar (1.13.0) - securerandom (0.4.1) - sqlite3 (2.8.1-arm64-darwin) - stringio (3.2.0) - thor (1.4.0) - timeout (0.6.0) - tsort (0.2.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (3.2.0) - unicode-emoji (~> 4.1) - unicode-emoji (4.2.0) - useragent (0.16.11) - websocket-driver (0.8.0) - base64 - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - zeitwerk (2.7.4) - -PLATFORMS - arm64-darwin-24 - -DEPENDENCIES - appraisal - factory_bot - puma - rails (~> 7.0) - rake - rspec - rubocop - rubocop-factory_bot - rubocop-performance - rubocop-rails - rubocop-rspec - rubocop-rspec_rails - simple_master! - sqlite3 (~> 2.1) - -BUNDLED WITH - 2.7.1 diff --git a/gemfiles/rails_7_1.gemfile.lock b/gemfiles/rails_7_1.gemfile.lock deleted file mode 100644 index 1d2dcdf..0000000 --- a/gemfiles/rails_7_1.gemfile.lock +++ /dev/null @@ -1,293 +0,0 @@ -PATH - remote: .. - specs: - simple_master (0.1.0) - activerecord (>= 7.0) - activesupport (>= 7.0) - request_store (>= 1.0) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (7.2.3) - actionpack (= 7.2.3) - activesupport (= 7.2.3) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - zeitwerk (~> 2.6) - actionmailbox (7.2.3) - actionpack (= 7.2.3) - activejob (= 7.2.3) - activerecord (= 7.2.3) - activestorage (= 7.2.3) - activesupport (= 7.2.3) - mail (>= 2.8.0) - actionmailer (7.2.3) - actionpack (= 7.2.3) - actionview (= 7.2.3) - activejob (= 7.2.3) - activesupport (= 7.2.3) - mail (>= 2.8.0) - rails-dom-testing (~> 2.2) - actionpack (7.2.3) - actionview (= 7.2.3) - activesupport (= 7.2.3) - cgi - nokogiri (>= 1.8.5) - racc - rack (>= 2.2.4, < 3.3) - rack-session (>= 1.0.1) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - useragent (~> 0.16) - actiontext (7.2.3) - actionpack (= 7.2.3) - activerecord (= 7.2.3) - activestorage (= 7.2.3) - activesupport (= 7.2.3) - globalid (>= 0.6.0) - nokogiri (>= 1.8.5) - actionview (7.2.3) - activesupport (= 7.2.3) - builder (~> 3.1) - cgi - erubi (~> 1.11) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - activejob (7.2.3) - activesupport (= 7.2.3) - globalid (>= 0.3.6) - activemodel (7.2.3) - activesupport (= 7.2.3) - activerecord (7.2.3) - activemodel (= 7.2.3) - activesupport (= 7.2.3) - timeout (>= 0.4.0) - activestorage (7.2.3) - actionpack (= 7.2.3) - activejob (= 7.2.3) - activerecord (= 7.2.3) - activesupport (= 7.2.3) - marcel (~> 1.0) - activesupport (7.2.3) - base64 - benchmark (>= 0.3) - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - appraisal (2.5.0) - bundler - rake - thor (>= 0.14.0) - ast (2.4.3) - base64 (0.3.0) - benchmark (0.5.0) - bigdecimal (4.0.1) - builder (3.3.0) - cgi (0.5.1) - concurrent-ruby (1.3.6) - connection_pool (3.0.2) - crass (1.0.6) - date (3.5.1) - diff-lcs (1.6.2) - drb (2.2.3) - erb (6.0.1) - erubi (1.13.1) - factory_bot (6.5.6) - activesupport (>= 6.1.0) - globalid (1.3.0) - activesupport (>= 6.1) - i18n (1.14.7) - concurrent-ruby (~> 1.0) - io-console (0.8.2) - irb (1.16.0) - pp (>= 0.6.0) - rdoc (>= 4.0.0) - reline (>= 0.4.2) - json (2.18.0) - language_server-protocol (3.17.0.5) - lint_roller (1.1.0) - logger (1.7.0) - loofah (2.25.0) - crass (~> 1.0.2) - nokogiri (>= 1.12.0) - mail (2.9.0) - logger - mini_mime (>= 0.1.1) - net-imap - net-pop - net-smtp - marcel (1.1.0) - mini_mime (1.1.5) - minitest (6.0.0) - prism (~> 1.5) - net-imap (0.6.2) - date - net-protocol - net-pop (0.1.2) - net-protocol - net-protocol (0.2.2) - timeout - net-smtp (0.5.1) - net-protocol - nio4r (2.7.5) - nokogiri (1.18.10-arm64-darwin) - racc (~> 1.4) - parallel (1.27.0) - parser (3.3.10.0) - ast (~> 2.4.1) - racc - pp (0.6.3) - prettyprint - prettyprint (0.2.0) - prism (1.7.0) - psych (5.3.1) - date - stringio - puma (7.1.0) - nio4r (~> 2.0) - racc (1.8.1) - rack (3.2.4) - rack-session (2.1.1) - base64 (>= 0.1.0) - rack (>= 3.0.0) - rack-test (2.2.0) - rack (>= 1.3) - rackup (2.3.1) - rack (>= 3) - rails (7.2.3) - actioncable (= 7.2.3) - actionmailbox (= 7.2.3) - actionmailer (= 7.2.3) - actionpack (= 7.2.3) - actiontext (= 7.2.3) - actionview (= 7.2.3) - activejob (= 7.2.3) - activemodel (= 7.2.3) - activerecord (= 7.2.3) - activestorage (= 7.2.3) - activesupport (= 7.2.3) - bundler (>= 1.15.0) - railties (= 7.2.3) - rails-dom-testing (2.3.0) - activesupport (>= 5.0.0) - minitest - nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) - nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (7.2.3) - actionpack (= 7.2.3) - activesupport (= 7.2.3) - cgi - irb (~> 1.13) - rackup (>= 1.0.0) - rake (>= 12.2) - thor (~> 1.0, >= 1.2.2) - tsort (>= 0.2) - zeitwerk (~> 2.6) - rainbow (3.1.1) - rake (13.3.1) - rdoc (7.0.1) - erb - psych (>= 4.0.0) - tsort - regexp_parser (2.11.3) - reline (0.6.3) - io-console (~> 0.5) - request_store (1.7.0) - rack (>= 1.4) - rspec (3.13.2) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) - rspec-core (3.13.6) - rspec-support (~> 3.13.0) - rspec-expectations (3.13.5) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-mocks (3.13.7) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-support (3.13.6) - rubocop (1.82.1) - json (~> 2.3) - language_server-protocol (~> 3.17.0.2) - lint_roller (~> 1.1.0) - parallel (~> 1.10) - parser (>= 3.3.0.2) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.48.0, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.48.0) - parser (>= 3.3.7.2) - prism (~> 1.4) - rubocop-factory_bot (2.28.0) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-performance (1.26.1) - lint_roller (~> 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rails (2.34.2) - activesupport (>= 4.2.0) - lint_roller (~> 1.1) - rack (>= 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rspec (3.8.0) - lint_roller (~> 1.1) - rubocop (~> 1.81) - rubocop-rspec_rails (2.32.0) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-rspec (~> 3.5) - ruby-progressbar (1.13.0) - securerandom (0.4.1) - sqlite3 (2.8.1-arm64-darwin) - stringio (3.2.0) - thor (1.4.0) - timeout (0.6.0) - tsort (0.2.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (3.2.0) - unicode-emoji (~> 4.1) - unicode-emoji (4.2.0) - useragent (0.16.11) - websocket-driver (0.8.0) - base64 - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - zeitwerk (2.7.4) - -PLATFORMS - arm64-darwin-24 - -DEPENDENCIES - appraisal - factory_bot - puma - rails (~> 7.1) - rake - rspec - rubocop - rubocop-factory_bot - rubocop-performance - rubocop-rails - rubocop-rspec - rubocop-rspec_rails - simple_master! - sqlite3 (~> 2.1) - -BUNDLED WITH - 2.7.1 diff --git a/gemfiles/rails_7_2.gemfile.lock b/gemfiles/rails_7_2.gemfile.lock deleted file mode 100644 index c298d3d..0000000 --- a/gemfiles/rails_7_2.gemfile.lock +++ /dev/null @@ -1,293 +0,0 @@ -PATH - remote: .. - specs: - simple_master (0.1.0) - activerecord (>= 7.0) - activesupport (>= 7.0) - request_store (>= 1.0) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (7.2.3) - actionpack (= 7.2.3) - activesupport (= 7.2.3) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - zeitwerk (~> 2.6) - actionmailbox (7.2.3) - actionpack (= 7.2.3) - activejob (= 7.2.3) - activerecord (= 7.2.3) - activestorage (= 7.2.3) - activesupport (= 7.2.3) - mail (>= 2.8.0) - actionmailer (7.2.3) - actionpack (= 7.2.3) - actionview (= 7.2.3) - activejob (= 7.2.3) - activesupport (= 7.2.3) - mail (>= 2.8.0) - rails-dom-testing (~> 2.2) - actionpack (7.2.3) - actionview (= 7.2.3) - activesupport (= 7.2.3) - cgi - nokogiri (>= 1.8.5) - racc - rack (>= 2.2.4, < 3.3) - rack-session (>= 1.0.1) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - useragent (~> 0.16) - actiontext (7.2.3) - actionpack (= 7.2.3) - activerecord (= 7.2.3) - activestorage (= 7.2.3) - activesupport (= 7.2.3) - globalid (>= 0.6.0) - nokogiri (>= 1.8.5) - actionview (7.2.3) - activesupport (= 7.2.3) - builder (~> 3.1) - cgi - erubi (~> 1.11) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - activejob (7.2.3) - activesupport (= 7.2.3) - globalid (>= 0.3.6) - activemodel (7.2.3) - activesupport (= 7.2.3) - activerecord (7.2.3) - activemodel (= 7.2.3) - activesupport (= 7.2.3) - timeout (>= 0.4.0) - activestorage (7.2.3) - actionpack (= 7.2.3) - activejob (= 7.2.3) - activerecord (= 7.2.3) - activesupport (= 7.2.3) - marcel (~> 1.0) - activesupport (7.2.3) - base64 - benchmark (>= 0.3) - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - appraisal (2.5.0) - bundler - rake - thor (>= 0.14.0) - ast (2.4.3) - base64 (0.3.0) - benchmark (0.5.0) - bigdecimal (4.0.1) - builder (3.3.0) - cgi (0.5.1) - concurrent-ruby (1.3.6) - connection_pool (3.0.2) - crass (1.0.6) - date (3.5.1) - diff-lcs (1.6.2) - drb (2.2.3) - erb (6.0.1) - erubi (1.13.1) - factory_bot (6.5.6) - activesupport (>= 6.1.0) - globalid (1.3.0) - activesupport (>= 6.1) - i18n (1.14.7) - concurrent-ruby (~> 1.0) - io-console (0.8.2) - irb (1.16.0) - pp (>= 0.6.0) - rdoc (>= 4.0.0) - reline (>= 0.4.2) - json (2.18.0) - language_server-protocol (3.17.0.5) - lint_roller (1.1.0) - logger (1.7.0) - loofah (2.25.0) - crass (~> 1.0.2) - nokogiri (>= 1.12.0) - mail (2.9.0) - logger - mini_mime (>= 0.1.1) - net-imap - net-pop - net-smtp - marcel (1.1.0) - mini_mime (1.1.5) - minitest (6.0.0) - prism (~> 1.5) - net-imap (0.6.2) - date - net-protocol - net-pop (0.1.2) - net-protocol - net-protocol (0.2.2) - timeout - net-smtp (0.5.1) - net-protocol - nio4r (2.7.5) - nokogiri (1.18.10-arm64-darwin) - racc (~> 1.4) - parallel (1.27.0) - parser (3.3.10.0) - ast (~> 2.4.1) - racc - pp (0.6.3) - prettyprint - prettyprint (0.2.0) - prism (1.7.0) - psych (5.3.1) - date - stringio - puma (7.1.0) - nio4r (~> 2.0) - racc (1.8.1) - rack (3.2.4) - rack-session (2.1.1) - base64 (>= 0.1.0) - rack (>= 3.0.0) - rack-test (2.2.0) - rack (>= 1.3) - rackup (2.3.1) - rack (>= 3) - rails (7.2.3) - actioncable (= 7.2.3) - actionmailbox (= 7.2.3) - actionmailer (= 7.2.3) - actionpack (= 7.2.3) - actiontext (= 7.2.3) - actionview (= 7.2.3) - activejob (= 7.2.3) - activemodel (= 7.2.3) - activerecord (= 7.2.3) - activestorage (= 7.2.3) - activesupport (= 7.2.3) - bundler (>= 1.15.0) - railties (= 7.2.3) - rails-dom-testing (2.3.0) - activesupport (>= 5.0.0) - minitest - nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) - nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (7.2.3) - actionpack (= 7.2.3) - activesupport (= 7.2.3) - cgi - irb (~> 1.13) - rackup (>= 1.0.0) - rake (>= 12.2) - thor (~> 1.0, >= 1.2.2) - tsort (>= 0.2) - zeitwerk (~> 2.6) - rainbow (3.1.1) - rake (13.3.1) - rdoc (7.0.1) - erb - psych (>= 4.0.0) - tsort - regexp_parser (2.11.3) - reline (0.6.3) - io-console (~> 0.5) - request_store (1.7.0) - rack (>= 1.4) - rspec (3.13.2) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) - rspec-core (3.13.6) - rspec-support (~> 3.13.0) - rspec-expectations (3.13.5) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-mocks (3.13.7) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-support (3.13.6) - rubocop (1.82.1) - json (~> 2.3) - language_server-protocol (~> 3.17.0.2) - lint_roller (~> 1.1.0) - parallel (~> 1.10) - parser (>= 3.3.0.2) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.48.0, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.48.0) - parser (>= 3.3.7.2) - prism (~> 1.4) - rubocop-factory_bot (2.28.0) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-performance (1.26.1) - lint_roller (~> 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rails (2.34.2) - activesupport (>= 4.2.0) - lint_roller (~> 1.1) - rack (>= 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rspec (3.8.0) - lint_roller (~> 1.1) - rubocop (~> 1.81) - rubocop-rspec_rails (2.32.0) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-rspec (~> 3.5) - ruby-progressbar (1.13.0) - securerandom (0.4.1) - sqlite3 (2.8.1-arm64-darwin) - stringio (3.2.0) - thor (1.4.0) - timeout (0.6.0) - tsort (0.2.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (3.2.0) - unicode-emoji (~> 4.1) - unicode-emoji (4.2.0) - useragent (0.16.11) - websocket-driver (0.8.0) - base64 - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - zeitwerk (2.7.4) - -PLATFORMS - arm64-darwin-24 - -DEPENDENCIES - appraisal - factory_bot - puma - rails (~> 7.2) - rake - rspec - rubocop - rubocop-factory_bot - rubocop-performance - rubocop-rails - rubocop-rspec - rubocop-rspec_rails - simple_master! - sqlite3 (~> 2.1) - -BUNDLED WITH - 2.7.1 diff --git a/gemfiles/rails_8_0.gemfile.lock b/gemfiles/rails_8_0.gemfile.lock deleted file mode 100644 index 5a25c24..0000000 --- a/gemfiles/rails_8_0.gemfile.lock +++ /dev/null @@ -1,292 +0,0 @@ -PATH - remote: .. - specs: - simple_master (0.1.0) - activerecord (>= 7.0) - activesupport (>= 7.0) - request_store (>= 1.0) - -GEM - remote: https://rubygems.org/ - specs: - action_text-trix (2.1.15) - railties - actioncable (8.1.1) - actionpack (= 8.1.1) - activesupport (= 8.1.1) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - zeitwerk (~> 2.6) - actionmailbox (8.1.1) - actionpack (= 8.1.1) - activejob (= 8.1.1) - activerecord (= 8.1.1) - activestorage (= 8.1.1) - activesupport (= 8.1.1) - mail (>= 2.8.0) - actionmailer (8.1.1) - actionpack (= 8.1.1) - actionview (= 8.1.1) - activejob (= 8.1.1) - activesupport (= 8.1.1) - mail (>= 2.8.0) - rails-dom-testing (~> 2.2) - actionpack (8.1.1) - actionview (= 8.1.1) - activesupport (= 8.1.1) - nokogiri (>= 1.8.5) - rack (>= 2.2.4) - rack-session (>= 1.0.1) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - useragent (~> 0.16) - actiontext (8.1.1) - action_text-trix (~> 2.1.15) - actionpack (= 8.1.1) - activerecord (= 8.1.1) - activestorage (= 8.1.1) - activesupport (= 8.1.1) - globalid (>= 0.6.0) - nokogiri (>= 1.8.5) - actionview (8.1.1) - activesupport (= 8.1.1) - builder (~> 3.1) - erubi (~> 1.11) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - activejob (8.1.1) - activesupport (= 8.1.1) - globalid (>= 0.3.6) - activemodel (8.1.1) - activesupport (= 8.1.1) - activerecord (8.1.1) - activemodel (= 8.1.1) - activesupport (= 8.1.1) - timeout (>= 0.4.0) - activestorage (8.1.1) - actionpack (= 8.1.1) - activejob (= 8.1.1) - activerecord (= 8.1.1) - activesupport (= 8.1.1) - marcel (~> 1.0) - activesupport (8.1.1) - base64 - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - json - logger (>= 1.4.2) - minitest (>= 5.1) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - uri (>= 0.13.1) - appraisal (2.5.0) - bundler - rake - thor (>= 0.14.0) - ast (2.4.3) - base64 (0.3.0) - bigdecimal (4.0.1) - builder (3.3.0) - concurrent-ruby (1.3.6) - connection_pool (3.0.2) - crass (1.0.6) - date (3.5.1) - diff-lcs (1.6.2) - drb (2.2.3) - erb (6.0.1) - erubi (1.13.1) - factory_bot (6.5.6) - activesupport (>= 6.1.0) - globalid (1.3.0) - activesupport (>= 6.1) - i18n (1.14.7) - concurrent-ruby (~> 1.0) - io-console (0.8.2) - irb (1.16.0) - pp (>= 0.6.0) - rdoc (>= 4.0.0) - reline (>= 0.4.2) - json (2.18.0) - language_server-protocol (3.17.0.5) - lint_roller (1.1.0) - logger (1.7.0) - loofah (2.25.0) - crass (~> 1.0.2) - nokogiri (>= 1.12.0) - mail (2.9.0) - logger - mini_mime (>= 0.1.1) - net-imap - net-pop - net-smtp - marcel (1.1.0) - mini_mime (1.1.5) - minitest (6.0.0) - prism (~> 1.5) - net-imap (0.6.2) - date - net-protocol - net-pop (0.1.2) - net-protocol - net-protocol (0.2.2) - timeout - net-smtp (0.5.1) - net-protocol - nio4r (2.7.5) - nokogiri (1.18.10-arm64-darwin) - racc (~> 1.4) - parallel (1.27.0) - parser (3.3.10.0) - ast (~> 2.4.1) - racc - pp (0.6.3) - prettyprint - prettyprint (0.2.0) - prism (1.7.0) - psych (5.3.1) - date - stringio - puma (7.1.0) - nio4r (~> 2.0) - racc (1.8.1) - rack (3.2.4) - rack-session (2.1.1) - base64 (>= 0.1.0) - rack (>= 3.0.0) - rack-test (2.2.0) - rack (>= 1.3) - rackup (2.3.1) - rack (>= 3) - rails (8.1.1) - actioncable (= 8.1.1) - actionmailbox (= 8.1.1) - actionmailer (= 8.1.1) - actionpack (= 8.1.1) - actiontext (= 8.1.1) - actionview (= 8.1.1) - activejob (= 8.1.1) - activemodel (= 8.1.1) - activerecord (= 8.1.1) - activestorage (= 8.1.1) - activesupport (= 8.1.1) - bundler (>= 1.15.0) - railties (= 8.1.1) - rails-dom-testing (2.3.0) - activesupport (>= 5.0.0) - minitest - nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) - nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (8.1.1) - actionpack (= 8.1.1) - activesupport (= 8.1.1) - irb (~> 1.13) - rackup (>= 1.0.0) - rake (>= 12.2) - thor (~> 1.0, >= 1.2.2) - tsort (>= 0.2) - zeitwerk (~> 2.6) - rainbow (3.1.1) - rake (13.3.1) - rdoc (7.0.1) - erb - psych (>= 4.0.0) - tsort - regexp_parser (2.11.3) - reline (0.6.3) - io-console (~> 0.5) - request_store (1.7.0) - rack (>= 1.4) - rspec (3.13.2) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) - rspec-core (3.13.6) - rspec-support (~> 3.13.0) - rspec-expectations (3.13.5) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-mocks (3.13.7) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-support (3.13.6) - rubocop (1.82.1) - json (~> 2.3) - language_server-protocol (~> 3.17.0.2) - lint_roller (~> 1.1.0) - parallel (~> 1.10) - parser (>= 3.3.0.2) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.48.0, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.48.0) - parser (>= 3.3.7.2) - prism (~> 1.4) - rubocop-factory_bot (2.28.0) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-performance (1.26.1) - lint_roller (~> 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rails (2.34.2) - activesupport (>= 4.2.0) - lint_roller (~> 1.1) - rack (>= 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rspec (3.8.0) - lint_roller (~> 1.1) - rubocop (~> 1.81) - rubocop-rspec_rails (2.32.0) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-rspec (~> 3.5) - ruby-progressbar (1.13.0) - securerandom (0.4.1) - sqlite3 (2.8.1-arm64-darwin) - stringio (3.2.0) - thor (1.4.0) - timeout (0.6.0) - tsort (0.2.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (3.2.0) - unicode-emoji (~> 4.1) - unicode-emoji (4.2.0) - uri (1.1.1) - useragent (0.16.11) - websocket-driver (0.8.0) - base64 - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - zeitwerk (2.7.4) - -PLATFORMS - arm64-darwin-24 - -DEPENDENCIES - appraisal - factory_bot - puma - rails (~> 8.0) - rake - rspec - rubocop - rubocop-factory_bot - rubocop-performance - rubocop-rails - rubocop-rspec - rubocop-rspec_rails - simple_master! - sqlite3 (~> 2.1) - -BUNDLED WITH - 2.7.1 diff --git a/gemfiles/rails_8_1.gemfile.lock b/gemfiles/rails_8_1.gemfile.lock deleted file mode 100644 index 0a08924..0000000 --- a/gemfiles/rails_8_1.gemfile.lock +++ /dev/null @@ -1,292 +0,0 @@ -PATH - remote: .. - specs: - simple_master (0.1.0) - activerecord (>= 7.0) - activesupport (>= 7.0) - request_store (>= 1.0) - -GEM - remote: https://rubygems.org/ - specs: - action_text-trix (2.1.15) - railties - actioncable (8.1.1) - actionpack (= 8.1.1) - activesupport (= 8.1.1) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - zeitwerk (~> 2.6) - actionmailbox (8.1.1) - actionpack (= 8.1.1) - activejob (= 8.1.1) - activerecord (= 8.1.1) - activestorage (= 8.1.1) - activesupport (= 8.1.1) - mail (>= 2.8.0) - actionmailer (8.1.1) - actionpack (= 8.1.1) - actionview (= 8.1.1) - activejob (= 8.1.1) - activesupport (= 8.1.1) - mail (>= 2.8.0) - rails-dom-testing (~> 2.2) - actionpack (8.1.1) - actionview (= 8.1.1) - activesupport (= 8.1.1) - nokogiri (>= 1.8.5) - rack (>= 2.2.4) - rack-session (>= 1.0.1) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - useragent (~> 0.16) - actiontext (8.1.1) - action_text-trix (~> 2.1.15) - actionpack (= 8.1.1) - activerecord (= 8.1.1) - activestorage (= 8.1.1) - activesupport (= 8.1.1) - globalid (>= 0.6.0) - nokogiri (>= 1.8.5) - actionview (8.1.1) - activesupport (= 8.1.1) - builder (~> 3.1) - erubi (~> 1.11) - rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - activejob (8.1.1) - activesupport (= 8.1.1) - globalid (>= 0.3.6) - activemodel (8.1.1) - activesupport (= 8.1.1) - activerecord (8.1.1) - activemodel (= 8.1.1) - activesupport (= 8.1.1) - timeout (>= 0.4.0) - activestorage (8.1.1) - actionpack (= 8.1.1) - activejob (= 8.1.1) - activerecord (= 8.1.1) - activesupport (= 8.1.1) - marcel (~> 1.0) - activesupport (8.1.1) - base64 - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - json - logger (>= 1.4.2) - minitest (>= 5.1) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - uri (>= 0.13.1) - appraisal (2.5.0) - bundler - rake - thor (>= 0.14.0) - ast (2.4.3) - base64 (0.3.0) - bigdecimal (4.0.1) - builder (3.3.0) - concurrent-ruby (1.3.6) - connection_pool (3.0.2) - crass (1.0.6) - date (3.5.1) - diff-lcs (1.6.2) - drb (2.2.3) - erb (6.0.1) - erubi (1.13.1) - factory_bot (6.5.6) - activesupport (>= 6.1.0) - globalid (1.3.0) - activesupport (>= 6.1) - i18n (1.14.7) - concurrent-ruby (~> 1.0) - io-console (0.8.2) - irb (1.16.0) - pp (>= 0.6.0) - rdoc (>= 4.0.0) - reline (>= 0.4.2) - json (2.18.0) - language_server-protocol (3.17.0.5) - lint_roller (1.1.0) - logger (1.7.0) - loofah (2.25.0) - crass (~> 1.0.2) - nokogiri (>= 1.12.0) - mail (2.9.0) - logger - mini_mime (>= 0.1.1) - net-imap - net-pop - net-smtp - marcel (1.1.0) - mini_mime (1.1.5) - minitest (6.0.0) - prism (~> 1.5) - net-imap (0.6.2) - date - net-protocol - net-pop (0.1.2) - net-protocol - net-protocol (0.2.2) - timeout - net-smtp (0.5.1) - net-protocol - nio4r (2.7.5) - nokogiri (1.18.10-arm64-darwin) - racc (~> 1.4) - parallel (1.27.0) - parser (3.3.10.0) - ast (~> 2.4.1) - racc - pp (0.6.3) - prettyprint - prettyprint (0.2.0) - prism (1.7.0) - psych (5.3.1) - date - stringio - puma (7.1.0) - nio4r (~> 2.0) - racc (1.8.1) - rack (3.2.4) - rack-session (2.1.1) - base64 (>= 0.1.0) - rack (>= 3.0.0) - rack-test (2.2.0) - rack (>= 1.3) - rackup (2.3.1) - rack (>= 3) - rails (8.1.1) - actioncable (= 8.1.1) - actionmailbox (= 8.1.1) - actionmailer (= 8.1.1) - actionpack (= 8.1.1) - actiontext (= 8.1.1) - actionview (= 8.1.1) - activejob (= 8.1.1) - activemodel (= 8.1.1) - activerecord (= 8.1.1) - activestorage (= 8.1.1) - activesupport (= 8.1.1) - bundler (>= 1.15.0) - railties (= 8.1.1) - rails-dom-testing (2.3.0) - activesupport (>= 5.0.0) - minitest - nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) - nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (8.1.1) - actionpack (= 8.1.1) - activesupport (= 8.1.1) - irb (~> 1.13) - rackup (>= 1.0.0) - rake (>= 12.2) - thor (~> 1.0, >= 1.2.2) - tsort (>= 0.2) - zeitwerk (~> 2.6) - rainbow (3.1.1) - rake (13.3.1) - rdoc (7.0.1) - erb - psych (>= 4.0.0) - tsort - regexp_parser (2.11.3) - reline (0.6.3) - io-console (~> 0.5) - request_store (1.7.0) - rack (>= 1.4) - rspec (3.13.2) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) - rspec-core (3.13.6) - rspec-support (~> 3.13.0) - rspec-expectations (3.13.5) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-mocks (3.13.7) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-support (3.13.6) - rubocop (1.82.1) - json (~> 2.3) - language_server-protocol (~> 3.17.0.2) - lint_roller (~> 1.1.0) - parallel (~> 1.10) - parser (>= 3.3.0.2) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.48.0, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.48.0) - parser (>= 3.3.7.2) - prism (~> 1.4) - rubocop-factory_bot (2.28.0) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-performance (1.26.1) - lint_roller (~> 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rails (2.34.2) - activesupport (>= 4.2.0) - lint_roller (~> 1.1) - rack (>= 1.1) - rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rspec (3.8.0) - lint_roller (~> 1.1) - rubocop (~> 1.81) - rubocop-rspec_rails (2.32.0) - lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-rspec (~> 3.5) - ruby-progressbar (1.13.0) - securerandom (0.4.1) - sqlite3 (2.8.1-arm64-darwin) - stringio (3.2.0) - thor (1.4.0) - timeout (0.6.0) - tsort (0.2.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (3.2.0) - unicode-emoji (~> 4.1) - unicode-emoji (4.2.0) - uri (1.1.1) - useragent (0.16.11) - websocket-driver (0.8.0) - base64 - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - zeitwerk (2.7.4) - -PLATFORMS - arm64-darwin-24 - -DEPENDENCIES - appraisal - factory_bot - puma - rails (~> 8.1) - rake - rspec - rubocop - rubocop-factory_bot - rubocop-performance - rubocop-rails - rubocop-rspec - rubocop-rspec_rails - simple_master! - sqlite3 (~> 2.1) - -BUNDLED WITH - 2.7.1 diff --git a/lib/simple_master/master/queryable.rb b/lib/simple_master/master/queryable.rb index 0401247..18bd189 100644 --- a/lib/simple_master/master/queryable.rb +++ b/lib/simple_master/master/queryable.rb @@ -37,11 +37,11 @@ def insert_queries(records, on_duplicate_key_update = false, batch_size: 10000) sql_column_methods .zip(column_names) .map { |method_name, column_name| - if [:updated_at, :created_at].include?(column_name) - current_time - else - record.send(method_name) - end + if [:updated_at, :created_at].include?(column_name) + current_time + else + record.send(method_name) + end }.join(", ").then { "(#{_1})" } }.join(", \n")