diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index cba1105238..7cb8012e40 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -12,7 +12,8 @@ jobs:
- "3.0"
- "3.1"
- "3.2"
- gemfile: [gemfiles/rails_7.0.gemfile]
+ - "3.3"
+ gemfile: [gemfiles/rails_7.1.gemfile]
orm: [active_record]
adapter: [sqlite3]
asset: [webpack]
@@ -32,17 +33,37 @@ jobs:
orm: active_record
adapter: sqlite3
asset: webpacker
- - ruby: "3.0"
+ - ruby: "3.2"
gemfile: gemfiles/rails_7.0.gemfile
orm: active_record
+ adapter: sqlite3
+ asset: sprockets
+ - ruby: "3.2"
+ gemfile: gemfiles/rails_7.1.gemfile
+ orm: active_record
adapter: mysql2
asset: importmap
- - ruby: "3.0"
- gemfile: gemfiles/rails_7.0.gemfile
+ - ruby: "3.2"
+ gemfile: gemfiles/rails_7.1.gemfile
orm: active_record
adapter: postgresql
asset: sprockets
- - ruby: "3.0"
+ - ruby: "3.2"
+ gemfile: gemfiles/rails_7.1.gemfile
+ orm: active_record
+ adapter: sqlite3
+ asset: vite
+ - ruby: "3.3"
+ gemfile: gemfiles/rails_7.2.gemfile
+ orm: active_record
+ adapter: sqlite3
+ asset: sprockets
+ - ruby: "3.3"
+ gemfile: gemfiles/rails_8.0.gemfile
+ orm: active_record
+ adapter: sqlite3
+ asset: sprockets
+ - ruby: "3.2"
gemfile: gemfiles/composite_primary_keys.gemfile
orm: active_record
adapter: sqlite3
@@ -57,13 +78,23 @@ jobs:
orm: mongoid
adapter: sqlite3
asset: sprockets
- - ruby: jruby-9.3
- gemfile: gemfiles/rails_6.1.gemfile
+ - ruby: "3.1"
+ gemfile: gemfiles/rails_7.0.gemfile
+ orm: mongoid
+ adapter: sqlite3
+ asset: sprockets
+ - ruby: "3.2"
+ gemfile: gemfiles/rails_7.1.gemfile
+ orm: mongoid
+ adapter: sqlite3
+ asset: sprockets
+ - ruby: jruby-9.4
+ gemfile: gemfiles/rails_7.0.gemfile
orm: active_record
adapter: mysql2
asset: sprockets
- - ruby: jruby-9.3
- gemfile: gemfiles/rails_6.1.gemfile
+ - ruby: jruby-9.4
+ gemfile: gemfiles/rails_7.0.gemfile
orm: mongoid
adapter: sqlite3
asset: sprockets
@@ -93,7 +124,7 @@ jobs:
CI_ASSET: ${{ matrix.asset }}
JRUBY_OPTS: --debug
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
@@ -104,15 +135,18 @@ jobs:
MAKEFLAGS: make --jobs 4
BUNDLE_WITHOUT: development
- name: Set up Node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
- node-version: "14"
+ node-version: "18"
+ - name: Install ImageMagick
+ run: sudo apt-get install imagemagick
- name: Setup application
env:
BUNDLE_GEMFILE: ../../${{ matrix.gemfile }}
CI_ASSET: ${{ matrix.asset }}
CI_DB_ADAPTER: ${{ matrix.adapter }}
RAILS_ENV: test
+ NODE_OPTIONS: --openssl-legacy-provider
run: |
yarn install
cd spec/dummy_app
@@ -127,7 +161,7 @@ jobs:
run: bundle exec rspec
- name: Coveralls Parallel
if: ${{ github.repository_owner == 'railsadminteam' }}
- uses: coverallsapp/github-action@master
+ uses: coverallsapp/github-action@v2
continue-on-error: true
with:
github-token: ${{ secrets.github_token }}
@@ -141,7 +175,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
- uses: coverallsapp/github-action@master
+ uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.github_token }}
parallel-finished: true
@@ -150,9 +184,9 @@ jobs:
name: Prettier
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
- name: Install dependencies
run: yarn install
- name: Run check
@@ -162,18 +196,13 @@ jobs:
name: RuboCop
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
- ruby-version: "3.0"
- - name: Cache gems
- uses: actions/cache@v3
- with:
- path: vendor/bundle
- key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ hashFiles('Gemfile') }}
- restore-keys: |
- ${{ runner.os }}-gems-${{ matrix.ruby }}-
+ ruby-version: "3.2"
+ bundler-cache: true
+ cache-version: gems-${{ hashFiles('Gemfile') }}
- name: Install dependencies
run: bundle install --without development --jobs=3 --retry=3 --path=vendor/bundle
- name: Run check
diff --git a/.rubocop.yml b/.rubocop.yml
index 28aa300c55..5c76364890 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -9,6 +9,7 @@ AllCops:
- "node_modules/**/*"
- "spec/dummy_app/bin/**/*"
- "spec/dummy_app/db/schema.rb"
+ - "spec/dummy_app/node_modules/**/*"
- "spec/dummy_app/tmp/**/*"
- "vendor/bundle/**/*"
NewCops: disable
@@ -108,7 +109,7 @@ Metrics/BlockNesting:
Metrics/ClassLength:
CountComments: false
- Max: 132 # TODO: Lower to 100
+ Max: 201 # TODO: Lower to 100
Metrics/CyclomaticComplexity:
Max: 15 # TODO: Lower to 6
@@ -201,6 +202,9 @@ Style/RaiseArgs:
Style/RedundantArgument:
Enabled: true
+Style/RedundantParentheses:
+ Enabled: false
+
Style/RedundantSelfAssignmentBranch:
Enabled: true
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index f701fb220d..447ccf2ad2 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -21,7 +21,7 @@ Lint/ReturnInVoidContext:
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
# IgnoredMethods: refine
Metrics/BlockLength:
- Max: 1097
+ Max: 1135
# Offense count: 1
# Configuration parameters: Max, CountKeywordArgs.
diff --git a/Appraisals b/Appraisals
index 08b6a0a8a3..112766ead3 100644
--- a/Appraisals
+++ b/Appraisals
@@ -2,21 +2,15 @@
appraise 'rails-6.0' do
gem 'rails', '~> 6.0.0'
- gem 'sassc-rails', '~> 2.1'
- gem 'devise', '~> 4.7'
+ gem 'psych', '~> 3.3'
+ gem 'turbo-rails', '< 2.0.8'
group :test do
- gem 'cancancan', '~> 3.0'
- gem 'kt-paperclip'
+ gem 'cancancan', ['~> 3.0', '< 3.6']
gem 'pundit', '~> 2.1.0'
- gem 'rspec-rails', '>= 4.0.0.beta2'
- gem 'shrine', '~> 3.0'
end
group :active_record do
- gem 'pg', '>= 1.0.0', platforms: :ruby
- gem 'paper_trail', '>= 12.0'
-
platforms :jruby do
gem 'activerecord-jdbcmysql-adapter', '~> 60.0'
gem 'activerecord-jdbcpostgresql-adapter', '~> 60.0'
@@ -25,32 +19,20 @@ appraise 'rails-6.0' do
end
group :mongoid do
- gem 'mongoid', '~> 7.0'
+ gem 'cancancan-mongoid'
+ gem 'carrierwave-mongoid', '>= 0.6.3', require: 'carrierwave/mongoid'
+ gem 'database_cleaner-mongoid', '>= 2.0', require: false
gem 'kaminari-mongoid'
+ gem 'mongoid', '~> 7.0'
gem 'mongoid-paperclip', '>= 0.0.8', require: 'mongoid_paperclip'
- gem 'carrierwave-mongoid', '>= 0.6.3', require: 'carrierwave/mongoid'
- gem 'cancancan-mongoid'
gem 'shrine-mongoid', '~> 1.0'
end
end
appraise 'rails-6.1' do
gem 'rails', '~> 6.1.0'
- gem 'sassc-rails', '~> 2.1'
- gem 'devise', '~> 4.7'
- gem 'turbo-rails', platform: :jruby, github: 'hotwired/turbo-rails'
-
- group :test do
- gem 'cancancan', '~> 3.2'
- gem 'kt-paperclip'
- gem 'rspec-rails', '>= 4.0.0.beta2'
- gem 'shrine', '~> 3.0'
- end
group :active_record do
- gem 'pg', '>= 1.0.0', platforms: :ruby
- gem 'paper_trail', '>= 12.0'
-
platforms :jruby do
gem 'activerecord-jdbcmysql-adapter', '~> 61.0'
gem 'activerecord-jdbcpostgresql-adapter', '~> 61.0'
@@ -59,11 +41,12 @@ appraise 'rails-6.1' do
end
group :mongoid do
- gem 'mongoid', '~> 7.0'
+ gem 'cancancan-mongoid'
+ gem 'carrierwave-mongoid', '>= 0.6.3', require: 'carrierwave/mongoid'
+ gem 'database_cleaner-mongoid', '>= 2.0', require: false
gem 'kaminari-mongoid'
+ gem 'mongoid', '~> 7.0'
gem 'mongoid-paperclip', '>= 0.0.8', require: 'mongoid_paperclip'
- gem 'carrierwave-mongoid', '>= 0.6.3', require: 'carrierwave/mongoid'
- gem 'cancancan-mongoid'
gem 'shrine-mongoid', '~> 1.0'
end
end
@@ -71,36 +54,69 @@ end
appraise 'rails-7.0' do
gem 'rails', '~> 7.0.0'
gem 'importmap-rails', require: false
- gem 'sassc-rails', '~> 2.1'
- gem 'devise', '~> 4.8'
- group :test do
- gem 'cancancan', '~> 3.2'
- gem 'kt-paperclip'
- gem 'rspec-rails', '>= 4.0.0.beta2'
- gem 'shrine', '~> 3.0'
+ group :active_record do
+ platforms :ruby, :mswin, :mingw, :x64_mingw do
+ gem 'sqlite3', '~> 1.3'
+ end
+
+ platforms :jruby do
+ gem 'activerecord-jdbcmysql-adapter', '~> 70.0'
+ gem 'activerecord-jdbcpostgresql-adapter', '~> 70.0'
+ gem 'activerecord-jdbcsqlite3-adapter', '~> 70.0'
+ end
+ end
+
+ group :mongoid do
+ gem 'cancancan-mongoid'
+ gem 'carrierwave-mongoid', '>= 0.6.3', require: 'carrierwave/mongoid'
+ gem 'database_cleaner-mongoid', '>= 2.0', require: false
+ gem 'kaminari-mongoid'
+ gem 'mongoid', '~> 8.0'
+ gem 'mongoid-paperclip', '>= 0.0.8', require: 'mongoid_paperclip'
+ gem 'shrine-mongoid', '~> 1.0'
end
+end
+
+appraise 'rails-7.1' do
+ gem 'rails', '~> 7.1.0'
+ gem 'importmap-rails', require: false
group :active_record do
- gem 'pg', '>= 1.0.0', platforms: :ruby
- gem 'paper_trail', '>= 12.0'
+ platforms :ruby, :mswin, :mingw, :x64_mingw do
+ gem 'sqlite3', '~> 1.3'
+ end
+ end
+
+ group :mongoid do
+ gem 'cancancan-mongoid'
+ gem 'carrierwave-mongoid', '>= 0.6.3', require: 'carrierwave/mongoid'
+ gem 'database_cleaner-mongoid', '>= 2.0', require: false
+ gem 'kaminari-mongoid'
+ gem 'mongoid', '~> 8.0'
+ gem 'mongoid-paperclip', '>= 0.0.8', require: 'mongoid_paperclip'
+ gem 'shrine-mongoid', '~> 1.0'
end
end
+appraise 'rails-7.2' do
+ gem 'rails', '~> 7.2.0'
+ gem 'importmap-rails', require: false
+end
+
+appraise 'rails-8.0' do
+ gem 'rails', '~> 8.0.0'
+ gem 'importmap-rails', require: false
+end
+
appraise 'composite_primary_keys' do
gem 'rails', '~> 7.0.0'
- gem 'sassc-rails', '~> 2.1'
- gem 'devise', '~> 4.8'
-
- group :test do
- gem 'cancancan', '~> 3.2'
- gem 'kt-paperclip'
- gem 'rspec-rails', '>= 4.0.0.beta2'
- gem 'shrine', '~> 3.0'
- end
group :active_record do
gem 'composite_primary_keys'
- gem 'paper_trail', '>= 12.0'
+
+ platforms :ruby, :mswin, :mingw, :x64_mingw do
+ gem 'sqlite3', '~> 1.3'
+ end
end
end
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d23eb4245b..73908df705 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,118 @@
## [Unreleased](https://github.com/railsadminteam/rails_admin/tree/HEAD)
-[Full Changelog](https://github.com/railsadminteam/rails_admin/compare/v3.1.1...HEAD)
+[Full Changelog](https://github.com/railsadminteam/rails_admin/compare/v3.3.0...HEAD)
+
+## [3.3.0](https://github.com/railsadminteam/rails_admin/tree/v3.3.0) - 2024-12-08
+
+[Full Changelog](https://github.com/railsadminteam/rails_admin/compare/v3.2.1...v3.3.0)
+
+### Added
+
+- Rails 8.0 support ([#3702](https://github.com/railsadminteam/rails_admin/pull/3702))
+
+## [3.2.1](https://github.com/railsadminteam/rails_admin/tree/v3.2.0) - 2024-10-10
+
+[Full Changelog](https://github.com/railsadminteam/rails_admin/compare/v3.2.0...v3.2.1)
+
+### Fixed
+
+- Disable Turbo's prefetch behavior globally, to prevent custom actions unintentionally triggered ([f54a102](https://github.com/railsadminteam/rails_admin/commit/f54a102c6b0a420244ef044503944574ef1dfbd2), [#3701](https://github.com/railsadminteam/rails_admin/issues/3701))
+
+## [3.2.0](https://github.com/railsadminteam/rails_admin/tree/v3.2.0) - 2024-09-08
+
+[Full Changelog](https://github.com/railsadminteam/rails_admin/compare/v3.2.0.rc...v3.2.0)
+
+### Fixed
+
+- Fix polymorphic id doesn't reset properly in an edge case ([7b2ffb1](https://github.com/railsadminteam/rails_admin/commit/7b2ffb12386e06a0e6e0bace6d331fc5af989e38), [#3630](https://github.com/railsadminteam/rails_admin/pull/3630))
+
+## [3.2.0.rc](https://github.com/railsadminteam/rails_admin/tree/v3.2.0.rc) - 2024-08-25
+
+[Full Changelog](https://github.com/railsadminteam/rails_admin/compare/v3.2.0.beta...v3.2.0.rc)
+
+### Added
+
+- ActiveRecord 7.1 composite primary keys support ([53b89c9](https://github.com/railsadminteam/rails_admin/commit/53b89c9161e48c0f9b4ecd5f544398c9360ea50f))
+
+### Changed
+
+- Introduce SingularAssociation and CollectionAssociation to tidy up the view files ([876be11](https://github.com/railsadminteam/rails_admin/commit/876be11ec01237596b2f27e15239e86418ce7610))
+
+### Fixed
+
+- Fix to show a thumbnail of all representable ActiveStorage attachments ([#3656](https://github.com/railsadminteam/rails_admin/pull/3656), [7754ac3](https://github.com/railsadminteam/rails_admin/commit/7754ac34eb8e0af7605b2e52ae0646b17e9bb0c6))
+- Fix to detect images properly in FileUpload and MultipleFileUpload field ([35c8702](https://github.com/railsadminteam/rails_admin/commit/35c8702351aa300bddcc950d36d68b80742f5011), [#3633](https://github.com/railsadminteam/rails_admin/pull/3633))
+- Fix to reset polymorphic id selection upon type change ([#3630](https://github.com/railsadminteam/rails_admin/pull/3630), [13114e5](https://github.com/railsadminteam/rails_admin/commit/13114e5629d49eab14d58df1319eb068dacedba7))
+- Lock jQuery UI version due to incompatibility with 1.14 ([5245d5b](https://github.com/railsadminteam/rails_admin/commit/5245d5bb91691d646219b5243f3f881a0144a3fd), [#3692](https://github.com/railsadminteam/rails_admin/issues/3692))
+
+## [3.2.0.beta](https://github.com/railsadminteam/rails_admin/tree/v3.2.0.beta) - 2024-07-13
+
+[Full Changelog](https://github.com/railsadminteam/rails_admin/compare/v3.1.4...v3.2.0.beta)
+
+### Added
+
+- Allow turbo-rails 2 in gemspec ([#3671](https://github.com/railsadminteam/rails_admin/pull/3671))
+- ViteRuby integration ([#3643](https://github.com/railsadminteam/rails_admin/pull/3643), [0e12e5b](https://github.com/railsadminteam/rails_admin/commit/0e12e5b465997c14d5b7a4d500a0d4cebed21aa9))
+- Support client-side dynamic scoping ([12715f2](https://github.com/railsadminteam/rails_admin/commit/12715f2dd12d97f0676e548e4271906df424b89d), [#2934](https://github.com/railsadminteam/rails_admin/issues/2934))
+- Add support for `%-l` option to Flatpickr ([#3616](https://github.com/railsadminteam/rails_admin/pull/3616))
+
+### Changed
+
+- Handle has_one assignment on the field level, making patched has_one getters/setters unnecessary ([91737ab](https://github.com/railsadminteam/rails_admin/commit/91737ab3c2fa22cbe08aedd28770a12704fde6c7))
+
+### Fixed
+
+- Use require_relative to avoid modifying $LOAD_PATH in gemspec ([#3690](https://github.com/railsadminteam/rails_admin/pull/3690))
+- Tidy up trailing whitespace in gem post_install_message ([#3689](https://github.com/railsadminteam/rails_admin/pull/3689))
+- Fix enum filter breaking when pre-populated ([d62f604](https://github.com/railsadminteam/rails_admin/commit/d62f604cc8d7d1434f7dfe0c5aca3aaf3dc2547b), [#3651](https://github.com/railsadminteam/rails_admin/issues/3651))
+- Fix error on searching or sorting by ActiveStorage field ([dba6c4b](https://github.com/railsadminteam/rails_admin/commit/dba6c4b815fbe4aa4f62a13b660e865a89151838), [#3678](https://github.com/railsadminteam/rails_admin/issues/3678))
+- Fix to remove trailing slash from meta tags ([#3672](https://github.com/railsadminteam/rails_admin/pull/3672))
+- Fix default config path for ImportmapFormatter being misspelled ([#3676](https://github.com/railsadminteam/rails_admin/pull/3676))
+- Fix table names not quoted properly on sorting ([#3652](https://github.com/railsadminteam/rails_admin/pull/3652), [#1631](https://github.com/railsadminteam/rails_admin/issues/1631))
+- Fix ActiveStorage/ActionText detection less likely to cause false positives ([073b809](https://github.com/railsadminteam/rails_admin/commit/073b809853b6bc231841e3f8dd9d35875220c616), [#3659](https://github.com/railsadminteam/rails_admin/issues/3659))
+- Fix boolean fields in a Mongoid embedded document fails to be updated ([#3555](https://github.com/railsadminteam/rails_admin/pull/3555), [#3554](https://github.com/railsadminteam/rails_admin/issues/3554))
+- Fix wrongly referring to `:update_only` in nested fields ([#3649](https://github.com/railsadminteam/rails_admin/pull/3649))
+- Fix to use HTML `q` element for better localization support ([#3636](https://github.com/railsadminteam/rails_admin/pull/3636))
+- Fix `is_blank` and `is_present` filters breaking for uuid columns ([#3629](https://github.com/railsadminteam/rails_admin/pull/3629), [#3669](https://github.com/railsadminteam/rails_admin/issues/3669))
+- Fix polymorphic association target classes not set correctly in subclasses ([2a89ebc](https://github.com/railsadminteam/rails_admin/commit/2a89ebcfa96243697988f6570b9c9be19a7a01b5), [#3631](https://github.com/railsadminteam/rails_admin/issues/3631))
+
+### Security
+
+- Validate `return_to` param using `request.base_url` to prevent arbitrary redirection ([#3627](https://github.com/railsadminteam/rails_admin/pull/3627))
+
+## [3.1.4](https://github.com/railsadminteam/rails_admin/tree/v3.1.4) - 2024-07-09
+
+[Full Changelog](https://github.com/railsadminteam/rails_admin/compare/v3.1.3...v3.1.4)
+
+### Fixed
+
+- Fix [32f91e4](https://github.com/railsadminteam/rails_admin/commit/32f91e4b49205e44d3931c2e36d9f7273384a250) broke fields with HTML tags ([758d249](https://github.com/railsadminteam/rails_admin/commit/758d249d950062be6840f9c96e2a286e02b92a1e), [#3686](https://github.com/railsadminteam/rails_admin/issues/3686#issuecomment-2215491140))
+
+## [3.1.3](https://github.com/railsadminteam/rails_admin/tree/v3.1.3) - 2024-07-06
+
+[Full Changelog](https://github.com/railsadminteam/rails_admin/compare/v3.1.2...v3.1.3)
+
+### Fixed
+
+- Fix bson 5.0 compatibility ([13da4f0](https://github.com/railsadminteam/rails_admin/commit/13da4f0191f5185dadecdfe9e6fc9ca808f7f73d))
+- Fix Importmap 2.0 compatibility ([bd0cf97](https://github.com/railsadminteam/rails_admin/commit/bd0cf97530d93dc66577e5d1ea0da2ebf3b57737))
+
+### Security
+
+- Fix XSS vulnerability in the list view ([b5a287d](https://github.com/railsadminteam/rails_admin/commit/b5a287d82e2cbd1737a1a01e11ede2911cce7fef), [GHSA-8qgm-g2vv-vwvc](https://github.com/railsadminteam/rails_admin/security/advisories/GHSA-8qgm-g2vv-vwvc))
+
+## [3.1.2](https://github.com/railsadminteam/rails_admin/tree/v3.1.2) - 2023-03-23
+
+[Full Changelog](https://github.com/railsadminteam/rails_admin/compare/v3.1.1...v3.1.2)
+
+### Fixed
+
+- Fix install failing with importmap setup ([aca22b6](https://github.com/railsadminteam/rails_admin/commit/aca22b6ba1eca1ac618525334cf14fc946e1c99e), [#3609](https://github.com/railsadminteam/rails_admin/issues/3609))
+- Fix to show non-eager-loaded models which are explicitly configured ([87c9d5b](https://github.com/railsadminteam/rails_admin/commit/87c9d5bc5b6ffb423e72054b3cfe8f949c12c178), [#3604](https://github.com/railsadminteam/rails_admin/issues/3604))
+- Fix `rails_admin.dom_ready` event not triggered with jQuery `on` ([2ee43de](https://github.com/railsadminteam/rails_admin/commit/2ee43deb1fa8d3a9e3ea0e589c1687d684e19ad6), [33773d7](https://github.com/railsadminteam/rails_admin/commit/33773d7f8dd43eeb0f6a7c125c4bee170132e5d2), [#3600](https://github.com/railsadminteam/rails_admin/discussions/3600))
+- Restore caching in RailsAdmin::Config::Model#excluded? ([#3587](https://github.com/railsadminteam/rails_admin/pull/3587))
+- Optimize/simplify viable_models file path to class name logic ([#3589](https://github.com/railsadminteam/rails_admin/pull/3589))
## [3.1.1](https://github.com/railsadminteam/rails_admin/tree/v3.1.1) - 2022-12-18
@@ -625,7 +736,7 @@
- Fix Syntax Error in removal of new nested entity([#2539](https://github.com/railsadminteam/rails_admin/pull/2539))
- Fix momentjs translations for '%-d' format day of the month([#2540](https://github.com/railsadminteam/rails_admin/pull/2540))
- Fix Mongoid BSON object field ([#2495](https://github.com/railsadminteam/rails_admin/issues/2495))
-- Make browser ignore validaitons of removed nested child models([#2443](https://github.com/railsadminteam/rails_admin/issues/2443), [#2490](https://github.com/railsadminteam/rails_admin/pull/2490))
+- Make browser ignore validations of removed nested child models([#2443](https://github.com/railsadminteam/rails_admin/issues/2443), [#2490](https://github.com/railsadminteam/rails_admin/pull/2490))
## [0.8.1](https://github.com/railsadminteam/rails_admin/tree/v0.8.1) - 2015-11-24
diff --git a/Gemfile b/Gemfile
index 2959f4f823..0965e94b45 100644
--- a/Gemfile
+++ b/Gemfile
@@ -3,20 +3,14 @@
source 'https://rubygems.org'
gem 'appraisal', '>= 2.0'
-gem 'devise'
+gem 'devise', '~> 4.7'
gem 'net-smtp', require: false
gem 'rails'
+gem 'sassc-rails', '~> 2.1'
+gem 'turbo-rails'
+gem 'vite_rails', require: false
gem 'webpacker', require: false
-gem 'webrick', '~> 1.7'
-
-group :active_record do
- gem 'paper_trail'
-
- platforms :ruby, :mswin, :mingw, :x64_mingw do
- gem 'mysql2', '>= 0.3.14'
- gem 'sqlite3', '>= 1.3'
- end
-end
+gem 'webrick'
group :development, :test do
gem 'pry', '>= 0.9'
@@ -25,21 +19,22 @@ end
group :test do
gem 'cancancan', '~> 3.0'
gem 'carrierwave', ['>= 2.0.0.rc', '< 3']
- gem 'cuprite'
+ gem 'cuprite', '!= 0.15.1'
gem 'database_cleaner-active_record', '>= 2.0', require: false
- gem 'database_cleaner-mongoid', '>= 2.0', require: false
gem 'dragonfly', '~> 1.0'
- gem 'factory_bot', '>= 4.2'
+ gem 'factory_bot', '>= 4.2', '!= 6.4.5'
gem 'generator_spec', '>= 0.8'
+ gem 'kt-paperclip'
gem 'launchy', '>= 2.2'
gem 'mini_magick', '>= 3.4'
gem 'pundit'
gem 'rack-cache', require: 'rack/cache'
gem 'rspec-expectations', '!= 3.8.3'
- gem 'rspec-rails', '>= 2.14'
+ gem 'rspec-rails', '>= 4.0.0.beta2'
gem 'rspec-retry'
gem 'rubocop', ['~> 1.20', '!= 1.22.2'], require: false
gem 'rubocop-performance', require: false
+ gem 'shrine', '~> 3.0'
gem 'simplecov', '>= 0.9', require: false
gem 'simplecov-lcov', require: false
gem 'timecop', '>= 0.5'
@@ -48,4 +43,14 @@ group :test do
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
end
+group :active_record do
+ gem 'paper_trail', '>= 12.0'
+
+ platforms :ruby, :mswin, :mingw, :x64_mingw do
+ gem 'mysql2', '>= 0.3.14'
+ gem 'pg', '>= 1.0.0'
+ gem 'sqlite3', '>= 1.3.0'
+ end
+end
+
gemspec
diff --git a/README.md b/README.md
index 421a70bea7..104726b85e 100644
--- a/README.md
+++ b/README.md
@@ -19,8 +19,8 @@ RailsAdmin is a Rails engine that provides an easy-to-use interface for managing
- Check out [the docs][docs].
- Try the [live demo][demo]. ([Source code][dummy_app])
-[demo]: http://rails-admin-tb.herokuapp.com/
-[dummy_app]: https://github.com/bbenezech/dummy_app
+[demo]: https://rails-admin.fly.dev/admin/
+[dummy_app]: https://github.com/railsadminteam/rails_admin/tree/master/spec/dummy_app
[docs]: https://github.com/railsadminteam/rails_admin/wiki
## Features
diff --git a/app/assets/javascripts/rails_admin/application.js.erb b/app/assets/javascripts/rails_admin/application.js.erb
index c5aa0765fa..012612e82d 100644
--- a/app/assets/javascripts/rails_admin/application.js.erb
+++ b/app/assets/javascripts/rails_admin/application.js.erb
@@ -9,6 +9,7 @@
//= require 'rails_admin/popper'
//= require 'rails_admin/bootstrap'
+//= require 'rails_admin/abstract-select'
//= require 'rails_admin/filter-box'
//= require 'rails_admin/filtering-multiselect'
//= require 'rails_admin/filtering-select'
@@ -20,10 +21,10 @@
//= require 'rails_admin/ui'
//= require 'rails_admin/custom/ui'
-<% if defined?(ActiveStorage) %>
+<% if defined?(ActiveStorage::Engine) %>
//= require activestorage
<% end %>
-<% if defined?(ActionText) && Rails.gem_version >= Gem::Version.new('7.0') %>
+<% if defined?(ActionText::Engine) && Rails.gem_version >= Gem::Version.new('7.0') %>
//= require trix
//= require actiontext
<% end %>
diff --git a/app/assets/stylesheets/rails_admin/application.scss.erb b/app/assets/stylesheets/rails_admin/application.scss.erb
index 698be8c605..133163db2b 100644
--- a/app/assets/stylesheets/rails_admin/application.scss.erb
+++ b/app/assets/stylesheets/rails_admin/application.scss.erb
@@ -30,6 +30,6 @@
@import "rails_admin/styles/base/theming";
@import "rails_admin/custom/theming";
-<% if defined?(ActionText) && Rails.gem_version >= Gem::Version.new('7.0') %>
+<% if defined?(ActionText::Engine) && Rails.gem_version >= Gem::Version.new('7.0') %>
@import "trix";
<% end %>
diff --git a/app/controllers/rails_admin/main_controller.rb b/app/controllers/rails_admin/main_controller.rb
index 5968bb7bec..af5a69aba3 100644
--- a/app/controllers/rails_admin/main_controller.rb
+++ b/app/controllers/rails_admin/main_controller.rb
@@ -56,7 +56,11 @@ def respond_to_missing?(sym, include_private)
end
def back_or_index
- params[:return_to].presence && params[:return_to].include?(request.host) && (params[:return_to] != request.fullpath) ? params[:return_to] : index_path
+ allowed_return_to?(params[:return_to].to_s) ? params[:return_to] : index_path
+ end
+
+ def allowed_return_to?(url)
+ url != request.fullpath && url.start_with?(request.base_url, '/') && !url.start_with?('//')
end
def get_sort_hash(model_config)
diff --git a/app/helpers/rails_admin/application_helper.rb b/app/helpers/rails_admin/application_helper.rb
index 147fd7ca3f..fbc8de8efa 100644
--- a/app/helpers/rails_admin/application_helper.rb
+++ b/app/helpers/rails_admin/application_helper.rb
@@ -7,6 +7,10 @@ def authorized?(action_name, abstract_model = nil, object = nil)
action(action_name, abstract_model, object).try(:authorized?)
end
+ def current_action
+ params[:action].in?(%w[create new]) ? 'create' : 'update'
+ end
+
def current_action?(action, abstract_model = @abstract_model, object = @object)
@action.custom_key == action.custom_key &&
abstract_model.try(:to_param) == @abstract_model.try(:to_param) &&
diff --git a/app/helpers/rails_admin/form_builder.rb b/app/helpers/rails_admin/form_builder.rb
index 37cbb03e28..d52f9eb2cf 100644
--- a/app/helpers/rails_admin/form_builder.rb
+++ b/app/helpers/rails_admin/form_builder.rb
@@ -108,8 +108,8 @@ def dom_name(field)
end
def hidden_field(method, options = {})
- if method == :id
- super method, {value: object.id.to_s}
+ if method == :id && object.id.is_a?(Array)
+ super method, {value: RailsAdmin.config.composite_keys_serializer.serialize(object.id)}
else
super
end
diff --git a/app/helpers/rails_admin/main_helper.rb b/app/helpers/rails_admin/main_helper.rb
index d80f4e4b09..3f7540b872 100644
--- a/app/helpers/rails_admin/main_helper.rb
+++ b/app/helpers/rails_admin/main_helper.rb
@@ -45,7 +45,7 @@ def ordered_filter_options
filter_for_field = duplet[1]
filter_name = filter_for_field.keys.first
filter_hash = filter_for_field.values.first
- unless (field = filterable_fields.find { |f| f.name == filter_name.to_sym })
+ unless (field = filterable_fields.find { |f| f.name == filter_name.to_sym }&.with({view: self}))
raise "#{filter_name} is not currently filterable; filterable fields are #{filterable_fields.map(&:name).join(', ')}"
end
diff --git a/app/views/layouts/rails_admin/_head.html.erb b/app/views/layouts/rails_admin/_head.html.erb
index d59e9d8965..43ce0096d6 100644
--- a/app/views/layouts/rails_admin/_head.html.erb
+++ b/app/views/layouts/rails_admin/_head.html.erb
@@ -1,7 +1,8 @@
-
-
-
-
+
+
+
+
+
<%= csrf_meta_tag %>
<% case RailsAdmin::config.asset_source
when :webpacker %>
@@ -12,6 +13,8 @@
<%= stylesheet_link_tag "rails_admin/application.css", media: :all, data: {'turbo-track': 'reload'} %>
<%= javascript_include_tag "rails_admin/application.js", defer: true, data: {'turbo-track': 'reload'} %>
<% end %>
+<% when :vite %>
+ <%= vite_javascript_tag "rails_admin", defer: true, data: {'turbo-track': 'reload'} %>
<% when :webpack %>
<%= stylesheet_link_tag "rails_admin.css", media: :all, data: {'turbo-track': 'reload'} %>
<%= javascript_include_tag "rails_admin.js", defer: true, data: {'turbo-track': 'reload'} %>
@@ -19,11 +22,11 @@
<%= stylesheet_link_tag "rails_admin.css", media: :all, data: {'turbo-track': 'reload'} %>
<%= javascript_inline_importmap_tag(RailsAdmin::Engine.importmap.to_json(resolver: self)) %>
<%= javascript_importmap_module_preload_tags(RailsAdmin::Engine.importmap) %>
- <%= javascript_importmap_shim_nonce_configuration_tag %>
- <%= javascript_importmap_shim_tag %>
+ <%= javascript_importmap_shim_nonce_configuration_tag if respond_to? :javascript_importmap_shim_nonce_configuration_tag %>
+ <%= javascript_importmap_shim_tag if respond_to? :javascript_importmap_shim_tag %>
<%= # Preload jQuery and make it global, unless jQuery UI fails to initialize
tag.script "import jQuery from 'jquery'; window.jQuery = jQuery;".html_safe, type: "module" %>
<%= javascript_import_module_tag 'rails_admin' %>
<% else
raise "Unknown asset_source: #{RailsAdmin::config.asset_source}"
- end %>
\ No newline at end of file
+ end %>
diff --git a/app/views/rails_admin/main/_form_boolean.html.erb b/app/views/rails_admin/main/_form_boolean.html.erb
index 87ae0bab16..36a5196716 100644
--- a/app/views/rails_admin/main/_form_boolean.html.erb
+++ b/app/views/rails_admin/main/_form_boolean.html.erb
@@ -2,9 +2,9 @@
<% {'1': [true, 'btn-outline-success'], '0': [false, 'btn-outline-danger'], '': [nil, 'btn-outline-secondary']}.each do |text, (value, btn_class)| %>
<%= form.radio_button field.method_name, text, field.html_attributes.reverse_merge({ checked: field.form_value == value, required: field.required, class: 'btn-check' }) %>
-
+ <% end %>
<% end %>
<% else %>
diff --git a/app/views/rails_admin/main/_form_filtering_multiselect.html.erb b/app/views/rails_admin/main/_form_filtering_multiselect.html.erb
index f279c940dd..5260087bd3 100644
--- a/app/views/rails_admin/main/_form_filtering_multiselect.html.erb
+++ b/app/views/rails_admin/main/_form_filtering_multiselect.html.erb
@@ -1,44 +1,14 @@
<%
config = field.associated_model_config
- source_abstract_model = RailsAdmin.config(form.object.class).abstract_model
-
- selected = form.object.send(field.name)
- selected_ids = selected.map{|s| s.send(field.associated_primary_key).to_s}
-
- current_action = params[:action].in?(['create', 'new']) ? 'create' : 'update'
-
- xhr = !field.associated_collection_cache_all
-
- collection = if xhr
- selected.map { |o| [o.send(field.associated_object_label_method), o.send(field.associated_primary_key)] }
- else
- i = 0
- controller.list_entries(config, :index, field.associated_collection_scope, false).map { |o| [o.send(field.associated_object_label_method), o.send(field.associated_primary_key).to_s] }.sort_by {|a| [selected_ids.index(a[1]) || selected_ids.size, i+=1] }
- end
-
- js_data = {
- xhr: xhr,
- :'edit-url' => (field.inline_edit && authorized?(:edit, config.abstract_model) ? edit_path(model_name: config.abstract_model.to_param, id: '__ID__') : ''),
- remote_source: index_path(config.abstract_model, source_object_id: form.object.id, source_abstract_model: source_abstract_model.to_param, associated_collection: field.name, current_action: current_action, compact: true),
- sortable: !!field.orderable,
- removable: !!field.removable,
- cacheAll: !!field.associated_collection_cache_all,
- regional: {
- add: t('admin.misc.add_new'),
- chooseAll: t('admin.misc.chose_all'),
- clearAll: t('admin.misc.clear_all'),
- down: t('admin.misc.down'),
- remove: t('admin.misc.remove'),
- search: t('admin.misc.search'),
- up: t('admin.misc.up')
- }
- }
%>
+
diff --git a/app/views/rails_admin/main/index.html.erb b/app/views/rails_admin/main/index.html.erb
index 18281e4314..54c32cab2c 100644
--- a/app/views/rails_admin/main/index.html.erb
+++ b/app/views/rails_admin/main/index.html.erb
@@ -133,9 +133,9 @@
<% end %>
<% properties.map{ |property| property.bind(:object, object) }.each do |property| %>
<% value = property.pretty_value %>
- |
+ <%= content_tag(:td, class: [property.sticky? && 'sticky', property.css_class, property.type_css_class].select(&:present?), title: strip_tags(value.to_s)) do %>
<%= value %>
- |
+ <% end %>
<% end %>
diff --git a/config/initializers/active_record_extensions.rb b/config/initializers/active_record_extensions.rb
index a2c1a7f3c0..b34f31a91a 100644
--- a/config/initializers/active_record_extensions.rb
+++ b/config/initializers/active_record_extensions.rb
@@ -20,27 +20,4 @@ def safe_send(value)
end
end
end
-
- if defined?(CompositePrimaryKeys)
- # Apply patch until the fix is released:
- # https://github.com/composite-primary-keys/composite_primary_keys/pull/572
- CompositePrimaryKeys::CompositeKeys.class_eval do
- alias_method :to_param, :to_s
- end
-
- CompositePrimaryKeys::CollectionAssociation.prepend(Module.new do
- def ids_writer(ids)
- if reflection.association_primary_key.is_a? Array
- ids = CompositePrimaryKeys.normalize(Array(ids).reject(&:blank?), reflection.association_primary_key.size)
- reflection.association_primary_key.each_with_index do |primary_key, i|
- pk_type = klass.type_for_attribute(primary_key)
- ids.each do |id|
- id[i] = pk_type.cast(id[i]) if id.is_a? Array
- end
- end
- end
- super ids
- end
- end)
- end
end
diff --git a/gemfiles/composite_primary_keys.gemfile b/gemfiles/composite_primary_keys.gemfile
index e1b1aae1d9..1ebba5c55d 100644
--- a/gemfiles/composite_primary_keys.gemfile
+++ b/gemfiles/composite_primary_keys.gemfile
@@ -3,36 +3,28 @@
source "https://rubygems.org"
gem "appraisal", ">= 2.0"
-gem "devise", "~> 4.8"
+gem "devise", "~> 4.7"
gem "net-smtp", require: false
gem "rails", "~> 7.0.0"
-gem "webpacker", require: false
-gem "webrick", "~> 1.7"
gem "sassc-rails", "~> 2.1"
-
-group :active_record do
- gem "paper_trail", ">= 12.0"
- gem "composite_primary_keys"
-
- platforms :ruby, :mswin, :mingw, :x64_mingw do
- gem "mysql2", ">= 0.3.14"
- gem "sqlite3", ">= 1.3"
- end
-end
+gem "turbo-rails"
+gem "vite_rails", require: false
+gem "webpacker", require: false
+gem "webrick"
group :development, :test do
gem "pry", ">= 0.9"
end
group :test do
- gem "cancancan", "~> 3.2"
+ gem "cancancan", "~> 3.0"
gem "carrierwave", [">= 2.0.0.rc", "< 3"]
- gem "cuprite"
+ gem "cuprite", "!= 0.15.1"
gem "database_cleaner-active_record", ">= 2.0", require: false
- gem "database_cleaner-mongoid", ">= 2.0", require: false
gem "dragonfly", "~> 1.0"
- gem "factory_bot", ">= 4.2"
+ gem "factory_bot", ">= 4.2", "!= 6.4.5"
gem "generator_spec", ">= 0.8"
+ gem "kt-paperclip"
gem "launchy", ">= 2.2"
gem "mini_magick", ">= 3.4"
gem "pundit"
@@ -42,12 +34,22 @@ group :test do
gem "rspec-retry"
gem "rubocop", ["~> 1.20", "!= 1.22.2"], require: false
gem "rubocop-performance", require: false
+ gem "shrine", "~> 3.0"
gem "simplecov", ">= 0.9", require: false
gem "simplecov-lcov", require: false
gem "timecop", ">= 0.5"
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
- gem "kt-paperclip"
- gem "shrine", "~> 3.0"
+end
+
+group :active_record do
+ gem "paper_trail", ">= 12.0"
+ gem "composite_primary_keys"
+
+ platforms :ruby, :mswin, :mingw, :x64_mingw do
+ gem "mysql2", ">= 0.3.14"
+ gem "pg", ">= 1.0.0"
+ gem "sqlite3", "~> 1.3"
+ end
end
gemspec path: "../"
diff --git a/gemfiles/rails_6.0.gemfile b/gemfiles/rails_6.0.gemfile
index 348a323df1..75954b32ec 100644
--- a/gemfiles/rails_6.0.gemfile
+++ b/gemfiles/rails_6.0.gemfile
@@ -6,39 +6,26 @@ gem "appraisal", ">= 2.0"
gem "devise", "~> 4.7"
gem "net-smtp", require: false
gem "rails", "~> 6.0.0"
-gem "webpacker", require: false
-gem "webrick", "~> 1.7"
gem "sassc-rails", "~> 2.1"
-
-group :active_record do
- gem "paper_trail", ">= 12.0"
- gem "pg", ">= 1.0.0", platforms: :ruby
-
- platforms :ruby, :mswin, :mingw, :x64_mingw do
- gem "mysql2", ">= 0.3.14"
- gem "sqlite3", ">= 1.3"
- end
-
- platforms :jruby do
- gem "activerecord-jdbcmysql-adapter", "~> 60.0"
- gem "activerecord-jdbcpostgresql-adapter", "~> 60.0"
- gem "activerecord-jdbcsqlite3-adapter", "~> 60.0"
- end
-end
+gem "turbo-rails", "< 2.0.8"
+gem "vite_rails", require: false
+gem "webpacker", require: false
+gem "webrick"
+gem "psych", "~> 3.3"
group :development, :test do
gem "pry", ">= 0.9"
end
group :test do
- gem "cancancan", "~> 3.0"
+ gem "cancancan", ["~> 3.0", "< 3.6"]
gem "carrierwave", [">= 2.0.0.rc", "< 3"]
- gem "cuprite"
+ gem "cuprite", "!= 0.15.1"
gem "database_cleaner-active_record", ">= 2.0", require: false
- gem "database_cleaner-mongoid", ">= 2.0", require: false
gem "dragonfly", "~> 1.0"
- gem "factory_bot", ">= 4.2"
+ gem "factory_bot", ">= 4.2", "!= 6.4.5"
gem "generator_spec", ">= 0.8"
+ gem "kt-paperclip"
gem "launchy", ">= 2.2"
gem "mini_magick", ">= 3.4"
gem "pundit", "~> 2.1.0"
@@ -48,20 +35,36 @@ group :test do
gem "rspec-retry"
gem "rubocop", ["~> 1.20", "!= 1.22.2"], require: false
gem "rubocop-performance", require: false
+ gem "shrine", "~> 3.0"
gem "simplecov", ">= 0.9", require: false
gem "simplecov-lcov", require: false
gem "timecop", ">= 0.5"
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
- gem "kt-paperclip"
- gem "shrine", "~> 3.0"
+end
+
+group :active_record do
+ gem "paper_trail", ">= 12.0"
+
+ platforms :ruby, :mswin, :mingw, :x64_mingw do
+ gem "mysql2", ">= 0.3.14"
+ gem "pg", ">= 1.0.0"
+ gem "sqlite3", ">= 1.3.0"
+ end
+
+ platforms :jruby do
+ gem "activerecord-jdbcmysql-adapter", "~> 60.0"
+ gem "activerecord-jdbcpostgresql-adapter", "~> 60.0"
+ gem "activerecord-jdbcsqlite3-adapter", "~> 60.0"
+ end
end
group :mongoid do
- gem "mongoid", "~> 7.0"
+ gem "cancancan-mongoid"
+ gem "carrierwave-mongoid", ">= 0.6.3", require: "carrierwave/mongoid"
+ gem "database_cleaner-mongoid", ">= 2.0", require: false
gem "kaminari-mongoid"
+ gem "mongoid", "~> 7.0"
gem "mongoid-paperclip", ">= 0.0.8", require: "mongoid_paperclip"
- gem "carrierwave-mongoid", ">= 0.6.3", require: "carrierwave/mongoid"
- gem "cancancan-mongoid"
gem "shrine-mongoid", "~> 1.0"
end
diff --git a/gemfiles/rails_6.1.gemfile b/gemfiles/rails_6.1.gemfile
index 1ca2b06c86..cdf08f0c85 100644
--- a/gemfiles/rails_6.1.gemfile
+++ b/gemfiles/rails_6.1.gemfile
@@ -6,40 +6,25 @@ gem "appraisal", ">= 2.0"
gem "devise", "~> 4.7"
gem "net-smtp", require: false
gem "rails", "~> 6.1.0"
-gem "webpacker", require: false
-gem "webrick", "~> 1.7"
gem "sassc-rails", "~> 2.1"
-gem "turbo-rails", platform: :jruby, github: "hotwired/turbo-rails"
-
-group :active_record do
- gem "paper_trail", ">= 12.0"
- gem "pg", ">= 1.0.0", platforms: :ruby
-
- platforms :ruby, :mswin, :mingw, :x64_mingw do
- gem "mysql2", ">= 0.3.14"
- gem "sqlite3", ">= 1.3"
- end
-
- platforms :jruby do
- gem "activerecord-jdbcmysql-adapter", "~> 61.0"
- gem "activerecord-jdbcpostgresql-adapter", "~> 61.0"
- gem "activerecord-jdbcsqlite3-adapter", "~> 61.0"
- end
-end
+gem "turbo-rails"
+gem "vite_rails", require: false
+gem "webpacker", require: false
+gem "webrick"
group :development, :test do
gem "pry", ">= 0.9"
end
group :test do
- gem "cancancan", "~> 3.2"
+ gem "cancancan", "~> 3.0"
gem "carrierwave", [">= 2.0.0.rc", "< 3"]
- gem "cuprite"
+ gem "cuprite", "!= 0.15.1"
gem "database_cleaner-active_record", ">= 2.0", require: false
- gem "database_cleaner-mongoid", ">= 2.0", require: false
gem "dragonfly", "~> 1.0"
- gem "factory_bot", ">= 4.2"
+ gem "factory_bot", ">= 4.2", "!= 6.4.5"
gem "generator_spec", ">= 0.8"
+ gem "kt-paperclip"
gem "launchy", ">= 2.2"
gem "mini_magick", ">= 3.4"
gem "pundit"
@@ -49,20 +34,36 @@ group :test do
gem "rspec-retry"
gem "rubocop", ["~> 1.20", "!= 1.22.2"], require: false
gem "rubocop-performance", require: false
+ gem "shrine", "~> 3.0"
gem "simplecov", ">= 0.9", require: false
gem "simplecov-lcov", require: false
gem "timecop", ">= 0.5"
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
- gem "kt-paperclip"
- gem "shrine", "~> 3.0"
+end
+
+group :active_record do
+ gem "paper_trail", ">= 12.0"
+
+ platforms :ruby, :mswin, :mingw, :x64_mingw do
+ gem "mysql2", ">= 0.3.14"
+ gem "pg", ">= 1.0.0"
+ gem "sqlite3", ">= 1.3.0"
+ end
+
+ platforms :jruby do
+ gem "activerecord-jdbcmysql-adapter", "~> 61.0"
+ gem "activerecord-jdbcpostgresql-adapter", "~> 61.0"
+ gem "activerecord-jdbcsqlite3-adapter", "~> 61.0"
+ end
end
group :mongoid do
- gem "mongoid", "~> 7.0"
+ gem "cancancan-mongoid"
+ gem "carrierwave-mongoid", ">= 0.6.3", require: "carrierwave/mongoid"
+ gem "database_cleaner-mongoid", ">= 2.0", require: false
gem "kaminari-mongoid"
+ gem "mongoid", "~> 7.0"
gem "mongoid-paperclip", ">= 0.0.8", require: "mongoid_paperclip"
- gem "carrierwave-mongoid", ">= 0.6.3", require: "carrierwave/mongoid"
- gem "cancancan-mongoid"
gem "shrine-mongoid", "~> 1.0"
end
diff --git a/gemfiles/rails_7.0.gemfile b/gemfiles/rails_7.0.gemfile
index 114b3932d7..36f7ff8cf6 100644
--- a/gemfiles/rails_7.0.gemfile
+++ b/gemfiles/rails_7.0.gemfile
@@ -3,37 +3,29 @@
source "https://rubygems.org"
gem "appraisal", ">= 2.0"
-gem "devise", "~> 4.8"
+gem "devise", "~> 4.7"
gem "net-smtp", require: false
gem "rails", "~> 7.0.0"
+gem "sassc-rails", "~> 2.1"
+gem "turbo-rails"
+gem "vite_rails", require: false
gem "webpacker", require: false
-gem "webrick", "~> 1.7"
+gem "webrick"
gem "importmap-rails", require: false
-gem "sassc-rails", "~> 2.1"
-
-group :active_record do
- gem "paper_trail", ">= 12.0"
- gem "pg", ">= 1.0.0", platforms: :ruby
-
- platforms :ruby, :mswin, :mingw, :x64_mingw do
- gem "mysql2", ">= 0.3.14"
- gem "sqlite3", ">= 1.3"
- end
-end
group :development, :test do
gem "pry", ">= 0.9"
end
group :test do
- gem "cancancan", "~> 3.2"
+ gem "cancancan", "~> 3.0"
gem "carrierwave", [">= 2.0.0.rc", "< 3"]
- gem "cuprite"
+ gem "cuprite", "!= 0.15.1"
gem "database_cleaner-active_record", ">= 2.0", require: false
- gem "database_cleaner-mongoid", ">= 2.0", require: false
gem "dragonfly", "~> 1.0"
- gem "factory_bot", ">= 4.2"
+ gem "factory_bot", ">= 4.2", "!= 6.4.5"
gem "generator_spec", ">= 0.8"
+ gem "kt-paperclip"
gem "launchy", ">= 2.2"
gem "mini_magick", ">= 3.4"
gem "pundit"
@@ -43,12 +35,37 @@ group :test do
gem "rspec-retry"
gem "rubocop", ["~> 1.20", "!= 1.22.2"], require: false
gem "rubocop-performance", require: false
+ gem "shrine", "~> 3.0"
gem "simplecov", ">= 0.9", require: false
gem "simplecov-lcov", require: false
gem "timecop", ">= 0.5"
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
- gem "kt-paperclip"
- gem "shrine", "~> 3.0"
+end
+
+group :active_record do
+ gem "paper_trail", ">= 12.0"
+
+ platforms :ruby, :mswin, :mingw, :x64_mingw do
+ gem "mysql2", ">= 0.3.14"
+ gem "pg", ">= 1.0.0"
+ gem "sqlite3", "~> 1.3"
+ end
+
+ platforms :jruby do
+ gem "activerecord-jdbcmysql-adapter", "~> 70.0"
+ gem "activerecord-jdbcpostgresql-adapter", "~> 70.0"
+ gem "activerecord-jdbcsqlite3-adapter", "~> 70.0"
+ end
+end
+
+group :mongoid do
+ gem "cancancan-mongoid"
+ gem "carrierwave-mongoid", ">= 0.6.3", require: "carrierwave/mongoid"
+ gem "database_cleaner-mongoid", ">= 2.0", require: false
+ gem "kaminari-mongoid"
+ gem "mongoid", "~> 8.0"
+ gem "mongoid-paperclip", ">= 0.0.8", require: "mongoid_paperclip"
+ gem "shrine-mongoid", "~> 1.0"
end
gemspec path: "../"
diff --git a/gemfiles/rails_7.1.gemfile b/gemfiles/rails_7.1.gemfile
new file mode 100644
index 0000000000..98c7769fbc
--- /dev/null
+++ b/gemfiles/rails_7.1.gemfile
@@ -0,0 +1,65 @@
+# This file was generated by Appraisal
+
+source "https://rubygems.org"
+
+gem "appraisal", ">= 2.0"
+gem "devise", "~> 4.7"
+gem "net-smtp", require: false
+gem "rails", "~> 7.1.0"
+gem "sassc-rails", "~> 2.1"
+gem "turbo-rails"
+gem "vite_rails", require: false
+gem "webpacker", require: false
+gem "webrick"
+gem "importmap-rails", require: false
+
+group :development, :test do
+ gem "pry", ">= 0.9"
+end
+
+group :test do
+ gem "cancancan", "~> 3.0"
+ gem "carrierwave", [">= 2.0.0.rc", "< 3"]
+ gem "cuprite", "!= 0.15.1"
+ gem "database_cleaner-active_record", ">= 2.0", require: false
+ gem "dragonfly", "~> 1.0"
+ gem "factory_bot", ">= 4.2", "!= 6.4.5"
+ gem "generator_spec", ">= 0.8"
+ gem "kt-paperclip"
+ gem "launchy", ">= 2.2"
+ gem "mini_magick", ">= 3.4"
+ gem "pundit"
+ gem "rack-cache", require: "rack/cache"
+ gem "rspec-expectations", "!= 3.8.3"
+ gem "rspec-rails", ">= 4.0.0.beta2"
+ gem "rspec-retry"
+ gem "rubocop", ["~> 1.20", "!= 1.22.2"], require: false
+ gem "rubocop-performance", require: false
+ gem "shrine", "~> 3.0"
+ gem "simplecov", ">= 0.9", require: false
+ gem "simplecov-lcov", require: false
+ gem "timecop", ">= 0.5"
+ gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+end
+
+group :active_record do
+ gem "paper_trail", ">= 12.0"
+
+ platforms :ruby, :mswin, :mingw, :x64_mingw do
+ gem "mysql2", ">= 0.3.14"
+ gem "pg", ">= 1.0.0"
+ gem "sqlite3", "~> 1.3"
+ end
+end
+
+group :mongoid do
+ gem "cancancan-mongoid"
+ gem "carrierwave-mongoid", ">= 0.6.3", require: "carrierwave/mongoid"
+ gem "database_cleaner-mongoid", ">= 2.0", require: false
+ gem "kaminari-mongoid"
+ gem "mongoid", "~> 8.0"
+ gem "mongoid-paperclip", ">= 0.0.8", require: "mongoid_paperclip"
+ gem "shrine-mongoid", "~> 1.0"
+end
+
+gemspec path: "../"
diff --git a/gemfiles/rails_7.2.gemfile b/gemfiles/rails_7.2.gemfile
new file mode 100644
index 0000000000..94dc6a1fab
--- /dev/null
+++ b/gemfiles/rails_7.2.gemfile
@@ -0,0 +1,55 @@
+# This file was generated by Appraisal
+
+source "https://rubygems.org"
+
+gem "appraisal", ">= 2.0"
+gem "devise", "~> 4.7"
+gem "net-smtp", require: false
+gem "rails", "~> 7.2.0"
+gem "sassc-rails", "~> 2.1"
+gem "turbo-rails"
+gem "vite_rails", require: false
+gem "webpacker", require: false
+gem "webrick"
+gem "importmap-rails", require: false
+
+group :development, :test do
+ gem "pry", ">= 0.9"
+end
+
+group :test do
+ gem "cancancan", "~> 3.0"
+ gem "carrierwave", [">= 2.0.0.rc", "< 3"]
+ gem "cuprite", "!= 0.15.1"
+ gem "database_cleaner-active_record", ">= 2.0", require: false
+ gem "dragonfly", "~> 1.0"
+ gem "factory_bot", ">= 4.2", "!= 6.4.5"
+ gem "generator_spec", ">= 0.8"
+ gem "kt-paperclip"
+ gem "launchy", ">= 2.2"
+ gem "mini_magick", ">= 3.4"
+ gem "pundit"
+ gem "rack-cache", require: "rack/cache"
+ gem "rspec-expectations", "!= 3.8.3"
+ gem "rspec-rails", ">= 4.0.0.beta2"
+ gem "rspec-retry"
+ gem "rubocop", ["~> 1.20", "!= 1.22.2"], require: false
+ gem "rubocop-performance", require: false
+ gem "shrine", "~> 3.0"
+ gem "simplecov", ">= 0.9", require: false
+ gem "simplecov-lcov", require: false
+ gem "timecop", ">= 0.5"
+ gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+end
+
+group :active_record do
+ gem "paper_trail", ">= 12.0"
+
+ platforms :ruby, :mswin, :mingw, :x64_mingw do
+ gem "mysql2", ">= 0.3.14"
+ gem "pg", ">= 1.0.0"
+ gem "sqlite3", ">= 1.3.0"
+ end
+end
+
+gemspec path: "../"
diff --git a/gemfiles/rails_8.0.gemfile b/gemfiles/rails_8.0.gemfile
new file mode 100644
index 0000000000..873be45708
--- /dev/null
+++ b/gemfiles/rails_8.0.gemfile
@@ -0,0 +1,55 @@
+# This file was generated by Appraisal
+
+source "https://rubygems.org"
+
+gem "appraisal", ">= 2.0"
+gem "devise", "~> 4.7"
+gem "net-smtp", require: false
+gem "rails", "~> 8.0.0"
+gem "sassc-rails", "~> 2.1"
+gem "turbo-rails"
+gem "vite_rails", require: false
+gem "webpacker", require: false
+gem "webrick"
+gem "importmap-rails", require: false
+
+group :development, :test do
+ gem "pry", ">= 0.9"
+end
+
+group :test do
+ gem "cancancan", "~> 3.0"
+ gem "carrierwave", [">= 2.0.0.rc", "< 3"]
+ gem "cuprite", "!= 0.15.1"
+ gem "database_cleaner-active_record", ">= 2.0", require: false
+ gem "dragonfly", "~> 1.0"
+ gem "factory_bot", ">= 4.2", "!= 6.4.5"
+ gem "generator_spec", ">= 0.8"
+ gem "kt-paperclip"
+ gem "launchy", ">= 2.2"
+ gem "mini_magick", ">= 3.4"
+ gem "pundit"
+ gem "rack-cache", require: "rack/cache"
+ gem "rspec-expectations", "!= 3.8.3"
+ gem "rspec-rails", ">= 4.0.0.beta2"
+ gem "rspec-retry"
+ gem "rubocop", ["~> 1.20", "!= 1.22.2"], require: false
+ gem "rubocop-performance", require: false
+ gem "shrine", "~> 3.0"
+ gem "simplecov", ">= 0.9", require: false
+ gem "simplecov-lcov", require: false
+ gem "timecop", ">= 0.5"
+ gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+end
+
+group :active_record do
+ gem "paper_trail", ">= 12.0"
+
+ platforms :ruby, :mswin, :mingw, :x64_mingw do
+ gem "mysql2", ">= 0.3.14"
+ gem "pg", ">= 1.0.0"
+ gem "sqlite3", ">= 1.3.0"
+ end
+end
+
+gemspec path: "../"
diff --git a/lib/generators/rails_admin/importmap_formatter.rb b/lib/generators/rails_admin/importmap_formatter.rb
index 6b40080f2b..138a0e5a39 100644
--- a/lib/generators/rails_admin/importmap_formatter.rb
+++ b/lib/generators/rails_admin/importmap_formatter.rb
@@ -6,12 +6,12 @@ module RailsAdmin
class ImportmapFormatter
attr_reader :packager
- def initialize(path = 'confing/importmap.rails_admin.rb')
+ def initialize(path = 'config/importmap.rails_admin.rb')
@packager = Importmap::Packager.new(path)
end
def format
- imports = packager.import("rails_admin@#{RailsAdmin::Version.js}")
+ imports = packager.import("rails_admin@#{RailsAdmin::Version.js}", from: 'jspm.io')
# Use ESM compatible version to work around https://github.com/cljsjs/packages/issues/1579
imports['@popperjs/core'].gsub!('lib/index.js', 'dist/esm/popper.js')
diff --git a/lib/generators/rails_admin/install_generator.rb b/lib/generators/rails_admin/install_generator.rb
index b0c83c4afe..ca86af7bfc 100644
--- a/lib/generators/rails_admin/install_generator.rb
+++ b/lib/generators/rails_admin/install_generator.rb
@@ -10,7 +10,7 @@ class InstallGenerator < Rails::Generators::Base
include Generators::Utils::InstanceMethods
argument :_namespace, type: :string, required: false, desc: 'RailsAdmin url namespace'
- class_option :asset, type: :string, required: false, default: nil, desc: 'Asset delivery method [options: webpacker, webpack, sprockets, importmap]'
+ class_option :asset, type: :string, required: false, default: nil, desc: 'Asset delivery method [options: webpacker, webpack, sprockets, importmap, vite]'
desc 'RailsAdmin installation generator'
def install
@@ -33,6 +33,8 @@ def install
configure_for_importmap
when 'webpacker'
configure_for_webpacker5
+ when 'vite'
+ configure_for_vite
when 'sprockets'
configure_for_sprockets
else
@@ -51,6 +53,8 @@ def asset
'webpack'
elsif Rails.root.join('config/importmap.rb').exist?
'importmap'
+ elsif defined?(ViteRuby)
+ 'vite'
else
'sprockets'
end
@@ -68,6 +72,14 @@ def configure_for_webpacker5
add_package_json_field('resolutions', {'rails_admin/@fortawesome/fontawesome-free' => '^5.15.0'})
end
+ def configure_for_vite
+ vite_source_code_dir = ViteRuby.config.source_code_dir
+ run "yarn add rails_admin@#{RailsAdmin::Version.js} sass"
+ template('rails_admin.vite.js', File.join(vite_source_code_dir, 'entrypoints', 'rails_admin.js'))
+ @fa_font_path = '@fortawesome/fontawesome-free/webfonts'
+ template('rails_admin.scss.erb', File.join(vite_source_code_dir, 'stylesheets', 'rails_admin.scss'))
+ end
+
def configure_for_webpack
run "yarn add rails_admin@#{RailsAdmin::Version.js}"
template 'rails_admin.js', 'app/javascript/rails_admin.js'
diff --git a/lib/generators/rails_admin/templates/rails_admin.vite.js b/lib/generators/rails_admin/templates/rails_admin.vite.js
new file mode 100644
index 0000000000..1248edc4ba
--- /dev/null
+++ b/lib/generators/rails_admin/templates/rails_admin.vite.js
@@ -0,0 +1,2 @@
+import "~/stylesheets/rails_admin.scss";
+import "rails_admin/src/rails_admin/base";
diff --git a/lib/rails_admin/abstract_model.rb b/lib/rails_admin/abstract_model.rb
index 88f2e5dcef..20884be487 100644
--- a/lib/rails_admin/abstract_model.rb
+++ b/lib/rails_admin/abstract_model.rb
@@ -60,6 +60,14 @@ def model
@model_name.constantize
end
+ def quoted_table_name
+ table_name
+ end
+
+ def quote_column_name(name)
+ name
+ end
+
def to_s
model.to_s
end
@@ -97,17 +105,20 @@ def each_associated_children(object)
end
end
+ def format_id(id)
+ id
+ end
+
+ def parse_id(id)
+ id
+ end
+
private
def initialize_active_record
@adapter = :active_record
- if defined?(::CompositePrimaryKeys)
- require 'rails_admin/adapters/composite_primary_keys'
- extend Adapters::CompositePrimaryKeys
- else
- require 'rails_admin/adapters/active_record'
- extend Adapters::ActiveRecord
- end
+ require 'rails_admin/adapters/active_record'
+ extend Adapters::ActiveRecord
end
def initialize_mongoid
diff --git a/lib/rails_admin/adapters/active_record.rb b/lib/rails_admin/adapters/active_record.rb
index 1c165d265a..e179280e78 100644
--- a/lib/rails_admin/adapters/active_record.rb
+++ b/lib/rails_admin/adapters/active_record.rb
@@ -15,7 +15,7 @@ def new(params = {})
end
def get(id, scope = scoped)
- object = scope.where(primary_key => id).first
+ object = primary_key_scope(scope, id).first
return unless object
object.extend(ObjectExtension)
@@ -72,6 +72,14 @@ def base_class
delegate :primary_key, :table_name, to: :model, prefix: false
+ def quoted_table_name
+ model.quoted_table_name
+ end
+
+ def quote_column_name(name)
+ model.connection.quote_column_name(name)
+ end
+
def encoding
adapter =
if ::ActiveRecord::Base.respond_to?(:connection_db_config)
@@ -107,10 +115,42 @@ def adapter_supports_joins?
true
end
+ def format_id(id)
+ if primary_key.is_a? Array
+ RailsAdmin.config.composite_keys_serializer.serialize(id)
+ else
+ id
+ end
+ end
+
+ def parse_id(id)
+ if primary_key.is_a?(Array)
+ ids = RailsAdmin.config.composite_keys_serializer.deserialize(id)
+ primary_key.each_with_index do |key, i|
+ ids[i] = model.type_for_attribute(key).cast(ids[i])
+ end
+ ids
+ else
+ id
+ end
+ end
+
private
+ def primary_key_scope(scope, id)
+ if primary_key.is_a? Array
+ scope.where(primary_key.zip(parse_id(id)).to_h)
+ else
+ scope.where(primary_key => id)
+ end
+ end
+
def bulk_scope(scope, options)
- scope.where(primary_key => options[:bulk_ids])
+ if primary_key.is_a? Array
+ options[:bulk_ids].map { |id| primary_key_scope(scope, id) }.reduce(&:or)
+ else
+ scope.where(primary_key => options[:bulk_ids])
+ end
end
def sort_scope(scope, options)
@@ -172,7 +212,7 @@ def query_scope(scope, query, fields = config.list.fields.select(&:queryable?))
# "0055" is the filter index, no use here. o is the operator, v the value
def filter_scope(scope, filters, fields = config.list.fields.select(&:filterable?))
filters.each_pair do |field_name, filters_dump|
- filters_dump.each do |_, filter_dump|
+ filters_dump.each_value do |filter_dump|
wb = WhereBuilder.new(scope)
field = fields.detect { |f| f.name.to_s == field_name }
value = parse_field_value(field, filter_dump[:v])
@@ -201,6 +241,8 @@ def unary_operators
case @type
when :boolean
boolean_unary_operators
+ when :uuid
+ uuid_unary_operators
when :integer, :decimal, :float
numeric_unary_operators
else
@@ -230,6 +272,7 @@ def boolean_unary_operators
)
end
alias_method :numeric_unary_operators, :boolean_unary_operators
+ alias_method :uuid_unary_operators, :boolean_unary_operators
def range_filter(min, max)
if min && max && min == max
@@ -255,8 +298,12 @@ def build_statement_for_type
end
def build_statement_for_boolean
- return ["(#{@column} IS NULL OR #{@column} = ?)", false] if %w[false f 0].include?(@value)
- return ["(#{@column} = ?)", true] if %w[true t 1].include?(@value)
+ case @value
+ when 'false', 'f', '0'
+ ["(#{@column} IS NULL OR #{@column} = ?)", false]
+ when 'true', 't', '1'
+ ["(#{@column} = ?)", true]
+ end
end
def column_for_value(value)
diff --git a/lib/rails_admin/adapters/active_record/association.rb b/lib/rails_admin/adapters/active_record/association.rb
index c31d00e48f..61f1767948 100644
--- a/lib/rails_admin/adapters/active_record/association.rb
+++ b/lib/rails_admin/adapters/active_record/association.rb
@@ -33,7 +33,7 @@ def field_type
def klass
if options[:polymorphic]
- polymorphic_parents(:active_record, model.name.to_s, name) || []
+ polymorphic_parents(:active_record, association.active_record.name.to_s, name) || []
else
association.klass
end
@@ -42,16 +42,29 @@ def klass
def primary_key
return nil if polymorphic?
- case type
- when :has_one
- association.klass.primary_key
+ value =
+ case type
+ when :has_one
+ association.klass.primary_key
+ else
+ association.association_primary_key
+ end
+
+ if value.is_a? Array
+ :id
else
- association.association_primary_key
- end.try(:to_sym)
+ value.to_sym
+ end
end
def foreign_key
- association.foreign_key.to_sym
+ if association.options[:query_constraints].present?
+ association.options[:query_constraints].map(&:to_sym)
+ elsif association.foreign_key.is_a?(Array)
+ association.foreign_key.map(&:to_sym)
+ else
+ association.foreign_key.to_sym
+ end
end
def foreign_key_nullable?
@@ -71,11 +84,15 @@ def foreign_inverse_of
def key_accessor
case type
when :has_many, :has_and_belongs_to_many
- "#{name.to_s.singularize}_ids".to_sym
+ :"#{name.to_s.singularize}_ids"
when :has_one
- "#{name}_id".to_sym
+ :"#{name}_id"
else
- foreign_key
+ if foreign_key.is_a?(Array)
+ :"#{name}_id"
+ else
+ foreign_key
+ end
end
end
diff --git a/lib/rails_admin/adapters/active_record/object_extension.rb b/lib/rails_admin/adapters/active_record/object_extension.rb
index 286883838b..a26be2e506 100644
--- a/lib/rails_admin/adapters/active_record/object_extension.rb
+++ b/lib/rails_admin/adapters/active_record/object_extension.rb
@@ -4,24 +4,6 @@ module RailsAdmin
module Adapters
module ActiveRecord
module ObjectExtension
- def self.extended(object)
- object.class.reflect_on_all_associations.each do |association|
- association = Association.new(association, object.class)
- case association.type
- when :has_one
- object.instance_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{association.name}_id
- self.#{association.name}&.id
- end
-
- def #{association.name}_id=(item_id)
- self.#{association.name} = (#{association.klass}.find(item_id) rescue nil)
- end
- RUBY
- end
- end
- end
-
def assign_attributes(attributes)
super if attributes
end
diff --git a/lib/rails_admin/adapters/composite_primary_keys.rb b/lib/rails_admin/adapters/composite_primary_keys.rb
deleted file mode 100644
index 851ea25725..0000000000
--- a/lib/rails_admin/adapters/composite_primary_keys.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_admin/adapters/active_record'
-require 'rails_admin/adapters/composite_primary_keys/association'
-
-module RailsAdmin
- module Adapters
- module CompositePrimaryKeys
- include RailsAdmin::Adapters::ActiveRecord
-
- def get(id, scope = scoped)
- begin
- object = scope.find(id)
- rescue ::ActiveRecord::RecordNotFound
- return nil
- end
-
- object.extend(RailsAdmin::Adapters::ActiveRecord::ObjectExtension)
- end
-
- def associations
- model.reflect_on_all_associations.collect do |association|
- RailsAdmin::Adapters::CompositePrimaryKeys::Association.new(association, model)
- end
- end
-
- private
-
- def bulk_scope(scope, options)
- if primary_key.is_a? Array
- options[:bulk_ids].map do |id|
- scope.where(primary_key.zip(::CompositePrimaryKeys::CompositeKeys.parse(id)).to_h)
- end.reduce(&:or)
- else
- super
- end
- end
- end
- end
-end
diff --git a/lib/rails_admin/adapters/composite_primary_keys/association.rb b/lib/rails_admin/adapters/composite_primary_keys/association.rb
deleted file mode 100644
index 73a7edfdfd..0000000000
--- a/lib/rails_admin/adapters/composite_primary_keys/association.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-module RailsAdmin
- module Adapters
- module CompositePrimaryKeys
- class Association < RailsAdmin::Adapters::ActiveRecord::Association
- def field_type
- if type == :belongs_to && association.foreign_key.is_a?(Array)
- :composite_keys_belongs_to_association
- else
- super
- end
- end
-
- def primary_key
- return nil if polymorphic?
-
- value = association.association_primary_key
-
- if value.is_a? Array
- :id
- else
- value.to_sym
- end
- end
-
- def foreign_key
- if association.foreign_key.is_a? Array
- association.foreign_key.map(&:to_sym)
- else
- super
- end
- end
-
- def key_accessor
- if type == :belongs_to && foreign_key.is_a?(Array)
- :"#{name}_id"
- else
- super
- end
- end
- end
- end
- end
-end
diff --git a/lib/rails_admin/adapters/mongoid.rb b/lib/rails_admin/adapters/mongoid.rb
index 7aa947fa59..52a3870afc 100644
--- a/lib/rails_admin/adapters/mongoid.rb
+++ b/lib/rails_admin/adapters/mongoid.rb
@@ -31,6 +31,7 @@ def get(id, scope = scoped)
Mongoid::Errors::InvalidFind
Moped::Errors::InvalidObjectId
BSON::InvalidObjectId
+ BSON::Error::InvalidObjectId
].exclude?(e.class.to_s)
end
@@ -152,7 +153,7 @@ def filter_scope(scope, filters, fields = config.list.fields.select(&:filterable
statements = []
filters.each_pair do |field_name, filters_dump|
- filters_dump.each do |_, filter_dump|
+ filters_dump.each_value do |filter_dump|
field = fields.detect { |f| f.name.to_s == field_name }
next unless field
@@ -250,8 +251,12 @@ def build_statement_for_type
end
def build_statement_for_boolean
- return {@column => false} if %w[false f 0].include?(@value)
- return {@column => true} if %w[true t 1].include?(@value)
+ case @value
+ when 'false', 'f', '0'
+ {@column => false}
+ when 'true', 't', '1'
+ {@column => true}
+ end
end
def column_for_value(value)
diff --git a/lib/rails_admin/adapters/mongoid/association.rb b/lib/rails_admin/adapters/mongoid/association.rb
index 726fbd6a24..c30bb31001 100644
--- a/lib/rails_admin/adapters/mongoid/association.rb
+++ b/lib/rails_admin/adapters/mongoid/association.rb
@@ -46,7 +46,7 @@ def field_type
def klass
if polymorphic? && %i[referenced_in belongs_to].include?(macro)
- polymorphic_parents(:mongoid, model.name, name) || []
+ polymorphic_parents(:mongoid, association.inverse_class_name, name) || []
else
association.klass
end
@@ -92,9 +92,9 @@ def foreign_inverse_of
def key_accessor
case macro.to_sym
when :has_many
- "#{name.to_s.singularize}_ids".to_sym
+ :"#{name.to_s.singularize}_ids"
when :has_one
- "#{name}_id".to_sym
+ :"#{name}_id"
when :embedded_in, :embeds_one, :embeds_many
nil
else
diff --git a/lib/rails_admin/adapters/mongoid/bson.rb b/lib/rails_admin/adapters/mongoid/bson.rb
index e1aa06ad81..1336d8bb7b 100644
--- a/lib/rails_admin/adapters/mongoid/bson.rb
+++ b/lib/rails_admin/adapters/mongoid/bson.rb
@@ -21,6 +21,7 @@ def parse_object_id(value)
Moped::Errors::InvalidObjectId
BSON::ObjectId::Invalid
BSON::InvalidObjectId
+ BSON::Error::InvalidObjectId
].exclude?(e.class.to_s)
end
end
diff --git a/lib/rails_admin/adapters/mongoid/object_extension.rb b/lib/rails_admin/adapters/mongoid/object_extension.rb
index 05b1df4f05..1dea9b890c 100644
--- a/lib/rails_admin/adapters/mongoid/object_extension.rb
+++ b/lib/rails_admin/adapters/mongoid/object_extension.rb
@@ -20,11 +20,6 @@ def self.extended(object)
send(name)&.save
end
end
- object.instance_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{name}_id=(item_id)
- self.#{name} = (#{association.klass}.find(item_id) rescue nil)
- end
- RUBY
end
end
end
diff --git a/lib/rails_admin/config.rb b/lib/rails_admin/config.rb
index bef1f4e78c..af557c8ee9 100644
--- a/lib/rails_admin/config.rb
+++ b/lib/rails_admin/config.rb
@@ -2,6 +2,7 @@
require 'rails_admin/config/lazy_model'
require 'rails_admin/config/sections/list'
+require 'rails_admin/support/composite_keys_serializer'
require 'active_support/core_ext/module/attribute_accessors'
module RailsAdmin
@@ -84,6 +85,9 @@ class << self
# Set where RailsAdmin fetches JS/CSS from, defaults to :sprockets
attr_writer :asset_source
+ # For customization of composite keys representation
+ attr_accessor :composite_keys_serializer
+
# Setup authentication to be run as a before filter
# This is run inside the controller instance so you can setup any authentication you need to
#
@@ -329,6 +333,7 @@ def reset
@navigation_static_links = {}
@navigation_static_label = nil
@asset_source = nil
+ @composite_keys_serializer = RailsAdmin::Support::CompositeKeysSerializer
@parent_controller = '::ActionController::Base'
@forgery_protection_settings = {with: :exception}
RailsAdmin::Config::Actions.reset
@@ -378,6 +383,8 @@ def viable_models
end
end
end.flatten.reject { |m| m.starts_with?('Concerns::') } # rubocop:disable Style/MultilineBlockChain
+
+ @@system_models + @registry.keys.collect(&:to_s)
end
end
diff --git a/lib/rails_admin/config/actions/index.rb b/lib/rails_admin/config/actions/index.rb
index 546099bee3..366a15b57c 100644
--- a/lib/rails_admin/config/actions/index.rb
+++ b/lib/rails_admin/config/actions/index.rb
@@ -50,9 +50,11 @@ class Index < RailsAdmin::Config::Actions::Base
format.json do
output =
if params[:compact]
- primary_key_method = @association ? @association.associated_primary_key : @model_config.abstract_model.primary_key
- label_method = @model_config.object_label_method
- @objects.collect { |o| {id: o.send(primary_key_method).to_s, label: o.send(label_method).to_s} }
+ if @association
+ @association.collection(@objects).collect { |(label, id)| {id: id, label: label} }
+ else
+ @objects.collect { |object| {id: object.id.to_s, label: object.send(@model_config.object_label_method).to_s} }
+ end
else
@objects.to_json(@schema)
end
diff --git a/lib/rails_admin/config/fields/association.rb b/lib/rails_admin/config/fields/association.rb
index dfa0ad348d..b5d8194cad 100644
--- a/lib/rails_admin/config/fields/association.rb
+++ b/lib/rails_admin/config/fields/association.rb
@@ -13,7 +13,7 @@ def association
end
def method_name
- association.key_accessor
+ nested_form ? :"#{name}_attributes" : association.key_accessor
end
register_instance_option :pretty_value do
@@ -56,7 +56,30 @@ def method_name
# preload entire associated collection (per associated_collection_scope) on load
# Be sure to set limit in associated_collection_scope if set is large
register_instance_option :associated_collection_cache_all do
- @associated_collection_cache_all ||= (associated_model_config.abstract_model.count < associated_model_limit)
+ @associated_collection_cache_all ||= dynamically_scope_by.blank? && (associated_model_config.abstract_model.count < associated_model_limit)
+ end
+
+ # client-side dynamic scoping
+ register_instance_option :dynamically_scope_by do
+ nil
+ end
+
+ # parses #dynamically_scope_by and returns a Hash in the form of
+ # {[form field name in this model]: [field name in the associated model]}
+ def dynamic_scope_relationships
+ @dynamic_scope_relationships ||=
+ Array.wrap(dynamically_scope_by).flat_map do |field|
+ field.is_a?(Hash) ? field.to_a : [[field, field]]
+ end.map do |field_name, target_name| # rubocop:disable Style/MultilineBlockChain
+ field = section.fields.detect { |f| f.name == field_name }
+ raise "Field '#{field_name}' was given for #dynamically_scope_by but not found in '#{abstract_model.model_name}'" unless field
+
+ target_field = associated_model_config.list.fields.detect { |f| f.name == target_name }
+ raise "Field '#{field_name}' was given for #dynamically_scope_by but not found in '#{associated_model_config.abstract_model.model_name}'" unless target_field
+ raise "Field '#{field_name}' in '#{associated_model_config.abstract_model.model_name}' can't be used for dynamic scoping because it's not filterable" unless target_field.filterable
+
+ [field.method_name, target_name]
+ end.to_h
end
# determines whether association's elements can be removed
@@ -111,6 +134,12 @@ def value
bindings[:object].send(association.name)
end
+ # Returns collection of all selectable records
+ def collection(scope = nil)
+ (scope || bindings[:controller].list_entries(associated_model_config, :index, associated_collection_scope, false)).
+ map { |o| [o.send(associated_object_label_method), format_key(o.send(associated_primary_key)).to_s] }
+ end
+
# has many?
def multiple?
true
@@ -123,6 +152,16 @@ def virtual?
def associated_model_limit
RailsAdmin.config.default_associated_collection_limit
end
+
+ private
+
+ def format_key(key)
+ if key.is_a?(Array)
+ RailsAdmin.config.composite_keys_serializer.serialize(key)
+ else
+ key
+ end
+ end
end
end
end
diff --git a/lib/rails_admin/config/fields/base.rb b/lib/rails_admin/config/fields/base.rb
index d57803a416..184bc499d6 100644
--- a/lib/rails_admin/config/fields/base.rb
+++ b/lib/rails_admin/config/fields/base.rb
@@ -62,15 +62,15 @@ def virtual?
def sort_column
if sortable == true
- "#{abstract_model.table_name}.#{name}"
+ "#{abstract_model.quoted_table_name}.#{abstract_model.quote_column_name(name)}"
elsif (sortable.is_a?(String) || sortable.is_a?(Symbol)) && sortable.to_s.include?('.') # just provide sortable, don't do anything smart
sortable
elsif sortable.is_a?(Hash) # just join sortable hash, don't do anything smart
"#{sortable.keys.first}.#{sortable.values.first}"
- elsif association # use column on target table
- "#{associated_model_config.abstract_model.table_name}.#{sortable}"
+ elsif association? # use column on target table
+ "#{associated_model_config.abstract_model.quoted_table_name}.#{abstract_model.quote_column_name(sortable)}"
else # use described column in the field conf.
- "#{abstract_model.table_name}.#{sortable}"
+ "#{abstract_model.quoted_table_name}.#{abstract_model.quote_column_name(sortable)}"
end
end
@@ -357,7 +357,7 @@ def generic_help
def generic_field_help
model = abstract_model.model_name.underscore
- model_lookup = "admin.help.#{model}.#{name}".to_sym
+ model_lookup = :"admin.help.#{model}.#{name}"
translated = I18n.translate(model_lookup, help: generic_help, default: [generic_help])
(translated.is_a?(Hash) ? translated.to_a.first[1] : translated).html_safe
end
diff --git a/lib/rails_admin/config/fields/collection_association.rb b/lib/rails_admin/config/fields/collection_association.rb
new file mode 100644
index 0000000000..fc08cf264c
--- /dev/null
+++ b/lib/rails_admin/config/fields/collection_association.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'rails_admin/config/fields/association'
+
+module RailsAdmin
+ module Config
+ module Fields
+ class CollectionAssociation < Association
+ # orderable associated objects
+ register_instance_option :orderable do
+ false
+ end
+
+ register_instance_option :partial do
+ nested_form ? :form_nested_many : :form_filtering_multiselect
+ end
+
+ def collection(scope = nil)
+ if scope
+ super
+ elsif associated_collection_cache_all
+ selected = selected_ids
+ i = 0
+ super.sort_by { |a| [selected.index(a[1]) || selected.size, i += 1] }
+ else
+ value.map { |o| [o.send(associated_object_label_method), format_key(o.send(associated_primary_key))] }
+ end
+ end
+
+ def associated_prepopulate_params
+ {associated_model_config.abstract_model.param_key => {association.foreign_key => bindings[:object].try(:id)}}
+ end
+
+ def multiple?
+ true
+ end
+
+ def selected_ids
+ value.map { |s| format_key(s.send(associated_primary_key)).to_s }
+ end
+
+ def parse_input(params)
+ return unless associated_model_config.abstract_model.primary_key.is_a?(Array)
+
+ if nested_form
+ params[method_name].each_value do |value|
+ value[:id] = associated_model_config.abstract_model.parse_id(value[:id])
+ end
+ elsif params[method_name].is_a?(Array)
+ params[method_name] = params[method_name].map { |key| associated_model_config.abstract_model.parse_id(key) if key.present? }.compact
+ if params[method_name].empty?
+ # Workaround for Arel::Visitors::UnsupportedVisitError in #ids_writer, until https://github.com/rails/rails/pull/51116 is in place
+ params.delete(method_name)
+ params[name] = []
+ end
+ end
+ end
+
+ def form_default_value
+ (default_value if bindings[:object].new_record? && value.empty?)
+ end
+
+ def form_value
+ form_default_value.nil? ? selected_ids : form_default_value
+ end
+
+ def widget_options
+ {
+ xhr: !associated_collection_cache_all,
+ 'edit-url': (inline_edit && bindings[:view].authorized?(:edit, associated_model_config.abstract_model) ? bindings[:view].edit_path(model_name: associated_model_config.abstract_model.to_param, id: '__ID__') : ''),
+ remote_source: bindings[:view].index_path(associated_model_config.abstract_model, source_object_id: abstract_model.format_id(bindings[:object].id), source_abstract_model: abstract_model.to_param, associated_collection: name, current_action: bindings[:view].current_action, compact: true),
+ scopeBy: dynamic_scope_relationships,
+ sortable: !!orderable,
+ removable: !!removable,
+ cacheAll: !!associated_collection_cache_all,
+ regional: {
+ add: ::I18n.t('admin.misc.add_new'),
+ chooseAll: ::I18n.t('admin.misc.chose_all'),
+ clearAll: ::I18n.t('admin.misc.clear_all'),
+ down: ::I18n.t('admin.misc.down'),
+ remove: ::I18n.t('admin.misc.remove'),
+ search: ::I18n.t('admin.misc.search'),
+ up: ::I18n.t('admin.misc.up'),
+ },
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rails_admin/config/fields/factories/active_storage.rb b/lib/rails_admin/config/fields/factories/active_storage.rb
index 4ac9bb18af..82f5f1d7b0 100644
--- a/lib/rails_admin/config/fields/factories/active_storage.rb
+++ b/lib/rails_admin/config/fields/factories/active_storage.rb
@@ -13,9 +13,9 @@
fields << field
associations =
if properties.type == :has_many
- ["#{name}_attachments".to_sym, "#{name}_blobs".to_sym]
+ [:"#{name}_attachments", :"#{name}_blobs"]
else
- ["#{name}_attachment".to_sym, "#{name}_blob".to_sym]
+ [:"#{name}_attachment", :"#{name}_blob"]
end
children_fields = associations.map do |child_name|
child_association = parent.abstract_model.associations.detect { |p| p.name.to_sym == child_name }
diff --git a/lib/rails_admin/config/fields/factories/carrierwave.rb b/lib/rails_admin/config/fields/factories/carrierwave.rb
index c9295f9a78..af1a659f3d 100644
--- a/lib/rails_admin/config/fields/factories/carrierwave.rb
+++ b/lib/rails_admin/config/fields/factories/carrierwave.rb
@@ -7,7 +7,7 @@
RailsAdmin::Config::Fields.register_factory do |parent, properties, fields|
model = parent.abstract_model.model
if defined?(::CarrierWave) && model.is_a?(CarrierWave::Mount) && model.uploaders.include?(attachment_name = properties.name.to_s.chomp('_file_name').to_sym)
- columns = [model.uploader_options[attachment_name][:mount_on] || attachment_name, "#{attachment_name}_content_type".to_sym, "#{attachment_name}_file_size".to_sym]
+ columns = [model.uploader_options[attachment_name][:mount_on] || attachment_name, :"#{attachment_name}_content_type", :"#{attachment_name}_file_size"]
field = RailsAdmin::Config::Fields::Types.load(
%i[serialized json].include?(properties.type) ? :multiple_carrierwave : :carrierwave,
).new(parent, attachment_name, properties)
diff --git a/lib/rails_admin/config/fields/factories/dragonfly.rb b/lib/rails_admin/config/fields/factories/dragonfly.rb
index 2fcd5b57bb..b3e400c3ba 100644
--- a/lib/rails_admin/config/fields/factories/dragonfly.rb
+++ b/lib/rails_admin/config/fields/factories/dragonfly.rb
@@ -10,7 +10,7 @@
field = RailsAdmin::Config::Fields::Types.load(:dragonfly).new(parent, attachment_name, properties)
children_fields = []
extensions.each do |ext|
- children_column_name = "#{attachment_name}_#{ext}".to_sym
+ children_column_name = :"#{attachment_name}_#{ext}"
child_properties = parent.abstract_model.properties.detect { |p| p.name.to_s == children_column_name.to_s }
next unless child_properties
diff --git a/lib/rails_admin/config/fields/factories/paperclip.rb b/lib/rails_admin/config/fields/factories/paperclip.rb
index 9f95d4b09b..623be41b8c 100644
--- a/lib/rails_admin/config/fields/factories/paperclip.rb
+++ b/lib/rails_admin/config/fields/factories/paperclip.rb
@@ -11,7 +11,7 @@
field = RailsAdmin::Config::Fields::Types.load(:paperclip).new(parent, attachment_name, properties)
children_fields = []
extensions.each do |ext|
- children_column_name = "#{attachment_name}_#{ext}".to_sym
+ children_column_name = :"#{attachment_name}_#{ext}"
child_properties = parent.abstract_model.properties.detect { |p| p.name.to_s == children_column_name.to_s }
next unless child_properties
diff --git a/lib/rails_admin/config/fields/factories/shrine.rb b/lib/rails_admin/config/fields/factories/shrine.rb
index 6ed86c675a..69febad6c3 100644
--- a/lib/rails_admin/config/fields/factories/shrine.rb
+++ b/lib/rails_admin/config/fields/factories/shrine.rb
@@ -16,7 +16,7 @@
field = RailsAdmin::Config::Fields::Types.load(:shrine).new(parent, attachment_name, properties)
fields << field
- data_field_name = "#{attachment_name}_data".to_sym
+ data_field_name = :"#{attachment_name}_data"
child_properties = parent.abstract_model.properties.detect { |p| p.name == data_field_name }
next true unless child_properties
diff --git a/lib/rails_admin/config/fields/singular_association.rb b/lib/rails_admin/config/fields/singular_association.rb
new file mode 100644
index 0000000000..44e5654a17
--- /dev/null
+++ b/lib/rails_admin/config/fields/singular_association.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'rails_admin/config/fields/association'
+
+module RailsAdmin
+ module Config
+ module Fields
+ class SingularAssociation < Association
+ register_instance_option :filter_operators do
+ %w[_discard like not_like is starts_with ends_with] + (required? ? [] : %w[_separator _present _blank])
+ end
+
+ register_instance_option :formatted_value do
+ (o = value) && o.send(associated_model_config.object_label_method)
+ end
+
+ register_instance_option :partial do
+ nested_form ? :form_nested_one : :form_filtering_select
+ end
+
+ def collection(scope = nil)
+ if associated_collection_cache_all || scope
+ super
+ else
+ [[formatted_value, selected_id]]
+ end
+ end
+
+ def multiple?
+ false
+ end
+
+ def selected_id
+ raise NoMethodError # abstract
+ end
+
+ def parse_input(params)
+ return unless nested_form && params[method_name].try(:[], :id).present?
+
+ ids = associated_model_config.abstract_model.parse_id(params[method_name][:id])
+ ids = ids.to_composite_keys.to_s if ids.respond_to?(:to_composite_keys)
+ params[method_name][:id] = ids
+ end
+
+ def form_value
+ form_default_value.nil? ? selected_id : form_default_value
+ end
+
+ def widget_options
+ {
+ xhr: !associated_collection_cache_all,
+ remote_source: bindings[:view].index_path(associated_model_config.abstract_model, source_object_id: abstract_model.format_id(bindings[:object].id), source_abstract_model: abstract_model.to_param, associated_collection: name, current_action: bindings[:view].current_action, compact: true),
+ scopeBy: dynamic_scope_relationships,
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rails_admin/config/fields/types/active_storage.rb b/lib/rails_admin/config/fields/types/active_storage.rb
index 415750a5ed..20fe7b472f 100644
--- a/lib/rails_admin/config/fields/types/active_storage.rb
+++ b/lib/rails_admin/config/fields/types/active_storage.rb
@@ -18,10 +18,7 @@ class ActiveStorage < RailsAdmin::Config::Fields::Types::FileUpload
end
register_instance_option :image? do
- if value
- mime_type = Mime::Type.lookup_by_extension(value.filename.extension_without_delimiter)
- mime_type.to_s.match?(/^image/)
- end
+ value && (value.representable? || value.content_type.match?(/^image/))
end
register_instance_option :eager_load do
@@ -40,14 +37,22 @@ class ActiveStorage < RailsAdmin::Config::Fields::Types::FileUpload
)
end
+ register_instance_option :searchable do
+ false
+ end
+
+ register_instance_option :sortable do
+ false
+ end
+
def resource_url(thumb = false)
return nil unless value
- if thumb && value.variable?
+ if thumb && value.representable?
thumb = thumb_method if thumb == true
- variant = value.variant(thumb)
+ representation = value.representation(thumb)
Rails.application.routes.url_helpers.rails_blob_representation_path(
- variant.blob.signed_id, variant.variation.key, variant.blob.filename, only_path: true
+ representation.blob.signed_id, representation.variation.key, representation.blob.filename, only_path: true
)
else
Rails.application.routes.url_helpers.rails_blob_path(value, only_path: true)
diff --git a/lib/rails_admin/config/fields/types/all.rb b/lib/rails_admin/config/fields/types/all.rb
index b9c1aebdad..0d66d3829a 100644
--- a/lib/rails_admin/config/fields/types/all.rb
+++ b/lib/rails_admin/config/fields/types/all.rb
@@ -6,7 +6,6 @@
require 'rails_admin/config/fields/types/belongs_to_association'
require 'rails_admin/config/fields/types/boolean'
require 'rails_admin/config/fields/types/bson_object_id'
-require 'rails_admin/config/fields/types/composite_keys_belongs_to_association'
require 'rails_admin/config/fields/types/date'
require 'rails_admin/config/fields/types/datetime'
require 'rails_admin/config/fields/types/decimal'
diff --git a/lib/rails_admin/config/fields/types/belongs_to_association.rb b/lib/rails_admin/config/fields/types/belongs_to_association.rb
index 88f91474d2..d74034542d 100644
--- a/lib/rails_admin/config/fields/types/belongs_to_association.rb
+++ b/lib/rails_admin/config/fields/types/belongs_to_association.rb
@@ -1,22 +1,14 @@
# frozen_string_literal: true
-require 'rails_admin/config/fields/association'
+require 'rails_admin/config/fields/singular_association'
module RailsAdmin
module Config
module Fields
module Types
- class BelongsToAssociation < RailsAdmin::Config::Fields::Association
+ class BelongsToAssociation < RailsAdmin::Config::Fields::SingularAssociation
RailsAdmin::Config::Fields::Types.register(self)
- register_instance_option :filter_operators do
- %w[_discard like not_like is starts_with ends_with] + (required? ? [] : %w[_separator _present _blank])
- end
-
- register_instance_option :formatted_value do
- (o = value) && o.send(associated_model_config.object_label_method)
- end
-
register_instance_option :sortable do
@sortable ||= abstract_model.adapter_supports_joins? && associated_model_config.abstract_model.properties.collect(&:name).include?(associated_model_config.object_label_method) ? associated_model_config.object_label_method : {abstract_model.table_name => method_name}
end
@@ -25,24 +17,29 @@ class BelongsToAssociation < RailsAdmin::Config::Fields::Association
@searchable ||= associated_model_config.abstract_model.properties.collect(&:name).include?(associated_model_config.object_label_method) ? [associated_model_config.object_label_method, {abstract_model.model => method_name}] : {abstract_model.model => method_name}
end
- register_instance_option :partial do
- nested_form ? :form_nested_one : :form_filtering_select
- end
-
register_instance_option :eager_load do
true
end
- def selected_id
- bindings[:object].safe_send(association.key_accessor)
+ register_instance_option :allowed_methods do
+ nested_form ? [method_name] : Array(association.foreign_key)
end
- def method_name
- nested_form ? "#{name}_attributes".to_sym : super
+ def selected_id
+ if association.foreign_key.is_a?(Array)
+ format_key(association.foreign_key.map { |attribute| bindings[:object].safe_send(attribute) })
+ else
+ bindings[:object].safe_send(association.key_accessor)
+ end
end
- def multiple?
- false
+ def parse_input(params)
+ return super if nested_form
+ return unless params[method_name].present? && association.foreign_key.is_a?(Array)
+
+ association.foreign_key.zip(RailsAdmin.config.composite_keys_serializer.deserialize(params.delete(method_name))).each do |key, value|
+ params[key] = value
+ end
end
end
end
diff --git a/lib/rails_admin/config/fields/types/composite_keys_belongs_to_association.rb b/lib/rails_admin/config/fields/types/composite_keys_belongs_to_association.rb
deleted file mode 100644
index 1c1f85c134..0000000000
--- a/lib/rails_admin/config/fields/types/composite_keys_belongs_to_association.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_admin/config/fields/types/belongs_to_association'
-
-module RailsAdmin
- module Config
- module Fields
- module Types
- class CompositeKeysBelongsToAssociation < RailsAdmin::Config::Fields::Types::BelongsToAssociation
- RailsAdmin::Config::Fields::Types.register(self)
-
- register_instance_option :allowed_methods do
- nested_form ? [method_name] : Array(association.foreign_key)
- end
-
- def selected_id
- association.foreign_key.map { |attribute| bindings[:object].safe_send(attribute) }.to_composite_keys.to_s
- end
-
- def parse_input(params)
- return unless params[method_name].present? && association.foreign_key.is_a?(Array) && !nested_form
-
- association.foreign_key.zip(CompositePrimaryKeys::CompositeKeys.parse(params.delete(method_name))).each do |key, value|
- params[key] = value
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/rails_admin/config/fields/types/dragonfly.rb b/lib/rails_admin/config/fields/types/dragonfly.rb
index 3f4049742e..80f9fe5a4e 100644
--- a/lib/rails_admin/config/fields/types/dragonfly.rb
+++ b/lib/rails_admin/config/fields/types/dragonfly.rb
@@ -12,7 +12,6 @@ class Dragonfly < RailsAdmin::Config::Fields::Types::FileUpload
RailsAdmin::Config::Fields::Types.register(self)
register_instance_option :image? do
- false unless value
if abstract_model.model.new.respond_to?("#{name}_name")
mime_type = Mime::Type.lookup_by_extension(bindings[:object].send("#{name}_name").to_s.split('.').last)
mime_type.to_s.match?(/^image/)
diff --git a/lib/rails_admin/config/fields/types/file_upload.rb b/lib/rails_admin/config/fields/types/file_upload.rb
index b3ffe52e9a..c3f96fa4e9 100644
--- a/lib/rails_admin/config/fields/types/file_upload.rb
+++ b/lib/rails_admin/config/fields/types/file_upload.rb
@@ -52,7 +52,7 @@ class FileUpload < RailsAdmin::Config::Fields::Base
end
register_instance_option :image? do
- mime_type = Mime::Type.lookup_by_extension(resource_url.to_s.split('.').last)
+ mime_type = Mime::Type.lookup_by_extension(extension)
mime_type.to_s.match?(/^image/)
end
@@ -66,6 +66,12 @@ class FileUpload < RailsAdmin::Config::Fields::Base
}
end
+ def extension
+ URI.parse(resource_url).path.split('.').last
+ rescue URI::InvalidURIError
+ nil
+ end
+
# virtual class
def resource_url
raise 'not implemented'
diff --git a/lib/rails_admin/config/fields/types/has_and_belongs_to_many_association.rb b/lib/rails_admin/config/fields/types/has_and_belongs_to_many_association.rb
index cb897c5653..1f63c2cf75 100644
--- a/lib/rails_admin/config/fields/types/has_and_belongs_to_many_association.rb
+++ b/lib/rails_admin/config/fields/types/has_and_belongs_to_many_association.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-require 'rails_admin/config/fields/types/has_many_association'
+require 'rails_admin/config/fields/collection_association'
module RailsAdmin
module Config
module Fields
module Types
- class HasAndBelongsToManyAssociation < RailsAdmin::Config::Fields::Types::HasManyAssociation
+ class HasAndBelongsToManyAssociation < RailsAdmin::Config::Fields::CollectionAssociation
# Register field type for the type loader
RailsAdmin::Config::Fields::Types.register(self)
end
diff --git a/lib/rails_admin/config/fields/types/has_many_association.rb b/lib/rails_admin/config/fields/types/has_many_association.rb
index 93a289f8a0..dd5609d100 100644
--- a/lib/rails_admin/config/fields/types/has_many_association.rb
+++ b/lib/rails_admin/config/fields/types/has_many_association.rb
@@ -1,36 +1,14 @@
# frozen_string_literal: true
-require 'rails_admin/config/fields/association'
+require 'rails_admin/config/fields/collection_association'
module RailsAdmin
module Config
module Fields
module Types
- class HasManyAssociation < RailsAdmin::Config::Fields::Association
+ class HasManyAssociation < RailsAdmin::Config::Fields::CollectionAssociation
# Register field type for the type loader
RailsAdmin::Config::Fields::Types.register(self)
-
- register_instance_option :partial do
- nested_form ? :form_nested_many : :form_filtering_multiselect
- end
-
- # orderable associated objects
- register_instance_option :orderable do
- false
- end
-
- def method_name
- nested_form ? "#{name}_attributes".to_sym : super
- end
-
- # Reader for validation errors of the bound object
- def errors
- bindings[:object].errors[name]
- end
-
- def associated_prepopulate_params
- {associated_model_config.abstract_model.param_key => {association.foreign_key => bindings[:object].try(:id)}}
- end
end
end
end
diff --git a/lib/rails_admin/config/fields/types/has_one_association.rb b/lib/rails_admin/config/fields/types/has_one_association.rb
index f6219a7fd3..ba1b48999b 100644
--- a/lib/rails_admin/config/fields/types/has_one_association.rb
+++ b/lib/rails_admin/config/fields/types/has_one_association.rb
@@ -1,42 +1,32 @@
# frozen_string_literal: true
-require 'rails_admin/config/fields/association'
+require 'rails_admin/config/fields/singular_association'
module RailsAdmin
module Config
module Fields
module Types
- class HasOneAssociation < RailsAdmin::Config::Fields::Association
+ class HasOneAssociation < RailsAdmin::Config::Fields::SingularAssociation
# Register field type for the type loader
RailsAdmin::Config::Fields::Types.register(self)
- register_instance_option :filter_operators do
- %w[_discard like not_like is starts_with ends_with] + (required? ? [] : %w[_separator _present _blank])
+ register_instance_option :allowed_methods do
+ nested_form ? [method_name] : [name]
end
- register_instance_option :partial do
- nested_form ? :form_nested_one : :form_filtering_select
- end
-
- # Accessor for field's formatted value
- register_instance_option :formatted_value do
- (o = value) && o.send(associated_model_config.object_label_method)
- end
-
- def selected_id
- value.try(:id).try(:to_s)
+ def associated_prepopulate_params
+ {associated_model_config.abstract_model.param_key => {association.foreign_key => bindings[:object].try(:id)}}
end
- def method_name
- nested_form ? "#{name}_attributes".to_sym : super
- end
+ def parse_input(params)
+ return super if nested_form
- def multiple?
- false
+ id = params.delete(method_name)
+ params[name] = associated_model_config.abstract_model.get(id) if id
end
- def associated_prepopulate_params
- {associated_model_config.abstract_model.param_key => {association.foreign_key => bindings[:object].try(:id)}}
+ def selected_id
+ format_key(value.try(:id)).try(:to_s)
end
end
end
diff --git a/lib/rails_admin/config/fields/types/multiple_active_storage.rb b/lib/rails_admin/config/fields/types/multiple_active_storage.rb
index 2687b1f440..43d163e029 100644
--- a/lib/rails_admin/config/fields/types/multiple_active_storage.rb
+++ b/lib/rails_admin/config/fields/types/multiple_active_storage.rb
@@ -23,19 +23,16 @@ class ActiveStorageAttachment < RailsAdmin::Config::Fields::Types::MultipleFileU
end
register_instance_option :image? do
- if value
- mime_type = Mime::Type.lookup_by_extension(value.filename.extension_without_delimiter)
- mime_type.to_s.match?(/^image/)
- end
+ value && (value.representable? || value.content_type.match?(/^image/))
end
def resource_url(thumb = false)
return nil unless value
- if thumb && value.variable?
- variant = value.variant(thumb_method)
+ if thumb && value.representable?
+ representation = value.representation(thumb_method)
Rails.application.routes.url_helpers.rails_blob_representation_path(
- variant.blob.signed_id, variant.variation.key, variant.blob.filename, only_path: true
+ representation.blob.signed_id, representation.variation.key, representation.blob.filename, only_path: true
)
else
Rails.application.routes.url_helpers.rails_blob_path(value, only_path: true)
@@ -48,7 +45,7 @@ def resource_url(thumb = false)
end
register_instance_option :keep_method do
- method_name if ::ActiveStorage.replace_on_assign_to_many
+ method_name if ::ActiveStorage.gem_version >= Gem::Version.new('7.1') || ::ActiveStorage.replace_on_assign_to_many
end
register_instance_option :delete_method do
@@ -70,6 +67,14 @@ def resource_url(thumb = false)
direct? && {data: {direct_upload_url: bindings[:view].main_app.rails_direct_uploads_url}} || {},
)
end
+
+ register_instance_option :searchable do
+ false
+ end
+
+ register_instance_option :sortable do
+ false
+ end
end
end
end
diff --git a/lib/rails_admin/config/fields/types/multiple_file_upload.rb b/lib/rails_admin/config/fields/types/multiple_file_upload.rb
index 3e6193e5a6..05f6afaa9f 100644
--- a/lib/rails_admin/config/fields/types/multiple_file_upload.rb
+++ b/lib/rails_admin/config/fields/types/multiple_file_upload.rb
@@ -47,13 +47,19 @@ def initialize(value)
end
register_instance_option :image? do
- mime_type = Mime::Type.lookup_by_extension(resource_url.to_s.split('.').last)
+ mime_type = Mime::Type.lookup_by_extension(extension)
mime_type.to_s.match?(/^image/)
end
def resource_url(_thumb = false)
raise 'not implemented'
end
+
+ def extension
+ URI.parse(resource_url).path.split('.').last
+ rescue URI::InvalidURIError
+ nil
+ end
end
def initialize(*args)
diff --git a/lib/rails_admin/config/fields/types/polymorphic_association.rb b/lib/rails_admin/config/fields/types/polymorphic_association.rb
index 327053f51a..7632df92f9 100644
--- a/lib/rails_admin/config/fields/types/polymorphic_association.rb
+++ b/lib/rails_admin/config/fields/types/polymorphic_association.rb
@@ -51,26 +51,29 @@ class PolymorphicAssociation < RailsAdmin::Config::Fields::Types::BelongsToAssoc
false
end
- def associated_collection(type)
- return [] if type.blank?
+ def associated_model_config
+ @associated_model_config ||= association.klass.collect { |type| RailsAdmin.config(type) }.reject(&:excluded?)
+ end
- config = RailsAdmin.config(type)
- config.abstract_model.all.collect do |object|
- [object.send(config.object_label_method), object.id]
+ def collection(_scope = nil)
+ if value
+ [[formatted_value, selected_id]]
+ else
+ [[]]
end
end
- def associated_model_config
- @associated_model_config ||= association.klass.collect { |type| RailsAdmin.config(type) }.reject(&:excluded?)
+ def type_column
+ association.foreign_type.to_s
end
- def polymorphic_type_collection
+ def type_collection
associated_model_config.collect do |config|
[config.label, config.abstract_model.model.name]
end
end
- def polymorphic_type_urls
+ def type_urls
types = associated_model_config.collect do |config|
[config.abstract_model.model.name, config.abstract_model.to_param]
end
@@ -82,6 +85,26 @@ def value
bindings[:object].send(association.name)
end
+ def widget_options_for_types
+ type_collection.inject({}) do |options, model|
+ options.merge(
+ model.second.downcase.gsub('::', '-') => {
+ xhr: true,
+ remote_source: bindings[:view].index_path(model.second.underscore, source_object_id: bindings[:object].id, source_abstract_model: abstract_model.to_param, current_action: bindings[:view].current_action, compact: true),
+ float_left: false,
+ },
+ )
+ end
+ end
+
+ def widget_options
+ widget_options_for_types[selected_type.try(:downcase)] || {float_left: false}
+ end
+
+ def selected_type
+ bindings[:object].send(type_column)
+ end
+
def parse_input(params)
if (type_value = params[association.foreign_type.to_sym]).present?
config = associated_model_config.find { |c| type_value == c.abstract_model.model.name }
diff --git a/lib/rails_admin/engine.rb b/lib/rails_admin/engine.rb
index f61a0c2ecf..3995e4f51d 100644
--- a/lib/rails_admin/engine.rb
+++ b/lib/rails_admin/engine.rb
@@ -4,6 +4,7 @@
require 'nested_form'
require 'rails'
require 'rails_admin'
+require 'rails_admin/extensions/url_for_extension'
require 'rails_admin/version'
require 'turbo-rails'
@@ -15,6 +16,10 @@ class Engine < Rails::Engine
config.action_dispatch.rescue_responses['RailsAdmin::ActionNotAllowed'] = :forbidden
+ initializer 'RailsAdmin load UrlForExtension' do
+ RailsAdmin::Engine.routes.singleton_class.prepend(RailsAdmin::Extensions::UrlForExtension)
+ end
+
initializer 'RailsAdmin reload config in development' do |app|
config.initializer_path = app.root.join('config/initializers/rails_admin.rb')
diff --git a/lib/rails_admin/extensions/paper_trail/auditing_adapter.rb b/lib/rails_admin/extensions/paper_trail/auditing_adapter.rb
index 635063cee5..a9834aa616 100644
--- a/lib/rails_admin/extensions/paper_trail/auditing_adapter.rb
+++ b/lib/rails_admin/extensions/paper_trail/auditing_adapter.rb
@@ -146,7 +146,7 @@ def listing_for_model_or_object(model, object, query, sort, sort_reverse, all, p
versions = object.nil? ? versions_for_model(model) : object.public_send(model.model.versions_association_name)
versions = versions.where('event LIKE ?', "%#{query}%") if query.present?
versions = versions.order(sort)
- versions = all ? versions : versions.send(Kaminari.config.page_method_name, current_page).per(per_page)
+ versions = versions.send(Kaminari.config.page_method_name, current_page).per(per_page) unless all
paginated_proxies = Kaminari.paginate_array([], total_count: versions.try(:total_count) || versions.count)
paginated_proxies = paginated_proxies.send(
paginated_proxies.respond_to?(Kaminari.config.page_method_name) ? Kaminari.config.page_method_name : :page,
diff --git a/lib/rails_admin/extensions/url_for_extension.rb b/lib/rails_admin/extensions/url_for_extension.rb
new file mode 100644
index 0000000000..8328b2dda1
--- /dev/null
+++ b/lib/rails_admin/extensions/url_for_extension.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module RailsAdmin
+ module Extensions
+ module UrlForExtension
+ def url_for(options, *args)
+ case options[:id]
+ when Array
+ options[:id] = RailsAdmin.config.composite_keys_serializer.serialize(options[:id])
+ end
+ super options, *args
+ end
+ end
+ end
+end
diff --git a/lib/rails_admin/support/composite_keys_serializer.rb b/lib/rails_admin/support/composite_keys_serializer.rb
new file mode 100644
index 0000000000..3d79e0eb9e
--- /dev/null
+++ b/lib/rails_admin/support/composite_keys_serializer.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module RailsAdmin
+ module Support
+ module CompositeKeysSerializer
+ def self.serialize(keys)
+ keys.map { |key| key&.to_s&.gsub('_', '__') }.join('_')
+ end
+
+ def self.deserialize(string)
+ string.split('_').map { |key| key&.gsub('__', '_') }
+ end
+ end
+ end
+end
diff --git a/lib/rails_admin/support/datetime.rb b/lib/rails_admin/support/datetime.rb
index 87cd7703ac..39345b9d16 100644
--- a/lib/rails_admin/support/datetime.rb
+++ b/lib/rails_admin/support/datetime.rb
@@ -21,6 +21,7 @@ class Datetime
'%-I' => 'h', # Hour of the day, 12-hour clock (1..12)
'%k' => 'H', # Hour of the day, 24-hour clock (0..23)
'%l' => 'h', # Hour of the day, 12-hour clock (1..12)
+ '%-l' => 'h', # Hour of the day, 12-hour clock (1..12)
'%M' => 'i', # Minute of the hour (00..59)
'%-M' => 'i', # Minute of the hour (00..59)
'%m' => 'm', # Month of the year (01..12)
diff --git a/lib/rails_admin/version.rb b/lib/rails_admin/version.rb
index d48c0729ed..6d0a8c5ced 100644
--- a/lib/rails_admin/version.rb
+++ b/lib/rails_admin/version.rb
@@ -3,8 +3,8 @@
module RailsAdmin
class Version
MAJOR = 3
- MINOR = 1
- PATCH = 1
+ MINOR = 3
+ PATCH = 0
PRE = nil
class << self
diff --git a/package.json b/package.json
index 9c2487dcdc..d60008df53 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rails_admin",
- "version": "3.1.1",
+ "version": "3.3.0",
"description": "RailsAdmin is a Rails engine that provides an easy-to-use interface for managing your data.",
"homepage": "https://github.com/railsadminteam/rails_admin",
"license": "MIT",
@@ -22,7 +22,7 @@
"bootstrap": "^5.1.3",
"flatpickr": "^4.6.9",
"jquery": "^3.6.0",
- "jquery-ui": "^1.12.1"
+ "jquery-ui": "^1.12.1 <1.14.0"
},
"devDependencies": {
"prettier": "^2.4.1"
diff --git a/rails_admin.gemspec b/rails_admin.gemspec
index 18fc3166ab..27bc72b3dc 100644
--- a/rails_admin.gemspec
+++ b/rails_admin.gemspec
@@ -1,16 +1,15 @@
# frozen_string_literal: true
-lib = File.expand_path('lib', __dir__)
-$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
-require 'rails_admin/version'
+require_relative 'lib/rails_admin/version'
Gem::Specification.new do |spec|
# If you add a dependency, please maintain alphabetical order
spec.add_dependency 'activemodel-serializers-xml', '>= 1.0'
+ spec.add_dependency 'csv'
spec.add_dependency 'kaminari', '>= 0.14', '< 2.0'
spec.add_dependency 'nested_form', '~> 0.3'
- spec.add_dependency 'rails', ['>= 6.0', '< 8']
- spec.add_dependency 'turbo-rails', '~> 1.0'
+ spec.add_dependency 'rails', ['>= 6.0', '< 9']
+ spec.add_dependency 'turbo-rails', ['>= 1.0', '< 3']
spec.add_development_dependency 'bundler', '>= 1.0'
spec.authors = ['Erik Michaels-Ober', 'Bogdan Gaza', 'Petteri Kaapa', 'Benoit Benezech', 'Mitsuhiro Shibuya']
spec.description = 'RailsAdmin is a Rails engine that provides an easy-to-use interface for managing your data.'
@@ -24,12 +23,12 @@ Gem::Specification.new do |spec|
spec.required_rubygems_version = '>= 1.8.11'
spec.summary = 'Admin for Rails'
spec.version = RailsAdmin::Version
- spec.post_install_message = '
+ spec.post_install_message = <<~MSG
### Upgrading RailsAdmin from 2.x.x to 3.x.x ###
Due to introduction of Webpack/Webpacker support, some additional dependencies and configuration will be needed.
Running `bin/rails g rails_admin:install` will suggest required changes, based on the current setup of your app.
For a complete list of changes, see https://github.com/railsadminteam/rails_admin/blob/master/CHANGELOG.md
- '
+ MSG
end
diff --git a/spec/controllers/rails_admin/main_controller_spec.rb b/spec/controllers/rails_admin/main_controller_spec.rb
index 82d4f2cc28..c9f1b61c1f 100644
--- a/spec/controllers/rails_admin/main_controller_spec.rb
+++ b/spec/controllers/rails_admin/main_controller_spec.rb
@@ -65,22 +65,60 @@ def get(action, params)
end
end
+ context 'with a virtual field' do
+ before do
+ RailsAdmin.config('Player') do
+ base do
+ field :virtual do
+ sortable :name
+ end
+ end
+
+ list do
+ sort_by :virtual
+ end
+ end
+ end
+
+ it 'returns the query referenced in the sortable' do
+ expect(controller.send(:get_sort_hash, RailsAdmin.config(Player))).to match(sort: /["`]?players["`]?\.["`]?name["`]?/, sort_reverse: true)
+ end
+ end
+
it 'works with belongs_to associations with label method virtual' do
controller.params = {sort: 'parent_category', model_name: 'categories'}
expect(controller.send(:get_sort_hash, RailsAdmin.config(Category))).to eq(sort: 'categories.parent_category_id', sort_reverse: true)
end
- context 'using mongoid, not supporting joins', mongoid: true do
- it 'gives back the remote table with label name' do
+ context 'using mongoid', mongoid: true do
+ it 'gives back the remote table with label name, as it does not support joins' do
controller.params = {sort: 'team', model_name: 'players'}
- expect(controller.send(:get_sort_hash, RailsAdmin.config(Player))).to eq(sort: 'players.team_id', sort_reverse: true)
+ expect(controller.send(:get_sort_hash, RailsAdmin.config(Player))).to match(sort: 'players.team_id', sort_reverse: true)
end
end
- context 'using active_record, supporting joins', active_record: true do
+ context 'using active_record', active_record: true do
+ let(:connection_config) do
+ if ActiveRecord::Base.respond_to?(:connection_db_config)
+ ActiveRecord::Base.connection_db_config.configuration_hash
+ else
+ ActiveRecord::Base.connection_config
+ end
+ end
+
it 'gives back the local column' do
controller.params = {sort: 'team', model_name: 'players'}
- expect(controller.send(:get_sort_hash, RailsAdmin.config(Player))).to eq(sort: 'teams.name', sort_reverse: true)
+ expect(controller.send(:get_sort_hash, RailsAdmin.config(Player))).to match(sort: /^["`]teams["`]\.["`]name["`]$/, sort_reverse: true)
+ end
+
+ it 'quotes the table and column names it returns as :sort' do
+ controller.params = {sort: 'team', model_name: 'players'}
+ case connection_config[:adapter]
+ when 'mysql2'
+ expect(controller.send(:get_sort_hash, RailsAdmin.config(Player))[:sort]).to eq '`teams`.`name`'
+ else
+ expect(controller.send(:get_sort_hash, RailsAdmin.config(Player))[:sort]).to eq '"teams"."name"'
+ end
end
end
end
@@ -293,8 +331,8 @@ def get(action, params)
it "uses target model's primary key" do
@user = FactoryBot.create :managing_user
@team = FactoryBot.create :managed_team, user: @user
- get :index, model_name: 'managing_user', source_object_id: @team.id, source_abstract_model: 'managing_user', associated_collection: 'teams', current_action: :create, compact: true, format: :json
- expect(response.body).to match(/"id":"#{@user.id}"/)
+ get :index, model_name: 'managed_team', source_object_id: @user.id, source_abstract_model: 'managing_user', associated_collection: 'teams', current_action: :create, compact: true, format: :json
+ expect(response.body).to match(/"id":"#{@team.id}"/)
end
context 'as JSON' do
@@ -470,4 +508,70 @@ def get(action, params)
)
end
end
+
+ describe 'back_or_index' do
+ before do
+ allow(controller).to receive(:index_path).and_return(index_path)
+ end
+
+ let(:index_path) { '/' }
+
+ it 'returns back to index when return_to is not defined' do
+ controller.params = {}
+ expect(controller.send(:back_or_index)).to eq(index_path)
+ end
+
+ it 'returns back to return_to url when it starts with same protocol and host' do
+ return_to_url = "http://#{request.host}/teams"
+ controller.params = {return_to: return_to_url}
+ expect(controller.send(:back_or_index)).to eq(return_to_url)
+ end
+
+ it 'returns back to return_to url when it contains a path' do
+ return_to_url = '/teams'
+ controller.params = {return_to: return_to_url}
+ expect(controller.send(:back_or_index)).to eq(return_to_url)
+ end
+
+ it 'returns back to index path when return_to path does not start with slash' do
+ return_to_url = 'teams'
+ controller.params = {return_to: return_to_url}
+ expect(controller.send(:back_or_index)).to eq(index_path)
+ end
+
+ it 'returns back to index path when return_to url does not start with full protocol' do
+ return_to_url = "#{request.host}/teams"
+ controller.params = {return_to: return_to_url}
+ expect(controller.send(:back_or_index)).to eq(index_path)
+ end
+
+ it 'returns back to index path when return_to url starts with double slash' do
+ return_to_url = "//#{request.host}/teams"
+ controller.params = {return_to: return_to_url}
+ expect(controller.send(:back_or_index)).to eq(index_path)
+ end
+
+ it 'returns back to index path when return_to url starts with triple slash' do
+ return_to_url = "///#{request.host}/teams"
+ controller.params = {return_to: return_to_url}
+ expect(controller.send(:back_or_index)).to eq(index_path)
+ end
+
+ it 'returns back to index path when return_to url does not have host' do
+ return_to_url = 'http:///teams'
+ controller.params = {return_to: return_to_url}
+ expect(controller.send(:back_or_index)).to eq(index_path)
+ end
+
+ it 'returns back to index path when return_to url starts with different protocol' do
+ return_to_url = "other://#{request.host}/teams"
+ controller.params = {return_to: return_to_url}
+ expect(controller.send(:back_or_index)).to eq(index_path)
+ end
+
+ it 'returns back to index path when return_to does not start with the same protocol and host' do
+ controller.params = {return_to: "http://google.com?#{request.host}"}
+ expect(controller.send(:back_or_index)).to eq(index_path)
+ end
+ end
end
diff --git a/spec/dummy_app/.dockerignore b/spec/dummy_app/.dockerignore
new file mode 100644
index 0000000000..9dc7e6a200
--- /dev/null
+++ b/spec/dummy_app/.dockerignore
@@ -0,0 +1,39 @@
+# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
+
+# Ignore git directory.
+/.git/
+
+# Ignore bundler config.
+/.bundle
+
+# Ignore all default key files.
+/config/master.key
+/config/credentials/*.key
+
+# Ignore all environment files.
+/.env*
+!/.env.example
+
+# Ignore all logfiles and tempfiles.
+/log/*
+/tmp/*
+!/log/.keep
+!/tmp/.keep
+
+# Ignore pidfiles, but keep the directory.
+/tmp/pids/*
+!/tmp/pids/
+!/tmp/pids/.keep
+
+# Ignore storage (uploaded files in development and any SQLite databases).
+/storage/*
+!/storage/.keep
+/tmp/storage/*
+!/tmp/storage/
+!/tmp/storage/.keep
+
+# Ignore assets.
+/node_modules/
+/app/assets/builds/*
+!/app/assets/builds/.keep
+/public/assets
diff --git a/spec/dummy_app/.gitignore b/spec/dummy_app/.gitignore
index 5aaa2881a9..8a791fb421 100644
--- a/spec/dummy_app/.gitignore
+++ b/spec/dummy_app/.gitignore
@@ -19,8 +19,10 @@
/public/system
+/public/assets
/public/packs
/public/packs-test
+/public/vite*
/node_modules
/yarn-error.log
/yarn.lock
diff --git a/spec/dummy_app/Dockerfile b/spec/dummy_app/Dockerfile
new file mode 100644
index 0000000000..d7ab47d6d4
--- /dev/null
+++ b/spec/dummy_app/Dockerfile
@@ -0,0 +1,87 @@
+# syntax = docker/dockerfile:1
+
+# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
+ARG RUBY_VERSION=3.1.2
+FROM ruby:$RUBY_VERSION-slim as base
+
+LABEL fly_launch_runtime="rails"
+
+# Rails app lives here
+WORKDIR /rails
+
+# Set production environment
+ENV RAILS_ENV="production" \
+ BUNDLE_WITHOUT="development:test"
+
+# Update gems and bundler
+RUN gem update --system --no-document && \
+ gem install -N bundler
+
+# Install packages needed to install nodejs
+RUN apt-get update -qq && \
+ apt-get install --no-install-recommends -y curl && \
+ rm -rf /var/lib/apt/lists /var/cache/apt/archives
+
+# Install Node.js
+# ARG NODE_VERSION=18.16.0
+# ENV PATH=/usr/local/node/bin:$PATH
+# RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
+# /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
+# rm -rf /tmp/node-build-master
+
+# Throw-away build stage to reduce size of final image
+FROM base as build
+
+# Install packages needed to build gems and node modules
+RUN apt-get update -qq && \
+ apt-get install --no-install-recommends -y build-essential default-libmysqlclient-dev git libpq-dev libvips node-gyp pkg-config python-is-python3
+
+# Build options
+ENV PATH="/usr/local/node/bin:$PATH"
+
+# Install application gems
+COPY --link Gemfile ./
+RUN sed -i "s/, path: '..\/..\/'//" Gemfile
+RUN bundle install && \
+ rm -rf ~/.bundle/ $BUNDLE_PATH/ruby/*/cache $BUNDLE_PATH/ruby/*/bundler/gems/*/.git
+
+# Install node modules
+# COPY --link package.json ./
+# RUN npm install
+
+# Copy application code
+COPY --link . .
+RUN sed -i "s/, path: '..\/..\/'//" Gemfile
+
+# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
+RUN sed -i "/link_tree ..\/..\/..\//d" app/assets/config/manifest.js
+RUN SECRET_KEY_BASE=DUMMY ./bin/rails assets:precompile
+
+
+# Final stage for app image
+FROM base
+
+# Install packages needed for deployment
+RUN apt-get update -qq && \
+ apt-get install --no-install-recommends -y curl default-mysql-client imagemagick libsqlite3-0 libvips postgresql-client && \
+ rm -rf /var/lib/apt/lists /var/cache/apt/archives
+
+# Copy built artifacts: gems, application
+COPY --from=build /usr/local/bundle /usr/local/bundle
+COPY --from=build /rails /rails
+
+# Run and own only the runtime files as a non-root user for security
+RUN useradd rails --create-home --shell /bin/bash && \
+ chown -R rails:rails db log tmp public/system public/uploads
+USER rails:rails
+
+# Deployment options
+ENV RAILS_LOG_TO_STDOUT="1" \
+ RAILS_SERVE_STATIC_FILES="true"
+
+# Entrypoint prepares the database.
+ENTRYPOINT ["/rails/bin/docker-entrypoint"]
+
+# Start the server by default, this can be overwritten at runtime
+EXPOSE 3000
+CMD ["./bin/rails", "server"]
diff --git a/spec/dummy_app/Gemfile b/spec/dummy_app/Gemfile
index 8080609f17..f8c3ec1df8 100644
--- a/spec/dummy_app/Gemfile
+++ b/spec/dummy_app/Gemfile
@@ -17,22 +17,22 @@ group :active_record do
gem 'sqlite3', '>= 1.3.0'
end
- gem 'composite_primary_keys'
gem 'paper_trail', '>= 12.0'
end
gem 'carrierwave', '>= 2.0.0.rc', '< 3.0'
-gem 'cssbundling-rails'
+gem 'cssbundling-rails', require: false
gem 'devise', '>= 3.2'
gem 'dragonfly', '~> 1.0'
gem 'importmap-rails', require: false
gem 'mini_magick', '>= 3.4'
-gem 'mlb', '>= 0.7', github: 'mshibuya/mlb', branch: 'ruby-3'
+gem 'mlb', '>= 0.7'
gem 'paperclip', '>= 3.4'
gem 'rails_admin', path: '../../'
gem 'shrine', '~> 3.0'
+gem 'vite_rails', require: false
gem 'webpacker', require: false
-gem 'webrick', '~> 1.7'
+gem 'webrick'
# Gems used only for assets and not required
# in production environments by default.
diff --git a/spec/dummy_app/Gemfile.rails6 b/spec/dummy_app/Gemfile.rails6
deleted file mode 100644
index 8d6da179c5..0000000000
--- a/spec/dummy_app/Gemfile.rails6
+++ /dev/null
@@ -1,49 +0,0 @@
-source 'https://rubygems.org'
-
-gem 'rails', '>= 6.0.0'
-gem 'webpacker', require: false
-
-group :active_record do
- platforms :jruby do
- gem 'activerecord-jdbcmysql-adapter', '>= 1.2'
- gem 'activerecord-jdbcpostgresql-adapter', '>= 1.2'
- gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2'
- end
-
- platforms :ruby, :mswin, :mingw do
- gem 'mysql2', '>= 0.3.14'
- gem 'pg', '>= 0.14'
- gem 'sqlite3', '>= 1.3.0'
- end
-
- gem 'paper_trail', '>= 12.0'
-end
-
-group :mongoid do
- gem 'carrierwave-mongoid', '>= 0.6.3', require: 'carrierwave/mongoid'
- gem 'kaminari-mongoid'
- gem 'mongoid', ['>= 6.0', '< 8']
- gem 'mongoid-paperclip', '>= 0.0.8', require: 'mongoid_paperclip'
- gem 'shrine-mongoid', '~> 1.0'
-end
-
-gem 'carrierwave', '>= 2.0.0.rc', '< 3.0'
-gem 'devise', '>= 3.2'
-gem 'dragonfly', '~> 1.0'
-gem 'mini_magick', '>= 3.4'
-gem 'mlb', '>= 0.7', github: 'mshibuya/mlb', branch: 'ruby-3'
-gem 'paperclip', '>= 3.4'
-gem 'rails_admin', path: '../../'
-gem 'shrine', '~> 3.0'
-gem 'webrick', '~> 1.7'
-
-# Gems used only for assets and not required
-# in production environments by default.
-group :assets do
- gem 'sassc-rails', '~> 2.1'
-
- # See https://github.com/sstephenson/execjs#readme for more supported runtimes
- # gem 'therubyracer'
-
- gem 'uglifier', '>= 1.3'
-end
diff --git a/spec/dummy_app/Procfile.dev b/spec/dummy_app/Procfile.dev
index 0c4b7357b9..38f2b2dabb 100644
--- a/spec/dummy_app/Procfile.dev
+++ b/spec/dummy_app/Procfile.dev
@@ -1,2 +1,3 @@
web: bin/rails server -p 3000
css: yarn build:css --watch
+vite: bin/vite dev
diff --git a/spec/dummy_app/app/active_record/fan.rb b/spec/dummy_app/app/active_record/fan.rb
index 106d0ae341..bd6bbf0959 100644
--- a/spec/dummy_app/app/active_record/fan.rb
+++ b/spec/dummy_app/app/active_record/fan.rb
@@ -3,7 +3,7 @@
class Fan < ActiveRecord::Base
has_and_belongs_to_many :teams
- if defined?(CompositePrimaryKeys)
+ if ActiveRecord.gem_version >= Gem::Version.new('7.1') || defined?(CompositePrimaryKeys)
has_many :fanships, inverse_of: :fan
has_one :fanship, inverse_of: :fan
end
diff --git a/spec/dummy_app/app/active_record/fanship.rb b/spec/dummy_app/app/active_record/fanship.rb
index 5b97bcba19..2bac5f5372 100644
--- a/spec/dummy_app/app/active_record/fanship.rb
+++ b/spec/dummy_app/app/active_record/fanship.rb
@@ -1,12 +1,21 @@
# frozen_string_literal: true
-if defined?(CompositePrimaryKeys)
+if ActiveRecord.gem_version >= Gem::Version.new('7.1') || defined?(CompositePrimaryKeys)
class Fanship < ActiveRecord::Base
self.table_name = :fans_teams
- self.primary_keys = :fan_id, :team_id
+ if defined?(CompositePrimaryKeys)
+ self.primary_keys = :fan_id, :team_id
+ else
+ self.primary_key = :fan_id, :team_id
+ end
+ if defined?(CompositePrimaryKeys) || ActiveRecord.gem_version >= Gem::Version.new('7.2')
+ has_many :favorite_players, foreign_key: %i[fan_id team_id], inverse_of: :fanship
+ else
+ has_many :favorite_players, query_constraints: %i[fan_id team_id], inverse_of: :fanship
+ end
+
belongs_to :fan, inverse_of: :fanships, optional: true
belongs_to :team, optional: true
- has_many :favorite_players, foreign_key: %i[fan_id team_id], inverse_of: :fanship
end
else
class Fanship; end
diff --git a/spec/dummy_app/app/active_record/favorite_player.rb b/spec/dummy_app/app/active_record/favorite_player.rb
index de222659d3..b91e26ab6b 100644
--- a/spec/dummy_app/app/active_record/favorite_player.rb
+++ b/spec/dummy_app/app/active_record/favorite_player.rb
@@ -1,9 +1,18 @@
# frozen_string_literal: true
-if defined?(CompositePrimaryKeys)
+if ActiveRecord.gem_version >= Gem::Version.new('7.1') || defined?(CompositePrimaryKeys)
class FavoritePlayer < ActiveRecord::Base
- self.primary_keys = :fan_id, :team_id, :player_id
- belongs_to :fanship, foreign_key: %i[fan_id team_id], inverse_of: :favorite_players
+ if defined?(CompositePrimaryKeys)
+ self.primary_keys = :fan_id, :team_id, :player_id
+ else
+ self.primary_key = :fan_id, :team_id, :player_id
+ end
+ if defined?(CompositePrimaryKeys) || ActiveRecord.gem_version >= Gem::Version.new('7.2')
+ belongs_to :fanship, foreign_key: %i[fan_id team_id], inverse_of: :favorite_players
+ else
+ belongs_to :fanship, query_constraints: %i[fan_id team_id], inverse_of: :favorite_players
+ end
+
belongs_to :player
end
end
diff --git a/spec/dummy_app/app/active_record/field_test.rb b/spec/dummy_app/app/active_record/field_test.rb
index 8e4452c248..63b3c4b2db 100644
--- a/spec/dummy_app/app/active_record/field_test.rb
+++ b/spec/dummy_app/app/active_record/field_test.rb
@@ -18,7 +18,11 @@ class FieldTest < ActiveRecord::Base
mount_uploader :carrierwave_asset, CarrierwaveUploader
mount_uploaders :carrierwave_assets, CarrierwaveUploader
- serialize :carrierwave_assets, JSON
+ if ActiveRecord.gem_version < Gem::Version.new('7.1')
+ serialize :carrierwave_assets, JSON
+ else
+ serialize :carrierwave_assets, coder: JSON
+ end
if defined?(ActiveStorage)
has_one_attached :active_storage_asset
@@ -39,8 +43,13 @@ class FieldTest < ActiveRecord::Base
has_rich_text :action_text_field if defined?(ActionText)
- enum string_enum_field: {S: 's', M: 'm', L: 'l'}
- enum integer_enum_field: %i[small medium large]
+ if ActiveRecord.gem_version >= Gem::Version.new('7.0')
+ enum :string_enum_field, {S: 's', M: 'm', L: 'l'}
+ enum :integer_enum_field, %i[small medium large]
+ else
+ enum string_enum_field: {S: 's', M: 'm', L: 'l'}
+ enum integer_enum_field: %i[small medium large]
+ end
validates :string_field, exclusion: {in: ['Invalid']} # to test file upload caching
end
diff --git a/spec/dummy_app/app/active_record/managing_user.rb b/spec/dummy_app/app/active_record/managing_user.rb
index 0404289744..91baf676dc 100644
--- a/spec/dummy_app/app/active_record/managing_user.rb
+++ b/spec/dummy_app/app/active_record/managing_user.rb
@@ -3,8 +3,4 @@
class ManagingUser < User
has_one :team, class_name: 'ManagedTeam', foreign_key: :manager, primary_key: :email, inverse_of: :user
has_many :teams, class_name: 'ManagedTeam', foreign_key: :manager, primary_key: :email, inverse_of: :user
-
- def team_id=(id)
- self.team = ManagedTeam.find_by_id(id)
- end
end
diff --git a/spec/dummy_app/app/active_record/nested_fan.rb b/spec/dummy_app/app/active_record/nested_fan.rb
index 85dd66a63b..f49fc7f453 100644
--- a/spec/dummy_app/app/active_record/nested_fan.rb
+++ b/spec/dummy_app/app/active_record/nested_fan.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-if defined?(CompositePrimaryKeys)
+if ActiveRecord.gem_version >= Gem::Version.new('7.1') || defined?(CompositePrimaryKeys)
class NestedFan < Fan
accepts_nested_attributes_for :fanships
accepts_nested_attributes_for :fanship
diff --git a/spec/dummy_app/app/active_record/nested_favorite_player.rb b/spec/dummy_app/app/active_record/nested_favorite_player.rb
index a188ef63cb..a609b6646e 100644
--- a/spec/dummy_app/app/active_record/nested_favorite_player.rb
+++ b/spec/dummy_app/app/active_record/nested_favorite_player.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-if defined?(CompositePrimaryKeys)
+if ActiveRecord.gem_version >= Gem::Version.new('7.1') || defined?(CompositePrimaryKeys)
class NestedFavoritePlayer < FavoritePlayer
accepts_nested_attributes_for :fanship
end
diff --git a/spec/dummy_app/app/active_record/player.rb b/spec/dummy_app/app/active_record/player.rb
index 6d14ef79f1..e5024b93f7 100644
--- a/spec/dummy_app/app/active_record/player.rb
+++ b/spec/dummy_app/app/active_record/player.rb
@@ -12,7 +12,11 @@ class Player < ActiveRecord::Base
record.errors.add(:base, 'Player is cheating') if /on steroids/.match?(value.to_s)
end
- enum formation: {start: 'start', substitute: 'substitute'}
+ if ActiveRecord.gem_version >= Gem::Version.new('7.0')
+ enum :formation, {start: 'start', substitute: 'substitute'}
+ else
+ enum formation: {start: 'start', substitute: 'substitute'}
+ end
before_destroy :destroy_hook
diff --git a/spec/dummy_app/app/active_record/team.rb b/spec/dummy_app/app/active_record/team.rb
index f4cc9f727e..dd2c2dde30 100644
--- a/spec/dummy_app/app/active_record/team.rb
+++ b/spec/dummy_app/app/active_record/team.rb
@@ -14,7 +14,11 @@ class Team < ActiveRecord::Base
validates_numericality_of :revenue, allow_nil: true
belongs_to :division, optional: true
- enum main_sponsor: %i[no_sponsor food_factory transportation_company bank energy_producer]
+ if ActiveRecord.gem_version >= Gem::Version.new('7.0')
+ enum :main_sponsor, %i[no_sponsor food_factory transportation_company bank energy_producer]
+ else
+ enum main_sponsor: %i[no_sponsor food_factory transportation_company bank energy_producer]
+ end
def player_names_truncated
players.collect(&:name).join(', ')[0..32]
diff --git a/spec/dummy_app/app/active_record/user.rb b/spec/dummy_app/app/active_record/user.rb
index 83bd61cb43..b091038a86 100644
--- a/spec/dummy_app/app/active_record/user.rb
+++ b/spec/dummy_app/app/active_record/user.rb
@@ -5,7 +5,11 @@ class User < ActiveRecord::Base
# :token_authenticatable, :confirmable, :lockable and :timeoutable
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
- serialize :roles, Array
+ if ActiveRecord.gem_version < Gem::Version.new('7.1')
+ serialize :roles, Array
+ else
+ serialize :roles, coder: YAML, type: Array
+ end
# Add Paperclip support for avatars
has_attached_file :avatar, styles: {medium: '300x300>', thumb: '100x100>'}
diff --git a/spec/dummy_app/app/assets/javascripts/rails_admin/custom/ui.js b/spec/dummy_app/app/assets/javascripts/rails_admin/custom/ui.js
index fe0ab0e7e8..44506531d1 100644
--- a/spec/dummy_app/app/assets/javascripts/rails_admin/custom/ui.js
+++ b/spec/dummy_app/app/assets/javascripts/rails_admin/custom/ui.js
@@ -1,17 +1,9 @@
window.domReadyTriggered = [];
-document.addEventListener("rails_admin:dom_ready", function () {
- window.domReadyTriggered.push("plainjs/colon");
-});
-
document.addEventListener("rails_admin.dom_ready", function () {
window.domReadyTriggered.push("plainjs/dot");
});
-$(document).on("rails_admin:dom_ready", function () {
- window.domReadyTriggered.push("jquery/colon");
-});
-
$(document).on("rails_admin.dom_ready", function () {
window.domReadyTriggered.push("jquery/dot");
});
diff --git a/spec/dummy_app/app/frontend/entrypoints/application.js b/spec/dummy_app/app/frontend/entrypoints/application.js
new file mode 100644
index 0000000000..7bb62d5763
--- /dev/null
+++ b/spec/dummy_app/app/frontend/entrypoints/application.js
@@ -0,0 +1,2 @@
+import "@rails/ujs";
+import "@hotwired/turbo-rails";
diff --git a/spec/dummy_app/app/frontend/entrypoints/rails_admin.js b/spec/dummy_app/app/frontend/entrypoints/rails_admin.js
new file mode 100644
index 0000000000..08b5caf6e6
--- /dev/null
+++ b/spec/dummy_app/app/frontend/entrypoints/rails_admin.js
@@ -0,0 +1,17 @@
+import "~/stylesheets/rails_admin.scss";
+import "rails_admin/src/rails_admin/base";
+import "flatpickr/dist/l10n/fr.js";
+import "trix";
+import "@rails/actiontext";
+import * as ActiveStorage from "@rails/activestorage";
+ActiveStorage.start();
+
+window.domReadyTriggered = [];
+
+document.addEventListener("rails_admin.dom_ready", function () {
+ window.domReadyTriggered.push("plainjs/dot");
+});
+
+$(document).on("rails_admin.dom_ready", function () {
+ window.domReadyTriggered.push("jquery/dot");
+});
diff --git a/spec/dummy_app/app/frontend/stylesheets/rails_admin.scss b/spec/dummy_app/app/frontend/stylesheets/rails_admin.scss
new file mode 100644
index 0000000000..eb98071c79
--- /dev/null
+++ b/spec/dummy_app/app/frontend/stylesheets/rails_admin.scss
@@ -0,0 +1,2 @@
+$fa-font-path: "@fortawesome/fontawesome-free/webfonts";
+@import "rails_admin/src/rails_admin/styles/base";
diff --git a/spec/dummy_app/app/javascript/rails_admin.js b/spec/dummy_app/app/javascript/rails_admin.js
index 0c8cc48d1d..038a150f86 100644
--- a/spec/dummy_app/app/javascript/rails_admin.js
+++ b/spec/dummy_app/app/javascript/rails_admin.js
@@ -7,18 +7,10 @@ ActiveStorage.start();
window.domReadyTriggered = [];
-document.addEventListener("rails_admin:dom_ready", function () {
- window.domReadyTriggered.push("plainjs/colon");
-});
-
document.addEventListener("rails_admin.dom_ready", function () {
window.domReadyTriggered.push("plainjs/dot");
});
-$(document).on("rails_admin:dom_ready", function () {
- window.domReadyTriggered.push("jquery/colon");
-});
-
$(document).on("rails_admin.dom_ready", function () {
window.domReadyTriggered.push("jquery/dot");
});
diff --git a/spec/dummy_app/app/mongoid/field_test.rb b/spec/dummy_app/app/mongoid/field_test.rb
index 93201d7348..57834e2ee5 100644
--- a/spec/dummy_app/app/mongoid/field_test.rb
+++ b/spec/dummy_app/app/mongoid/field_test.rb
@@ -65,7 +65,7 @@ class FieldTest
mount_uploader :carrierwave_asset, CarrierwaveUploader
# carrierwave-mongoid does not support mount_uploaders yet:
# https://github.com/carrierwaveuploader/carrierwave-mongoid/issues/138
- mount_uploader :carrierwave_assets, CarrierwaveUploader
+ mount_uploaders :carrierwave_assets, CarrierwaveUploader
validates :short_text, length: {maximum: 255}
end
diff --git a/spec/dummy_app/app/mongoid/managing_user.rb b/spec/dummy_app/app/mongoid/managing_user.rb
index 06bf76f0dd..010d9862c8 100644
--- a/spec/dummy_app/app/mongoid/managing_user.rb
+++ b/spec/dummy_app/app/mongoid/managing_user.rb
@@ -5,8 +5,4 @@ class ManagingUser < User
has_many :teams, class_name: 'ManagedTeam', foreign_key: :manager, primary_key: :email, inverse_of: :user
has_and_belongs_to_many :players, foreign_key: :player_names, primary_key: :name, inverse_of: :nil
has_and_belongs_to_many :balls, primary_key: :color, inverse_of: :nil
-
- def team_id=(id)
- self.team = ManagedTeam.where(_id: id).first
- end
end
diff --git a/spec/dummy_app/app/views/layouts/application.html.erb b/spec/dummy_app/app/views/layouts/application.html.erb
index ddf532edd0..680c846ac5 100644
--- a/spec/dummy_app/app/views/layouts/application.html.erb
+++ b/spec/dummy_app/app/views/layouts/application.html.erb
@@ -7,6 +7,9 @@
<%= javascript_pack_tag "application" %>
<% when :importmap %>
<%= javascript_importmap_tags %>
+ <% when :vite %>
+ <%= vite_client_tag %>
+ <%= vite_javascript_tag 'application' %>
<% else %>
<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application", type: 'module' %>
diff --git a/spec/dummy_app/bin/bundle b/spec/dummy_app/bin/bundle
index 66e9889e8b..981e650b68 100755
--- a/spec/dummy_app/bin/bundle
+++ b/spec/dummy_app/bin/bundle
@@ -1,3 +1,114 @@
#!/usr/bin/env ruby
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
-load Gem.bin_path('bundler', 'bundle')
+# frozen_string_literal: true
+
+#
+# This file was generated by Bundler.
+#
+# The application 'bundle' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require "rubygems"
+
+m = Module.new do
+ module_function
+
+ def invoked_as_script?
+ File.expand_path($0) == File.expand_path(__FILE__)
+ end
+
+ def env_var_version
+ ENV["BUNDLER_VERSION"]
+ end
+
+ def cli_arg_version
+ return unless invoked_as_script? # don't want to hijack other binstubs
+ return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
+ bundler_version = nil
+ update_index = nil
+ ARGV.each_with_index do |a, i|
+ if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
+ bundler_version = a
+ end
+ next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
+ bundler_version = $1
+ update_index = i
+ end
+ bundler_version
+ end
+
+ def gemfile
+ gemfile = ENV["BUNDLE_GEMFILE"]
+ return gemfile if gemfile && !gemfile.empty?
+
+ File.expand_path("../Gemfile", __dir__)
+ end
+
+ def lockfile
+ lockfile =
+ case File.basename(gemfile)
+ when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
+ else "#{gemfile}.lock"
+ end
+ File.expand_path(lockfile)
+ end
+
+ def lockfile_version
+ return unless File.file?(lockfile)
+ lockfile_contents = File.read(lockfile)
+ return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
+ Regexp.last_match(1)
+ end
+
+ def bundler_requirement
+ @bundler_requirement ||=
+ env_var_version || cli_arg_version ||
+ bundler_requirement_for(lockfile_version)
+ end
+
+ def bundler_requirement_for(version)
+ return "#{Gem::Requirement.default}.a" unless version
+
+ bundler_gem_version = Gem::Version.new(version)
+
+ requirement = bundler_gem_version.approximate_recommendation
+
+ return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0")
+
+ requirement += ".a" if bundler_gem_version.prerelease?
+
+ requirement
+ end
+
+ def load_bundler!
+ ENV["BUNDLE_GEMFILE"] ||= gemfile
+
+ activate_bundler
+ end
+
+ def activate_bundler
+ gem_error = activation_error_handling do
+ gem "bundler", bundler_requirement
+ end
+ return if gem_error.nil?
+ require_error = activation_error_handling do
+ require "bundler/version"
+ end
+ return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
+ warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
+ exit 42
+ end
+
+ def activation_error_handling
+ yield
+ nil
+ rescue StandardError, LoadError => e
+ e
+ end
+end
+
+m.load_bundler!
+
+if m.invoked_as_script?
+ load Gem.bin_path("bundler", "bundle")
+end
diff --git a/spec/dummy_app/bin/docker-entrypoint b/spec/dummy_app/bin/docker-entrypoint
new file mode 100755
index 0000000000..183c9fa576
--- /dev/null
+++ b/spec/dummy_app/bin/docker-entrypoint
@@ -0,0 +1,8 @@
+#!/bin/bash -e
+
+# If running the rails server then create or migrate existing database
+if [ "${*}" == "./bin/rails server" ]; then
+ ./bin/rails db:create db:migrate db:seed
+fi
+
+exec "${@}"
diff --git a/spec/dummy_app/bin/vite b/spec/dummy_app/bin/vite
new file mode 100755
index 0000000000..7527d097eb
--- /dev/null
+++ b/spec/dummy_app/bin/vite
@@ -0,0 +1,27 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+#
+# This file was generated by Bundler.
+#
+# The application 'vite' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
+
+bundle_binstub = File.expand_path("bundle", __dir__)
+
+if File.file?(bundle_binstub)
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
+ load(bundle_binstub)
+ else
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
+Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
+ end
+end
+
+require "rubygems"
+require "bundler/setup"
+
+load Gem.bin_path("vite_ruby", "vite")
diff --git a/spec/dummy_app/config/application.rb b/spec/dummy_app/config/application.rb
index df869eced1..c07817cf9f 100644
--- a/spec/dummy_app/config/application.rb
+++ b/spec/dummy_app/config/application.rb
@@ -23,6 +23,8 @@
when :importmap
require 'sprockets/railtie'
require 'importmap-rails'
+when :vite
+ require 'vite_rails'
end
# Require the gems listed in Gemfile, including any gems
@@ -35,7 +37,7 @@ class Application < Rails::Application
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
config.load_defaults Rails.version[0, 3]
- config.eager_load_paths.reject! { |p| p =~ %r{/app/([^/]+)} && !%W[controllers jobs locales mailers #{CI_ORM}].include?(Regexp.last_match[1]) }
+ config.eager_load_paths = (config.try(:all_eager_load_paths) || config.eager_load_paths).reject { |p| p =~ %r{/app/([^/]+)} && !%W[controllers jobs locales mailers #{CI_ORM}].include?(Regexp.last_match[1]) }
config.eager_load_paths += %W[#{config.root}/app/eager_loaded]
config.autoload_paths += %W[#{config.root}/lib]
config.i18n.load_path += Dir[Rails.root.join('app', 'locales', '*.{rb,yml}').to_s]
diff --git a/spec/dummy_app/config/database.yml b/spec/dummy_app/config/database.yml
index 23110f4017..d75ec1f7e2 100644
--- a/spec/dummy_app/config/database.yml
+++ b/spec/dummy_app/config/database.yml
@@ -30,3 +30,7 @@ development:
test:
<<: *sqlite
database: db/test.sqlite3
+
+production:
+ <<: *sqlite
+ database: db/production.sqlite3
diff --git a/spec/dummy_app/config/dockerfile.yml b/spec/dummy_app/config/dockerfile.yml
new file mode 100644
index 0000000000..04f4fe9f77
--- /dev/null
+++ b/spec/dummy_app/config/dockerfile.yml
@@ -0,0 +1,6 @@
+# generated by dockerfile-rails
+---
+options:
+ label:
+ fly_launch_runtime: rails
+ sentry: false
diff --git a/spec/dummy_app/config/environments/production.rb b/spec/dummy_app/config/environments/production.rb
index 0c0370cf8b..e24a00b7e5 100644
--- a/spec/dummy_app/config/environments/production.rb
+++ b/spec/dummy_app/config/environments/production.rb
@@ -22,7 +22,7 @@
config.static_cache_control = 'public, max-age=31536000'
# Compress JavaScripts and CSS.
- config.assets.js_compressor = :uglifier
+ # config.assets.js_compressor = :uglifier
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
diff --git a/spec/dummy_app/config/environments/test.rb b/spec/dummy_app/config/environments/test.rb
index 2e2d07d4b4..03736ffa98 100644
--- a/spec/dummy_app/config/environments/test.rb
+++ b/spec/dummy_app/config/environments/test.rb
@@ -28,7 +28,7 @@
config.action_controller.perform_caching = false
# Raise exceptions instead of rendering exception templates.
- config.action_dispatch.show_exceptions = false
+ config.action_dispatch.show_exceptions = Rails.gem_version >= Gem::Version.new('7.1') ? :none : false
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
diff --git a/spec/dummy_app/config/initializers/application_controller_renderer.rb b/spec/dummy_app/config/initializers/application_controller_renderer.rb
index 315ac48a9a..6e2d5d2b87 100644
--- a/spec/dummy_app/config/initializers/application_controller_renderer.rb
+++ b/spec/dummy_app/config/initializers/application_controller_renderer.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
# Be sure to restart your server when you modify this file.
# ApplicationController.renderer.defaults.merge!(
diff --git a/spec/dummy_app/config/initializers/backtrace_silencers.rb b/spec/dummy_app/config/initializers/backtrace_silencers.rb
index d0f0d3b5df..4b63f2893d 100644
--- a/spec/dummy_app/config/initializers/backtrace_silencers.rb
+++ b/spec/dummy_app/config/initializers/backtrace_silencers.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
# Be sure to restart your server when you modify this file.
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
diff --git a/spec/dummy_app/config/initializers/cors.rb b/spec/dummy_app/config/initializers/cors.rb
index 5f68d44d3c..82eafe5ca4 100644
--- a/spec/dummy_app/config/initializers/cors.rb
+++ b/spec/dummy_app/config/initializers/cors.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
# Be sure to restart your server when you modify this file.
# Avoid CORS issues when API is called from the frontend app.
diff --git a/spec/dummy_app/config/initializers/dragonfly.rb b/spec/dummy_app/config/initializers/dragonfly.rb
index d290e31191..16209b474a 100644
--- a/spec/dummy_app/config/initializers/dragonfly.rb
+++ b/spec/dummy_app/config/initializers/dragonfly.rb
@@ -2,6 +2,9 @@
require 'dragonfly'
+# Logger
+Dragonfly.logger = Rails.logger
+
# Configure
Dragonfly.app.configure do
plugin :imagemagick
@@ -16,8 +19,5 @@
server_root: Rails.root.join('public'))
end
-# Logger
-Dragonfly.logger = Rails.logger
-
# Mount as middleware
Rails.application.middleware.use Dragonfly::Middleware
diff --git a/spec/dummy_app/config/initializers/inflections.rb b/spec/dummy_app/config/initializers/inflections.rb
index aa7435fbc9..dc84742212 100644
--- a/spec/dummy_app/config/initializers/inflections.rb
+++ b/spec/dummy_app/config/initializers/inflections.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
# Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format. Inflections
diff --git a/spec/dummy_app/config/initializers/mime_types.rb b/spec/dummy_app/config/initializers/mime_types.rb
index 6e1d16f027..be6fedc535 100644
--- a/spec/dummy_app/config/initializers/mime_types.rb
+++ b/spec/dummy_app/config/initializers/mime_types.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
# Be sure to restart your server when you modify this file.
# Add new mime types for use in respond_to blocks:
diff --git a/spec/dummy_app/config/initializers/rails_admin.rb b/spec/dummy_app/config/initializers/rails_admin.rb
index b8f793d0e3..9e8798c13b 100644
--- a/spec/dummy_app/config/initializers/rails_admin.rb
+++ b/spec/dummy_app/config/initializers/rails_admin.rb
@@ -6,4 +6,15 @@
include_all_fields
field :color, :hidden
end
+
+ if Rails.env.production?
+ # Live demo configuration
+ c.main_app_name = ['RailsAdmin', 'Live Demo']
+ c.included_models = %w[Comment Division Draft Fan FieldTest League NestedFieldTest Player Team User]
+ c.model 'FieldTest' do
+ configure :paperclip_asset do
+ visible false
+ end
+ end
+ end
end
diff --git a/spec/dummy_app/config/vite.json b/spec/dummy_app/config/vite.json
new file mode 100644
index 0000000000..227b1370f6
--- /dev/null
+++ b/spec/dummy_app/config/vite.json
@@ -0,0 +1,15 @@
+{
+ "all": {
+ "sourceCodeDir": "app/frontend",
+ "watchAdditionalPaths": ["../../src"]
+ },
+ "development": {
+ "autoBuild": true,
+ "publicOutputDir": "vite",
+ "port": 3036
+ },
+ "test": {
+ "autoBuild": false,
+ "publicOutputDir": "vite"
+ }
+}
diff --git a/spec/dummy_app/db/migrate/20240921171953_add_non_nullable_boolean_field.rb b/spec/dummy_app/db/migrate/20240921171953_add_non_nullable_boolean_field.rb
new file mode 100644
index 0000000000..c7f729d5a0
--- /dev/null
+++ b/spec/dummy_app/db/migrate/20240921171953_add_non_nullable_boolean_field.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddNonNullableBooleanField < ActiveRecord::Migration[6.0]
+ def change
+ add_column :field_tests, :non_nullable_boolean_field, :boolean, null: false, default: false
+ end
+end
diff --git a/spec/dummy_app/fly.toml b/spec/dummy_app/fly.toml
new file mode 100644
index 0000000000..36c8da793e
--- /dev/null
+++ b/spec/dummy_app/fly.toml
@@ -0,0 +1,22 @@
+# fly.toml app configuration file generated for rails-admin on 2023-08-06T18:22:31+09:00
+#
+# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
+#
+
+app = "rails-admin"
+primary_region = "iad"
+console_command = "/rails/bin/rails console"
+
+[build]
+
+[http_service]
+ internal_port = 3000
+ force_https = true
+ auto_stop_machines = true
+ auto_start_machines = true
+ min_machines_running = 0
+ processes = ["app"]
+
+[[statics]]
+ guest_path = "/rails/public"
+ url_prefix = "/"
diff --git a/spec/dummy_app/package.json b/spec/dummy_app/package.json
index 19d9c2c706..4c879b9192 100644
--- a/spec/dummy_app/package.json
+++ b/spec/dummy_app/package.json
@@ -3,15 +3,19 @@
"private": true,
"version": "0.1.0",
"dependencies": {
+ "@babel/plugin-proposal-private-methods": "^7.18.6",
+ "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@rails/actiontext": "^7.0.3-1",
"@rails/activestorage": "^7.0.3-1",
"@rails/webpacker": "5.4.3",
- "rails_admin": "file:../../",
+ "rails_admin": "file:../..",
"trix": "^2.0.0-beta.0",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12"
},
"devDependencies": {
+ "vite": "^5.0",
+ "vite-plugin-ruby": ">=5.0 <6",
"webpack-dev-server": "^3"
},
"scripts": {
diff --git a/spec/dummy_app/vite.config.ts b/spec/dummy_app/vite.config.ts
new file mode 100644
index 0000000000..ebd34ec97c
--- /dev/null
+++ b/spec/dummy_app/vite.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from "vite";
+import RubyPlugin from "vite-plugin-ruby";
+
+export default defineConfig({
+ plugins: [RubyPlugin()],
+});
diff --git a/spec/fixtures/test.txt b/spec/fixtures/test.txt
new file mode 100644
index 0000000000..30d74d2584
--- /dev/null
+++ b/spec/fixtures/test.txt
@@ -0,0 +1 @@
+test
\ No newline at end of file
diff --git a/spec/integration/actions/bulk_delete_spec.rb b/spec/integration/actions/bulk_delete_spec.rb
index 36a20df8b6..43a1af59bd 100644
--- a/spec/integration/actions/bulk_delete_spec.rb
+++ b/spec/integration/actions/bulk_delete_spec.rb
@@ -87,7 +87,7 @@
end
end
- context 'with composite_primary_keys', composite_primary_keys: true do
+ context 'with composite primary keys', composite_primary_keys: true do
let!(:fanships) { FactoryBot.create_list(:fanship, 3) }
it 'provides check boxes for bulk operation' do
@@ -96,7 +96,7 @@
end
it 'deletes selected records' do
- delete(bulk_delete_path(bulk_action: 'bulk_delete', model_name: 'fanship', bulk_ids: fanships[0..1].map { |fanship| fanship.id.to_s }))
+ delete(bulk_delete_path(bulk_action: 'bulk_delete', model_name: 'fanship', bulk_ids: fanships[0..1].map { |fanship| RailsAdmin::Support::CompositeKeysSerializer.serialize(fanship.id) }))
expect(flash[:success]).to match(/2 Fanships successfully deleted/)
expect(Fanship.all).to eq fanships[2..2]
end
diff --git a/spec/integration/actions/delete_spec.rb b/spec/integration/actions/delete_spec.rb
index 63faf63f7d..fc69f9fb92 100644
--- a/spec/integration/actions/delete_spec.rb
+++ b/spec/integration/actions/delete_spec.rb
@@ -173,7 +173,7 @@
end
end
- context 'with composite_primary_keys', composite_primary_keys: true do
+ context 'with composite primary keys', composite_primary_keys: true do
let(:fanship) { FactoryBot.create(:fanship) }
it 'deletes the object' do
diff --git a/spec/integration/actions/edit_spec.rb b/spec/integration/actions/edit_spec.rb
index f44e655296..6fa5d1b323 100644
--- a/spec/integration/actions/edit_spec.rb
+++ b/spec/integration/actions/edit_spec.rb
@@ -879,7 +879,7 @@ class HelpTest < Tableless
end
end
- context 'with composite_primary_keys', composite_primary_keys: true do
+ context 'with composite primary keys', composite_primary_keys: true do
let(:fanship) { FactoryBot.create(:fanship) }
it 'edits the object' do
@@ -888,5 +888,34 @@ class HelpTest < Tableless
click_button 'Save'
expect { fanship.reload }.to change { fanship.since }.from(nil).to(Date.new(2000, 1, 23))
end
+
+ context 'using custom serializer' do
+ before do
+ RailsAdmin.config.composite_keys_serializer = Class.new do
+ def self.serialize(keys)
+ keys.join(',')
+ end
+
+ def self.deserialize(string)
+ string.split(',')
+ end
+ end
+ end
+
+ it 'edits the object' do
+ visit edit_path(model_name: 'fanship', id: "#{fanship.fan_id},#{fanship.team_id}")
+ fill_in 'Since', with: '2000-01-23'
+ click_button 'Save'
+ expect { fanship.reload }.to change { fanship.since }.from(nil).to(Date.new(2000, 1, 23))
+ end
+ end
+
+ context 'receiving invalid id' do
+ it 'returns 404' do
+ visit edit_path(model_name: 'fanship', id: '11')
+ expect(page.driver.status_code).to eq(404)
+ is_expected.to have_content("Fanship with id '11' could not be found")
+ end
+ end
end
end
diff --git a/spec/integration/actions/export_spec.rb b/spec/integration/actions/export_spec.rb
index b91563d61b..4ba5ea8f40 100644
--- a/spec/integration/actions/export_spec.rb
+++ b/spec/integration/actions/export_spec.rb
@@ -160,7 +160,7 @@
end
end
- context 'with composite_primary_keys', composite_primary_keys: true do
+ context 'with composite primary keys', composite_primary_keys: true do
let!(:fanship) { FactoryBot.create(:fanship) }
it 'exports to CSV' do
diff --git a/spec/integration/actions/index_spec.rb b/spec/integration/actions/index_spec.rb
index 44ba1d44ca..6f13ad32aa 100644
--- a/spec/integration/actions/index_spec.rb
+++ b/spec/integration/actions/index_spec.rb
@@ -350,7 +350,7 @@
describe 'fields' do
before do
- if defined?(CompositePrimaryKeys)
+ if defined?(ActiveRecord) && ActiveRecord.gem_version >= Gem::Version.new('7.1') || defined?(CompositePrimaryKeys)
RailsAdmin.config Fan do
configure(:fanships) { hide }
configure(:fanship) { hide }
@@ -674,6 +674,31 @@
visit index_path(model_name: 'team')
expect(find('tbody tr:nth-child(1) td:nth-child(4)')).to have_content(@players.sort_by(&:id).collect(&:name).join(', '))
end
+
+ describe 'with title attribute' do
+ it 'does not allow XSS' do
+ RailsAdmin.config Team do
+ list do
+ field :name
+ end
+ end
+ @team = FactoryBot.create :team, name: '" onclick="alert()" "'
+ visit index_path(model_name: 'team')
+ expect(find('tbody tr:nth-child(1) td:nth-child(2)')['onclick']).to be_nil
+ expect(find('tbody tr:nth-child(1) td:nth-child(2)')['title']).to eq '" onclick="alert()" "'
+ end
+
+ it 'does not break values with HTML tags' do
+ RailsAdmin.config Player do
+ list do
+ field :team
+ end
+ end
+ @player = FactoryBot.create :player, team: FactoryBot.create(:team)
+ visit index_path(model_name: 'player')
+ expect(find('tbody tr:nth-child(1) td:nth-child(2)')['title']).to eq @player.team.name
+ end
+ end
end
context 'when no record exists' do
@@ -1220,7 +1245,7 @@ def visit_page(page)
end
end
- context 'with composite_primary_keys', composite_primary_keys: true do
+ context 'with composite primary keys', composite_primary_keys: true do
let!(:fanships) { FactoryBot.create_list(:fanship, 3) }
it 'shows the list' do
@@ -1232,5 +1257,24 @@ def visit_page(page)
end
is_expected.to have_content '3 fanships'
end
+
+ context 'using custom serializer' do
+ before do
+ RailsAdmin.config.composite_keys_serializer = Class.new do
+ def self.serialize(keys)
+ keys.join(',')
+ end
+
+ def self.deserialize(string)
+ string.split(',')
+ end
+ end
+ end
+
+ it 'shows the member action links accordingly' do
+ visit index_path(model_name: 'fanship')
+ is_expected.to have_css(%(a[href$="/admin/fanship/#{fanships[0].fan_id},#{fanships[0].team_id}/edit"]))
+ end
+ end
end
end
diff --git a/spec/integration/actions/new_spec.rb b/spec/integration/actions/new_spec.rb
index 3a83184396..e8b67965c4 100644
--- a/spec/integration/actions/new_spec.rb
+++ b/spec/integration/actions/new_spec.rb
@@ -195,7 +195,7 @@
end
end
- context 'with composite_primary_keys', composite_primary_keys: true do
+ context 'with composite primary keys', composite_primary_keys: true do
let!(:fan) { FactoryBot.create(:fan) }
let!(:team) { FactoryBot.create(:team) }
diff --git a/spec/integration/actions/show_spec.rb b/spec/integration/actions/show_spec.rb
index 47b639d38f..c08b3619ae 100644
--- a/spec/integration/actions/show_spec.rb
+++ b/spec/integration/actions/show_spec.rb
@@ -458,7 +458,7 @@
end
end
- context 'with composite_primary_keys', composite_primary_keys: true do
+ context 'with composite primary keys', composite_primary_keys: true do
let(:fanship) { FactoryBot.create(:fanship) }
it 'shows the object' do
diff --git a/spec/integration/fields/active_storage_spec.rb b/spec/integration/fields/active_storage_spec.rb
index 1c6a52cc15..0e8260533f 100644
--- a/spec/integration/fields/active_storage_spec.rb
+++ b/spec/integration/fields/active_storage_spec.rb
@@ -8,7 +8,7 @@
before do
# To suppress 'SQLite3::BusyException: database is locked' exception
@original = page.driver.browser.url_blacklist # rubocop:disable Naming/InclusiveLanguage
- page.driver.browser.url_blacklist = ['/rails/active_storage/representations'] # rubocop:disable Naming/InclusiveLanguage
+ page.driver.browser.url_blacklist = [%r{/rails/active_storage/representations}] # rubocop:disable Naming/InclusiveLanguage
end
after { page.driver.browser.url_blacklist = @original } # rubocop:disable Naming/InclusiveLanguage
diff --git a/spec/integration/fields/belongs_to_association_spec.rb b/spec/integration/fields/belongs_to_association_spec.rb
index 9a03a93fa1..78f997f5f0 100644
--- a/spec/integration/fields/belongs_to_association_spec.rb
+++ b/spec/integration/fields/belongs_to_association_spec.rb
@@ -12,14 +12,29 @@
end
describe 'on create' do
- before do
- FactoryBot.create :draft
- visit new_path(model_name: 'player')
- end
+ let!(:draft) { FactoryBot.create :draft }
+ let(:team) { FactoryBot.create :team }
it 'shows selects' do
+ visit new_path(model_name: 'player')
is_expected.to have_selector('select#player_team_id')
end
+
+ context 'with default_value' do
+ before do
+ id = team.id
+ RailsAdmin.config Player do
+ configure :team do
+ default_value id
+ end
+ end
+ end
+
+ it 'shows the value as selected' do
+ visit new_path(model_name: 'player')
+ expect(find('select#player_team_id').value).to eq team.id.to_s
+ end
+ end
end
describe 'on show' do
@@ -86,6 +101,20 @@
is_expected.to have_content 'Favorite player successfully updated'
expect(FavoritePlayer.all.map(&:fanship)).to eq [fanship]
end
+
+ context 'with invalid key' do
+ before do
+ allow_any_instance_of(RailsAdmin::Config::Fields::Types::BelongsToAssociation).
+ to receive(:collection).and_return([["Fanship ##{fanship.id}", 'invalid']])
+ end
+
+ it 'fails to update' do
+ visit edit_path(model_name: 'favorite_player', id: favorite_player.id)
+ select("Fanship ##{fanship.id}", from: 'Fanship')
+ click_button 'Save'
+ is_expected.to have_content 'Fanship must exist'
+ end
+ end
end
describe 'via remote-sourced field' do
diff --git a/spec/integration/fields/boolean_spec.rb b/spec/integration/fields/boolean_spec.rb
index 088a1cdd3e..3370a78466 100644
--- a/spec/integration/fields/boolean_spec.rb
+++ b/spec/integration/fields/boolean_spec.rb
@@ -44,6 +44,29 @@
end
end
+ context 'when the boolean is in an embedded document' do
+ before do
+ RailsAdmin.config FieldTest do
+ field :comment
+ end
+
+ RailsAdmin.config Comment do
+ field :content, :boolean
+ end
+ end
+
+ it 'can be updated', js: true do
+ visit edit_path(model_name: 'field_test', id: field_test.id)
+
+ # toggle open the embedded document section
+ find('#field_test_comment_attributes_field .add_nested_fields').click
+ # set the value to false and assert the values
+ find('.boolean_type label.danger').click
+ click_button 'Save and edit'
+ expect(field_test.reload.comment.content).to eq '0'
+ end
+ end
+
context 'if not nullable' do
before do
RailsAdmin.config FieldTest do
@@ -69,4 +92,28 @@
expect(field_test.reload.boolean_field).to be false
end
end
+
+ context 'if the database column is not nullable', active_record: true do
+ before do
+ RailsAdmin.config FieldTest do
+ field :non_nullable_boolean_field
+ end
+ end
+
+ it 'shows a checkbox' do
+ visit new_path(model_name: 'field_test')
+ is_expected.to have_content 'New Field test'
+ is_expected.to have_css '[type="checkbox"][name="field_test[non_nullable_boolean_field]"]'
+ end
+
+ it 'can be updated' do
+ visit edit_path(model_name: 'field_test', id: field_test.id)
+ find('.boolean_type input').check
+ click_button 'Save and edit'
+ expect(field_test.reload.non_nullable_boolean_field).to be true
+ find('.boolean_type input').uncheck
+ click_button 'Save and edit'
+ expect(field_test.reload.non_nullable_boolean_field).to be false
+ end
+ end
end
diff --git a/spec/integration/fields/has_many_association_spec.rb b/spec/integration/fields/has_many_association_spec.rb
index 34a552e21c..e2e2fde6e0 100644
--- a/spec/integration/fields/has_many_association_spec.rb
+++ b/spec/integration/fields/has_many_association_spec.rb
@@ -47,6 +47,22 @@
expect(@league.divisions).not_to include(@divisions[1])
expect(@league.divisions).not_to include(@divisions[2])
end
+
+ context 'with default_value' do
+ before do
+ ids = [@divisions[2].id]
+ RailsAdmin.config League do
+ configure :divisions do
+ default_value ids
+ end
+ end
+ end
+
+ it 'shows the value as selected' do
+ visit new_path(model_name: 'league')
+ expect(find('select#league_division_ids').value).to eq [@divisions[2].id.to_s]
+ end
+ end
end
context 'on update' do
@@ -212,6 +228,19 @@
is_expected.to have_content 'Fan successfully updated'
expect(fan.reload.fanships.map(&:team_id)).to match_array fanships.map(&:team_id)[0..1]
end
+
+ context 'with invalid key' do
+ before do
+ allow_any_instance_of(RailsAdmin::Config::Fields::Types::HasManyAssociation).
+ to receive(:collection).and_return([["Fanship ##{fanships[0].id}", 'invalid']])
+ end
+
+ it 'fails to update' do
+ visit edit_path(model_name: 'fan', id: fan.id)
+ select("Fanship ##{fanships[0].id}", from: 'Fanships')
+ expect { click_button 'Save' }.to raise_error ActiveRecord::RecordNotFound
+ end
+ end
end
describe 'via remote-sourced field' do
diff --git a/spec/integration/fields/has_one_association_spec.rb b/spec/integration/fields/has_one_association_spec.rb
index 8174e3e6cd..8834921f13 100644
--- a/spec/integration/fields/has_one_association_spec.rb
+++ b/spec/integration/fields/has_one_association_spec.rb
@@ -22,31 +22,53 @@
end
it 'creates an object with correct associations' do
- post new_path(model_name: 'player', player: FactoryBot.attributes_for(:player).merge(name: 'Jackie Robinson', draft_id: @draft.id))
+ visit new_path(model_name: 'player')
+ fill_in 'Name', with: 'Jackie Robinson'
+ fill_in 'Number', with: @draft.player.number + 1
+ select("Draft ##{@draft.id}", from: 'Draft')
+ click_button 'Save'
+ is_expected.to have_content 'Player successfully created'
@player = Player.where(name: 'Jackie Robinson').first
@draft.reload
expect(@player.draft).to eq(@draft)
end
+
+ context 'with default_value' do
+ before do
+ id = @draft.id
+ RailsAdmin.config Player do
+ configure :draft do
+ default_value id
+ end
+ end
+ end
+
+ it 'shows the value as selected' do
+ visit new_path(model_name: 'player')
+ expect(find('select#player_draft_id').value).to eq @draft.id.to_s
+ end
+ end
end
context 'on update' do
before do
- @player = FactoryBot.create :player
- @draft = FactoryBot.create :draft
- @number = @draft.player.number + 1 # to avoid collision
- put edit_path(model_name: 'player', id: @player.id, player: {name: 'Jackie Robinson', draft_id: @draft.id, number: @number, position: 'Second baseman'})
- @player.reload
+ @drafts = FactoryBot.create_list :draft, 2
+ @player = FactoryBot.create :player, draft: @drafts[0]
+ visit edit_path(model_name: 'player', id: @player.id)
end
- it 'updates an object with correct attributes' do
- expect(@player.name).to eq('Jackie Robinson')
- expect(@player.number).to eq(@number)
- expect(@player.position).to eq('Second baseman')
+ it 'updates an object with correct associations' do
+ select("Draft ##{@drafts[1].id}", from: 'Draft')
+ click_button 'Save'
+ @player.reload
+ expect(@player.draft).to eq(@drafts[1])
end
- it 'updates an object with correct associations' do
- @draft.reload
- expect(@player.draft).to eq(@draft)
+ it 'clears the current selection' do
+ select('', from: 'Draft')
+ click_button 'Save'
+ @player.reload
+ expect(@player.draft).to be nil
end
end
diff --git a/spec/integration/fields/multiple_active_storage_spec.rb b/spec/integration/fields/multiple_active_storage_spec.rb
index 10985407ba..cfb72c8a9e 100644
--- a/spec/integration/fields/multiple_active_storage_spec.rb
+++ b/spec/integration/fields/multiple_active_storage_spec.rb
@@ -12,7 +12,7 @@
end
# To suppress 'SQLite3::BusyException: database is locked' exception
@original = page.driver.browser.url_blacklist # rubocop:disable Naming/InclusiveLanguage
- page.driver.browser.url_blacklist = ['/rails/active_storage/representations'] # rubocop:disable Naming/InclusiveLanguage
+ page.driver.browser.url_blacklist = [%r{/rails/active_storage/representations}] # rubocop:disable Naming/InclusiveLanguage
end
after { page.driver.browser.url_blacklist = @original } # rubocop:disable Naming/InclusiveLanguage
diff --git a/spec/integration/fields/multiple_file_upload_spec.rb b/spec/integration/fields/multiple_file_upload_spec.rb
index 34bbfb0798..5446924246 100644
--- a/spec/integration/fields/multiple_file_upload_spec.rb
+++ b/spec/integration/fields/multiple_file_upload_spec.rb
@@ -17,7 +17,7 @@ def resource_url(_thumb = false)
delete_method 'boolean_field'
reorderable true
def value
- bindings[:object].safe_send(name)&.split(' ')
+ bindings[:object].safe_send(name)&.split
end
end
end
diff --git a/spec/integration/fields/polymorphic_assosiation_spec.rb b/spec/integration/fields/polymorphic_assosiation_spec.rb
index 56f4c648c5..c2ed1bac76 100644
--- a/spec/integration/fields/polymorphic_assosiation_spec.rb
+++ b/spec/integration/fields/polymorphic_assosiation_spec.rb
@@ -27,6 +27,18 @@
expect(@comment.commentable).to eq @hardball
end
+ it 'clears the selected id on type change', js: true do
+ @players = ['Jackie Robinson', 'Rob Wooten'].map { |name| FactoryBot.create :player, name: name }
+ visit new_path(model_name: 'comment')
+ select 'Player', from: 'comment[commentable_type]'
+ find('input.ra-filtering-select-input').set('Rob')
+ page.execute_script("document.querySelector('input.ra-filtering-select-input').dispatchEvent(new KeyboardEvent('keydown'))")
+ expect(page).to have_selector('ul.ui-autocomplete li.ui-menu-item a')
+ page.execute_script %{[...document.querySelectorAll('ul.ui-autocomplete li.ui-menu-item')].find(e => e.innerText.includes("Jackie Robinson")).click()}
+ select 'Team', from: 'comment[commentable_type]'
+ expect(find('#comment_commentable_id', visible: false).value).to eq ''
+ end
+
context 'when the associated model is declared in a two-level namespace' do
it 'successfully saves the record', js: true do
polymorphic_association_tests = ['Jackie Robinson', 'Rob Wooten'].map do |name|
@@ -78,6 +90,30 @@
is_expected.to have_selector('select#team_comment_ids')
end
+
+ context 'with records in different models share the same id', js: true do
+ let!(:players) { [FactoryBot.create(:player, id: team.id, name: 'Jackie Robinson')] }
+
+ it 'clears the selected id on type change', js: true do
+ visit edit_path(model_name: 'comment', id: comment.id)
+ select 'Player', from: 'comment[commentable_type]'
+ click_button 'Save'
+ is_expected.to have_content 'Comment successfully updated'
+ expect(comment.reload.commentable).to eq nil
+ end
+
+ it 'updates correctly', js: true do
+ visit edit_path(model_name: 'comment', id: comment.id)
+ select 'Player', from: 'comment[commentable_type]'
+ find('input.ra-filtering-select-input').set('Rob')
+ page.execute_script("document.querySelector('input.ra-filtering-select-input').dispatchEvent(new KeyboardEvent('keydown'))")
+ expect(page).to have_selector('ul.ui-autocomplete li.ui-menu-item a')
+ page.execute_script %{[...document.querySelectorAll('ul.ui-autocomplete li.ui-menu-item')].find(e => e.innerText.includes("Jackie Robinson")).click()}
+ click_button 'Save'
+ is_expected.to have_content 'Comment successfully updated'
+ expect(comment.reload.commentable).to eq players[0]
+ end
+ end
end
context 'on show' do
diff --git a/spec/integration/rails_admin_spec.rb b/spec/integration/rails_admin_spec.rb
index f9fd9645e1..26bb603f67 100644
--- a/spec/integration/rails_admin_spec.rb
+++ b/spec/integration/rails_admin_spec.rb
@@ -197,19 +197,26 @@
is_expected.to have_content 'Details for Player'
end
- it 'triggers rails_admin:dom_ready right after a validation error' do
+ it 'triggers rails_admin.dom_ready right after a validation error' do
visit edit_path(model_name: 'player', id: player.id)
fill_in 'player[name]', with: 'on steroids'
find_button('Save').trigger 'click'
is_expected.to have_content 'Player failed to be updated'
is_expected.to have_css '.filtering-select[data-input-for="player_team_id"]'
end
+
+ it 'does not prefetch pages' do
+ allow_any_instance_of(RailsAdmin::Config::Actions::Index).to receive(:controller).and_raise('index prefetched')
+ visit dashboard_path
+ find('.sidebar a.nav-link[href$="/player"]').hover
+ sleep 0.3 # Turbo waits 100ms before prefetch
+ end
end
describe 'dom_ready events', js: true do
it 'trigger properly' do
visit dashboard_path
- expect(evaluate_script('domReadyTriggered')).to match_array %w[plainjs/colon plainjs/dot jquery/colon jquery/dot]
+ expect(evaluate_script('domReadyTriggered')).to match_array %w[plainjs/dot jquery/dot]
end
end
diff --git a/spec/integration/widgets/filter_box_spec.rb b/spec/integration/widgets/filter_box_spec.rb
index 8f66e8da7f..27a2a89a35 100644
--- a/spec/integration/widgets/filter_box_spec.rb
+++ b/spec/integration/widgets/filter_box_spec.rb
@@ -157,7 +157,7 @@
describe 'for enum field' do
before do
RailsAdmin.config Team do
- field :color
+ field :color, :enum
end
end
@@ -170,6 +170,15 @@
expect(find('#filters_box select')['multiple']).to be true
expect(find('#filters_box select')['name']).to match(/\[\]$/)
end
+
+ context 'with the filter pre-populated' do
+ it 'does not break' do
+ visit index_path(model_name: 'team', f: {color: {'1' => {v: 'red'}}})
+ is_expected.to have_css('.filter select[name^="f[color]"]')
+ expect(find('.filter select[name^="f[color]"]').value).to eq 'red'
+ expect(all('#filters_box option').map(&:text)).to include 'white', 'black', 'red', 'green', 'blué'
+ end
+ end
end
describe 'for time field', active_record: true do
diff --git a/spec/integration/widgets/filtering_multi_select_spec.rb b/spec/integration/widgets/filtering_multi_select_spec.rb
index aa1602479c..2ad21a16b6 100644
--- a/spec/integration/widgets/filtering_multi_select_spec.rb
+++ b/spec/integration/widgets/filtering_multi_select_spec.rb
@@ -125,4 +125,41 @@
is_expected.to have_content 'New Team'
expect(all(:css, 'input.ra-multiselect-search').count).to eq 1
end
+
+ describe 'dynamic scoping' do
+ let!(:team) { FactoryBot.create :team, division: FactoryBot.create(:division) }
+ let(:division) { FactoryBot.create(:division) }
+ let!(:teams) { ['Los Angeles Dodgers', 'Texas Rangers'].map { |name| FactoryBot.create :team, name: name, division: division } }
+ before do
+ RailsAdmin.config Team do
+ field :name
+ field :division
+ end
+ RailsAdmin.config Fan do
+ field :division, :enum do
+ enum { Division.pluck(:name, CI_ORM == :active_record ? :custom_id : :id).to_h }
+ def value
+ nil
+ end
+
+ def parse_input(params)
+ params.delete :division
+ end
+ end
+ field :teams do
+ dynamically_scope_by :division
+ end
+ end
+ visit new_path(model_name: 'fan')
+ end
+
+ it 'changes selection candidates based on value of the specified field' do
+ expect(all('#fan_team_ids option', visible: false).map(&:value).filter(&:present?)).to be_empty
+ select division.name, from: 'Division', visible: false
+ find('input.ra-multiselect-search').set('e')
+ page.execute_script("document.querySelector('input.ra-multiselect-search').dispatchEvent(new KeyboardEvent('keydown'))")
+ is_expected.to have_content 'Dodgers'
+ expect(all('.ra-multiselect-collection option').map(&:text)).to match_array ['Los Angeles Dodgers', 'Texas Rangers']
+ end
+ end
end
diff --git a/spec/integration/widgets/filtering_select_spec.rb b/spec/integration/widgets/filtering_select_spec.rb
index 85a44bd77c..613df635af 100644
--- a/spec/integration/widgets/filtering_select_spec.rb
+++ b/spec/integration/widgets/filtering_select_spec.rb
@@ -10,6 +10,7 @@
before do
RailsAdmin.config Player do
field :team
+ field :number
end
end
@@ -40,7 +41,6 @@
find('input.ra-filtering-select-input').set('Tex')
page.execute_script("document.querySelector('input.ra-filtering-select-input').dispatchEvent(new KeyboardEvent('keydown'))")
is_expected.to have_selector('ul.ui-autocomplete li.ui-menu-item a')
- expect(page).to have_selector('ul.ui-autocomplete li.ui-menu-item a')
page.execute_script %{[...document.querySelectorAll('ul.ui-autocomplete li.ui-menu-item')].find(e => e.innerText.includes("Texas Rangers")).click()}
expect(find('#player_team_id', visible: false).value).to eq teams[1].id.to_s
end
@@ -53,7 +53,6 @@
find('input.ra-filtering-select-input').set('Tex')
page.execute_script("document.querySelector('input.ra-filtering-select-input').dispatchEvent(new KeyboardEvent('keydown'))")
is_expected.to have_selector('ul.ui-autocomplete li.ui-menu-item a')
- expect(page).to have_selector('ul.ui-autocomplete li.ui-menu-item a')
page.execute_script %{[...document.querySelectorAll('ul.ui-autocomplete li.ui-menu-item')].find(e => e.innerText.includes("Texas Rangers")).click()}
expect(find('#player_team_id', visible: false).value).to eq teams[1].id.to_s
end
@@ -133,4 +132,57 @@
expect(all(:css, 'ul.ui-autocomplete li.ui-menu-item a').map(&:text)).to eq ['Cincinnati Reds']
end
end
+
+ describe 'dynamic scoping' do
+ let!(:players) { FactoryBot.create_list :player, 2, team: teams[1] }
+ let!(:freelancer) { FactoryBot.create :player, team: nil }
+
+ context 'with single field' do
+ before do
+ player
+ RailsAdmin.config Draft do
+ field :team
+ field :player do
+ dynamically_scope_by :team
+ end
+ end
+ visit new_path(model_name: 'draft')
+ end
+
+ it 'changes selection candidates based on value of the specified field' do
+ expect(all('#draft_player_id option', visible: false).map(&:value).filter(&:present?)).to be_empty
+ find('[data-input-for="draft_team_id"] input.ra-filtering-select-input').set('Tex')
+ page.execute_script(%{document.querySelector('[data-input-for="draft_team_id"] input.ra-filtering-select-input').dispatchEvent(new KeyboardEvent('keydown'))})
+ is_expected.to have_selector('ul.ui-autocomplete li.ui-menu-item a')
+ page.execute_script %{[...document.querySelectorAll('ul.ui-autocomplete li.ui-menu-item')].find(e => e.innerText.includes("Texas Rangers")).click()}
+ within('[data-input-for="draft_player_id"].filtering-select') { find('.dropdown-toggle').click }
+ expect(all(:css, 'ul.ui-autocomplete li.ui-menu-item a').map(&:text)).to match_array players.map(&:name)
+ end
+
+ it 'allows filtering by blank value' do
+ within('[data-input-for="draft_player_id"].filtering-select') { find('.dropdown-toggle').click }
+ expect(all(:css, 'ul.ui-autocomplete li.ui-menu-item a').map(&:text)).to match_array [freelancer.name]
+ end
+ end
+
+ context 'with multiple fields' do
+ before do
+ player
+ RailsAdmin.config Draft do
+ field :team
+ field :player do
+ dynamically_scope_by [:team, {round: :number}]
+ end
+ field :round
+ end
+ visit new_path(model_name: 'draft', draft: {team_id: teams[1].id})
+ end
+
+ it 'changes selection candidates based on value of the specified fields' do
+ fill_in 'draft[round]', with: players[1].number
+ within('[data-input-for="draft_player_id"].filtering-select') { find('.dropdown-toggle').click }
+ expect(all(:css, 'ul.ui-autocomplete li.ui-menu-item a').map(&:text)).to match_array [players[1].name]
+ end
+ end
+ end
end
diff --git a/spec/integration/widgets/nested_many_spec.rb b/spec/integration/widgets/nested_many_spec.rb
index 85076df202..3121f39562 100644
--- a/spec/integration/widgets/nested_many_spec.rb
+++ b/spec/integration/widgets/nested_many_spec.rb
@@ -108,21 +108,13 @@
context 'with nested_attributes_options given' do
before do
allow(FieldTest.nested_attributes_options).to receive(:[]).with(any_args).
- and_return(allow_destroy: true, update_only: false)
- end
-
- it 'does not show add button when :update_only is true' do
- allow(FieldTest.nested_attributes_options).to receive(:[]).with(:nested_field_tests).
- and_return(allow_destroy: true, update_only: true)
- visit new_path(model_name: 'field_test')
- is_expected.to have_selector('.toggler')
- is_expected.not_to have_selector('#field_test_nested_field_tests_attributes_field .add_nested_fields')
+ and_return(allow_destroy: true)
end
it 'does not show destroy button except for newly created when :allow_destroy is false', js: false do
nested_field_tests
allow(FieldTest.nested_attributes_options).to receive(:[]).with(:nested_field_tests).
- and_return(allow_destroy: false, update_only: false)
+ and_return(allow_destroy: false)
visit edit_path(model_name: 'field_test', id: field_test.id)
expect(find('#field_test_nested_field_tests_attributes_0_title').value).to eq('title 1')
is_expected.not_to have_selector('form .remove_nested_fields')
diff --git a/spec/rails_admin/adapters/active_record/association_spec.rb b/spec/rails_admin/adapters/active_record/association_spec.rb
index d0b1cc5bc4..2d24126ca2 100644
--- a/spec/rails_admin/adapters/active_record/association_spec.rb
+++ b/spec/rails_admin/adapters/active_record/association_spec.rb
@@ -215,6 +215,18 @@ class FieldTestWithSymbolForeignKey < FieldTest
expect(@category.associations.detect { |a| a.name == :librarian }.klass).to eq [ARUser]
expect(@blog.associations.detect { |a| a.name == :librarian }.klass).to eq [ARProfile]
end
+
+ describe 'on a subclass' do
+ before do
+ class ARReview < ARComment; end
+ allow(RailsAdmin::Config).to receive(:models_pool).and_return(%w[ARBlog ARPost ARCategory ARUser ARProfile ARComment ARReview])
+ end
+ subject { RailsAdmin::AbstractModel.new(ARReview).associations.detect { |a| a.name == :commentable } }
+
+ it 'returns correct target klasses' do
+ expect(subject.klass).to eq [ARBlog, ARPost]
+ end
+ end
end
describe 'polymorphic inverse has_many association' do
diff --git a/spec/rails_admin/adapters/active_record/object_extension_spec.rb b/spec/rails_admin/adapters/active_record/object_extension_spec.rb
index 74a83c7342..a8d066f73c 100644
--- a/spec/rails_admin/adapters/active_record/object_extension_spec.rb
+++ b/spec/rails_admin/adapters/active_record/object_extension_spec.rb
@@ -11,63 +11,4 @@
expect(object.assign_attributes(nil)).to be nil
end
end
-
- describe 'has_one association' do
- let(:draft) { FactoryBot.create(:draft) }
- let(:player) { FactoryBot.build(:player).extend(RailsAdmin::Adapters::ActiveRecord::ObjectExtension) }
- before do
- class PlayerWithAutoSave < Player
- has_one :draft, inverse_of: :player, foreign_key: :player_id, autosave: true
- end
- end
-
- it 'provides id getter' do
- player.draft = draft
- expect(player.draft_id).to eq draft.id
- end
-
- context 'on create' do
- before do
- player.draft_id = draft.id
- expect(player.draft).to receive(:save).once.and_call_original
- player.save
- end
-
- it 'persists associated documents changes on save' do
- expect(player.reload.draft).to eq draft
- end
- end
-
- context 'on update' do
- let(:player) { FactoryBot.create(:player).extend(RailsAdmin::Adapters::ActiveRecord::ObjectExtension) }
- before do
- player.draft_id = draft.id
- end
-
- it 'persists associated documents changes on assignment' do
- expect(player.reload.draft).to eq draft
- end
- end
-
- context 'with explicit id setter' do
- let(:user) { ManagingUser.create(FactoryBot.attributes_for(:user)) }
- let(:team) { ManagedTeam.create(FactoryBot.attributes_for(:team)) }
-
- it 'works without issues' do
- user.team_id = team.id
- expect(user.reload.team).to eq team
- end
- end
-
- context 'when associated class has custom primary key' do
- let(:league) { FactoryBot.build(:league).extend(RailsAdmin::Adapters::ActiveRecord::ObjectExtension) }
- let(:division) { FactoryBot.create :division }
-
- it 'does not break' do
- league.division_id = division.id
- league.save!
- expect(league.reload.division).to eq division
- end
- end
- end
end
diff --git a/spec/rails_admin/adapters/active_record_spec.rb b/spec/rails_admin/adapters/active_record_spec.rb
index b2b5eb6cbd..444d870fd1 100644
--- a/spec/rails_admin/adapters/active_record_spec.rb
+++ b/spec/rails_admin/adapters/active_record_spec.rb
@@ -28,6 +28,25 @@
def predicates_for(scope)
scope.where_clause.instance_variable_get(:@predicates)
+ # .map do |predicate|
+ # if predicate.is_a? Arel::Nodes::BoundSqlLiteral
+ # binds = predicate.positional_binds
+ # predicate.sql_with_placeholders.delete_prefix('(').delete_suffix(')').gsub('?') do |_|
+ # bind = binds.shift
+ # case bind
+ # when Date
+ # "'#{bind.to_fs(:db)}'"
+ # when DateTime, Time
+ # "'#{bind.to_fs(:db)}'"
+ # else
+ # p bind
+ # bind.to_s
+ # end
+ # end
+ # else
+ # predicate
+ # end
+ # end
end
describe '#associations' do
@@ -127,9 +146,10 @@ class PlayerWithDefaultScope < Player
expect(abstract_model.all(bulk_ids: @players[0..1].collect(&:id))).to match_array @players[0..1]
end
- it 'supports retrieval by bulk_ids with composite_primary_keys', composite_primary_keys: true do
- expect(RailsAdmin::AbstractModel.new(Fanship).all(bulk_ids: ['1,2', '3,4']).to_sql).
- to include 'WHERE ("fans_teams"."fan_id" = 1 AND "fans_teams"."team_id" = 2 OR "fans_teams"."fan_id" = 3 AND "fans_teams"."team_id" = 4)'
+ it 'supports retrieval by bulk_ids with composite primary keys', composite_primary_keys: true do
+ expect(RailsAdmin::AbstractModel.new(Fanship).all(
+ bulk_ids: %w[1_2 3_4],
+ ).to_sql.tr('`', '"')).to include 'WHERE ("fans_teams"."fan_id" = 1 AND "fans_teams"."team_id" = 2 OR "fans_teams"."fan_id" = 3 AND "fans_teams"."team_id" = 4)'
end
it 'supports pagination' do
@@ -529,33 +549,33 @@ def build_statement(type, value, operator)
let(:scope) { FieldTest.all }
it 'supports date type query' do
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', '2012-02-01', '2012-03-01'], o: 'between'}}))).to eq(["(field_tests.date_field BETWEEN '2012-02-01' AND '2012-03-01')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', '2012-03-01', ''], o: 'between'}}))).to eq(["(field_tests.date_field >= '2012-03-01')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', '', '2012-02-01'], o: 'between'}}))).to eq(["(field_tests.date_field <= '2012-02-01')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['2012-02-01'], o: 'default'}}))).to eq(["(field_tests.date_field = '2012-02-01')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: [], o: 'today'}}))).to eq(["(field_tests.date_field = '#{Date.today}')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: [], o: 'yesterday'}}))).to eq(["(field_tests.date_field = '#{Date.yesterday}')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: [], o: 'this_week'}}))).to eq(["(field_tests.date_field BETWEEN '#{Date.today.beginning_of_week}' AND '#{Date.today.end_of_week}')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: [], o: 'last_week'}}))).to eq(["(field_tests.date_field BETWEEN '#{1.week.ago.to_date.beginning_of_week}' AND '#{1.week.ago.to_date.end_of_week}')"])
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', '2012-02-01', '2012-03-01'], o: 'between'}}))).to eq(predicates_for(scope.where('(field_tests.date_field BETWEEN ? AND ?)', Date.new(2012, 2, 1), Date.new(2012, 3, 1))))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', '2012-03-01', ''], o: 'between'}}))).to eq(predicates_for(scope.where('(field_tests.date_field >= ?)', Date.new(2012, 3, 1))))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', '', '2012-02-01'], o: 'between'}}))).to eq(predicates_for(scope.where('(field_tests.date_field <= ?)', Date.new(2012, 2, 1))))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['2012-02-01'], o: 'default'}}))).to eq(predicates_for(scope.where('(field_tests.date_field = ?)', Date.new(2012, 2, 1))))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: [], o: 'today'}}))).to eq(predicates_for(scope.where('(field_tests.date_field = ?)', Date.today)))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: [], o: 'yesterday'}}))).to eq(predicates_for(scope.where('(field_tests.date_field = ?)', Date.yesterday)))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: [], o: 'this_week'}}))).to eq(predicates_for(scope.where('(field_tests.date_field BETWEEN ? AND ?)', Date.today.beginning_of_week, Date.today.end_of_week)))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: [], o: 'last_week'}}))).to eq(predicates_for(scope.where('(field_tests.date_field BETWEEN ? AND ?)', 1.week.ago.to_date.beginning_of_week, 1.week.ago.to_date.end_of_week)))
end
it 'supports datetime type query' do
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', '2012-02-01T12:00:00', '2012-03-01T12:00:00'], o: 'between'}}))).to eq(["(field_tests.datetime_field BETWEEN '2012-02-01 12:00:00' AND '2012-03-01 12:00:00')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', '2012-03-01T12:00:00', ''], o: 'between'}}))).to eq(["(field_tests.datetime_field >= '2012-03-01 12:00:00')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', '', '2012-02-01T12:00:00'], o: 'between'}}))).to eq(["(field_tests.datetime_field <= '2012-02-01 12:00:00')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['2012-02-01T12:00:00'], o: 'default'}}))).to eq(["(field_tests.datetime_field = '2012-02-01 12:00:00')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: [], o: 'today'}}))).to eq(["(field_tests.datetime_field BETWEEN '#{Date.today} 00:00:00' AND '#{Date.today} 23:59:59.999999')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: [], o: 'yesterday'}}))).to eq(["(field_tests.datetime_field BETWEEN '#{Date.yesterday} 00:00:00' AND '#{Date.yesterday} 23:59:59.999999')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: [], o: 'this_week'}}))).to eq(["(field_tests.datetime_field BETWEEN '#{Date.today.beginning_of_week} 00:00:00' AND '#{Date.today.end_of_week} 23:59:59.999999')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: [], o: 'last_week'}}))).to eq(["(field_tests.datetime_field BETWEEN '#{1.week.ago.to_date.beginning_of_week} 00:00:00' AND '#{1.week.ago.to_date.end_of_week} 23:59:59.999999')"])
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', '2012-02-01T12:00:00', '2012-03-01T12:00:00'], o: 'between'}}))).to eq(predicates_for(scope.where('(field_tests.datetime_field BETWEEN ? AND ?)', Time.utc(2012, 2, 1, 12), Time.utc(2012, 3, 1, 12))))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', '2012-03-01T12:00:00', ''], o: 'between'}}))).to eq(predicates_for(scope.where('(field_tests.datetime_field >= ?)', Time.utc(2012, 3, 1, 12))))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', '', '2012-02-01T12:00:00'], o: 'between'}}))).to eq(predicates_for(scope.where('(field_tests.datetime_field <= ?)', Time.utc(2012, 2, 1, 12))))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['2012-02-01T12:00:00'], o: 'default'}}))).to eq(predicates_for(scope.where('(field_tests.datetime_field = ?)', Time.utc(2012, 2, 1, 12))))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: [], o: 'today'}}))).to eq(predicates_for(scope.where('(field_tests.datetime_field BETWEEN ? AND ?)', Date.today.beginning_of_day, Date.today.end_of_day)))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: [], o: 'yesterday'}}))).to eq(predicates_for(scope.where('(field_tests.datetime_field BETWEEN ? AND ?)', Date.yesterday.beginning_of_day, Date.yesterday.end_of_day)))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: [], o: 'this_week'}}))).to eq(predicates_for(scope.where('(field_tests.datetime_field BETWEEN ? AND ?)', Date.today.beginning_of_week.beginning_of_day, Date.today.end_of_week.end_of_day)))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: [], o: 'last_week'}}))).to eq(predicates_for(scope.where('(field_tests.datetime_field BETWEEN ? AND ?)', 1.week.ago.beginning_of_week, 1.week.ago.end_of_week)))
end
it 'supports time type query' do
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['', '2000-01-01T12:00:00', '2000-01-01T14:00:00'], o: 'between'}}))).to eq(["(field_tests.time_field BETWEEN '2000-01-01 12:00:00' AND '2000-01-01 14:00:00')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['', '2000-01-01T14:00:00', ''], o: 'between'}}))).to eq(["(field_tests.time_field >= '2000-01-01 14:00:00')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['', '', '2000-01-01T12:00:00'], o: 'between'}}))).to eq(["(field_tests.time_field <= '2000-01-01 12:00:00')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['2000-01-01T12:00:00'], o: 'default'}}))).to eq(["(field_tests.time_field = '2000-01-01 12:00:00')"])
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['2021-02-03T12:00:00'], o: 'default'}}))).to eq(["(field_tests.time_field = '2000-01-01 12:00:00')"])
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['', '2000-01-01T12:00:00', '2000-01-01T14:00:00'], o: 'between'}}))).to eq(predicates_for(scope.where('(field_tests.time_field BETWEEN ? AND ?)', Time.utc(2000, 1, 1, 12), Time.utc(2000, 1, 1, 14))))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['', '2000-01-01T14:00:00', ''], o: 'between'}}))).to eq(predicates_for(scope.where('(field_tests.time_field >= ?)', Time.utc(2000, 1, 1, 14))))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['', '', '2000-01-01T12:00:00'], o: 'between'}}))).to eq(predicates_for(scope.where('(field_tests.time_field <= ?)', Time.utc(2000, 1, 1, 12))))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['2000-01-01T12:00:00'], o: 'default'}}))).to eq(predicates_for(scope.where('(field_tests.time_field = ?)', Time.utc(2000, 1, 1, 12))))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'time_field' => {'1' => {v: ['2021-02-03T12:00:00'], o: 'default'}}))).to eq(predicates_for(scope.where('(field_tests.time_field = ?)', Time.utc(2000, 1, 1, 12))))
end
end
@@ -567,17 +587,55 @@ def build_statement(type, value, operator)
let(:scope) { FieldTest.all }
it 'supports integer enum type query' do
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'integer_enum_field' => {'1' => {v: 2, o: 'default'}}))).to eq(predicates_for(scope.where(['(field_tests.integer_enum_field IN (?))', 2])))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'integer_enum_field' => {'1' => {v: 2, o: 'default'}}))).to eq(predicates_for(scope.where('(field_tests.integer_enum_field IN (?))', [2])))
end
it 'supports string enum type query' do
- expect(predicates_for(abstract_model.send(:filter_scope, scope, 'string_enum_field' => {'1' => {v: 'm', o: 'default'}}))).to eq(predicates_for(scope.where(['(field_tests.string_enum_field IN (?))', 'm'])))
+ expect(predicates_for(abstract_model.send(:filter_scope, scope, 'string_enum_field' => {'1' => {v: 'm', o: 'default'}}))).to eq(predicates_for(scope.where('(field_tests.string_enum_field IN (?))', ['m'])))
end
end
- it 'supports uuid type query' do
- uuid = SecureRandom.uuid
- expect(build_statement(:uuid, uuid, nil)).to eq(['(field = ?)', uuid])
+ describe 'uuid type queries' do
+ it 'supports uuid type query' do
+ uuid = SecureRandom.uuid
+ expect(build_statement(:uuid, uuid, nil)).to eq(['(field = ?)', uuid])
+ end
+
+ it "supports '_blank' operator" do
+ [['_blank', ''], ['', '_blank']].each do |value, operator|
+ expect(build_statement(:uuid, value, operator)).to eq(['(field IS NULL)'])
+ end
+ end
+
+ it "supports '_present' operator" do
+ [['_present', ''], ['', '_present']].each do |value, operator|
+ expect(build_statement(:uuid, value, operator)).to eq(['(field IS NOT NULL)'])
+ end
+ end
+
+ it "supports '_null' operator" do
+ [['_null', ''], ['', '_null']].each do |value, operator|
+ expect(build_statement(:uuid, value, operator)).to eq(['(field IS NULL)'])
+ end
+ end
+
+ it "supports '_not_null' operator" do
+ [['_not_null', ''], ['', '_not_null']].each do |value, operator|
+ expect(build_statement(:uuid, value, operator)).to eq(['(field IS NOT NULL)'])
+ end
+ end
+
+ it "supports '_empty' operator" do
+ [['_empty', ''], ['', '_empty']].each do |value, operator|
+ expect(build_statement(:uuid, value, operator)).to eq(['(field IS NULL)'])
+ end
+ end
+
+ it "supports '_not_empty' operator" do
+ [['_not_empty', ''], ['', '_not_empty']].each do |value, operator|
+ expect(build_statement(:uuid, value, operator)).to eq(['(field IS NOT NULL)'])
+ end
+ end
end
end
diff --git a/spec/rails_admin/adapters/mongoid/association_spec.rb b/spec/rails_admin/adapters/mongoid/association_spec.rb
index 0bde4f1b30..94e8004f76 100644
--- a/spec/rails_admin/adapters/mongoid/association_spec.rb
+++ b/spec/rails_admin/adapters/mongoid/association_spec.rb
@@ -168,6 +168,18 @@ class MongoNote
expect(subject.read_only?).to be_falsey
expect(subject.nested_options).to be_nil
end
+
+ describe 'on a subclass' do
+ before do
+ class MongoReview < MongoComment; end
+ allow(RailsAdmin::Config).to receive(:models_pool).and_return(%w[MongoBlog MongoPost MongoCategory MongoUser MongoProfile MongoComment MongoReview])
+ end
+ subject { RailsAdmin::AbstractModel.new(MongoReview).associations.detect { |a| a.name == :commentable } }
+
+ it 'returns correct target klasses' do
+ expect(subject.klass).to eq [MongoBlog, MongoPost]
+ end
+ end
end
describe 'polymorphic inverse has_many association' do
diff --git a/spec/rails_admin/adapters/mongoid/object_extension_spec.rb b/spec/rails_admin/adapters/mongoid/object_extension_spec.rb
index 9609ca4dfb..20a5954d7a 100644
--- a/spec/rails_admin/adapters/mongoid/object_extension_spec.rb
+++ b/spec/rails_admin/adapters/mongoid/object_extension_spec.rb
@@ -69,7 +69,7 @@ class PlayerWithAutoSave < Player
context 'on create' do
before do
- player.draft_id = draft.id
+ player.draft = draft
expect(player.draft._target).to receive(:save).once.and_call_original
player.save
end
@@ -93,7 +93,7 @@ class PlayerWithAutoSave < Player
context 'on update' do
before do
- player.draft_id = draft.id
+ player.draft = draft
end
context 'with autosave: false' do
@@ -112,15 +112,5 @@ class PlayerWithAutoSave < Player
end
end
end
-
- context 'with explicit id setter' do
- let(:user) { ManagingUser.create(FactoryBot.attributes_for(:user)) }
- let(:team) { ManagedTeam.create(FactoryBot.attributes_for(:team)) }
-
- it 'works without issues' do
- user.team_id = team.id
- expect(user.reload.team).to eq team
- end
- end
end
end
diff --git a/spec/rails_admin/config/fields/association_spec.rb b/spec/rails_admin/config/fields/association_spec.rb
index 802282b929..bc6cf624f3 100644
--- a/spec/rails_admin/config/fields/association_spec.rb
+++ b/spec/rails_admin/config/fields/association_spec.rb
@@ -28,6 +28,91 @@
end
end
+ describe '#dynamic_scope_relationships' do
+ let(:player) { FactoryBot.create(:player, team: FactoryBot.create(:team)) }
+ let(:field) { RailsAdmin.config('Draft').fields.detect { |f| f.name == :player } }
+
+ it 'returns the relationship of fields in this model and in the associated model' do
+ RailsAdmin.config Draft do
+ field :team
+ field :player do
+ dynamically_scope_by :team
+ end
+ end
+ expect(field.dynamic_scope_relationships).to eq({team_id: :team})
+ end
+
+ it 'accepts Array' do
+ RailsAdmin.config Draft do
+ field :team
+ field :notes
+ field :player do
+ dynamically_scope_by %i[team notes]
+ end
+ end
+ expect(field.dynamic_scope_relationships).to eq({team_id: :team, notes: :notes})
+ end
+
+ it 'accepts Hash' do
+ RailsAdmin.config Draft do
+ field :round
+ field :player do
+ dynamically_scope_by({round: :number})
+ end
+ end
+ expect(field.dynamic_scope_relationships).to eq({round: :number})
+ end
+
+ it 'accepts mixture of Array and Hash' do
+ RailsAdmin.config Draft do
+ field :team
+ field :round
+ field :player do
+ dynamically_scope_by [:team, {round: :number}]
+ end
+ end
+ expect(field.dynamic_scope_relationships).to eq({team_id: :team, round: :number})
+ end
+
+ it 'raises error if the field does not exist in this model' do
+ RailsAdmin.config Draft do
+ field :player do
+ dynamically_scope_by :team
+ end
+ end
+ expect { field.dynamic_scope_relationships }.to raise_error "Field 'team' was given for #dynamically_scope_by but not found in 'Draft'"
+ end
+
+ it 'raises error if the field does not exist in the associated model' do
+ RailsAdmin.config Player do
+ field :name
+ end
+ RailsAdmin.config Draft do
+ field :team
+ field :player do
+ dynamically_scope_by :team
+ end
+ end
+ expect { field.dynamic_scope_relationships }.to raise_error "Field 'team' was given for #dynamically_scope_by but not found in 'Player'"
+ end
+
+ it 'raises error if the target field is not filterable' do
+ RailsAdmin.config Player do
+ field :name
+ field :team do
+ filterable false
+ end
+ end
+ RailsAdmin.config Draft do
+ field :team
+ field :player do
+ dynamically_scope_by :team
+ end
+ end
+ expect { field.dynamic_scope_relationships }.to raise_error "Field 'team' in 'Player' can't be used for dynamic scoping because it's not filterable"
+ end
+ end
+
describe '#removable?', active_record: true do
context 'with non-nullable foreign key' do
let(:field) { RailsAdmin.config('FieldTest').fields.detect { |f| f.name == :nested_field_tests } }
diff --git a/spec/rails_admin/config/fields/base_spec.rb b/spec/rails_admin/config/fields/base_spec.rb
index f9c5ef8b1a..195b17d03e 100644
--- a/spec/rails_admin/config/fields/base_spec.rb
+++ b/spec/rails_admin/config/fields/base_spec.rb
@@ -520,12 +520,6 @@ class CommentReversed < Tableless
end
end
- describe '#associated_collection' do
- it 'returns [] when type is blank?' do
- expect(RailsAdmin.config(Comment).fields.detect { |f| f.name == :commentable }.associated_collection('')).to be_empty
- end
- end
-
describe '#visible?' do
it 'is false when fields have specific name ' do
class FieldVisibilityTest < Tableless
diff --git a/spec/rails_admin/config/fields/types/active_record_enum_spec.rb b/spec/rails_admin/config/fields/types/active_record_enum_spec.rb
index 560cc41375..2661215d44 100644
--- a/spec/rails_admin/config/fields/types/active_record_enum_spec.rb
+++ b/spec/rails_admin/config/fields/types/active_record_enum_spec.rb
@@ -9,7 +9,11 @@
context 'when column name is format' do
before do
class FormatAsEnum < FieldTest
- enum format: {Text: 'txt', Markdown: 'md'}
+ if ActiveRecord.gem_version >= Gem::Version.new('7.0')
+ enum :format, {Text: 'txt', Markdown: 'md'}
+ else
+ enum format: {Text: 'txt', Markdown: 'md'}
+ end
end
end
let(:field) do
diff --git a/spec/rails_admin/config/fields/types/active_storage_spec.rb b/spec/rails_admin/config/fields/types/active_storage_spec.rb
index 24fca546ab..01a8dacaae 100644
--- a/spec/rails_admin/config/fields/types/active_storage_spec.rb
+++ b/spec/rails_admin/config/fields/types/active_storage_spec.rb
@@ -42,6 +42,15 @@
expect(field.image?).to be_falsy
end
end
+
+ context 'when attachment is a PDF file' do
+ let(:record) { FactoryBot.create :field_test, active_storage_asset: {io: StringIO.new('dummy'), filename: 'test.pdf', content_type: 'application/pdf'} }
+ before { allow(ActiveStorage::Previewer::PopplerPDFPreviewer).to receive(:accept?).and_return(true) }
+
+ it 'returns true' do
+ expect(field.image?).to be_truthy
+ end
+ end
end
describe '#resource_url' do
@@ -68,6 +77,15 @@
expect(field.resource_url(true)).not_to match(/representations/)
end
end
+
+ context 'when attachment is a PDF file' do
+ let(:record) { FactoryBot.create :field_test, active_storage_asset: {io: StringIO.new('dummy'), filename: 'test.pdf', content_type: 'application/pdf'} }
+ before { allow(ActiveStorage::Previewer::PopplerPDFPreviewer).to receive(:accept?).and_return(true) }
+
+ it 'returns variant\'s url' do
+ expect(field.resource_url(true)).to match(/representations/)
+ end
+ end
end
describe '#value' do
@@ -123,5 +141,17 @@
end
end
end
+
+ describe '#searchable' do
+ it 'is false' do
+ expect(field.searchable).to be false
+ end
+ end
+
+ describe '#sortable' do
+ it 'is false' do
+ expect(field.sortable).to be false
+ end
+ end
end
end
diff --git a/spec/rails_admin/config/fields/types/drangonfly_spec.rb b/spec/rails_admin/config/fields/types/drangonfly_spec.rb
index 354d00b923..da8cd3c616 100644
--- a/spec/rails_admin/config/fields/types/drangonfly_spec.rb
+++ b/spec/rails_admin/config/fields/types/drangonfly_spec.rb
@@ -5,6 +5,29 @@
RSpec.describe RailsAdmin::Config::Fields::Types::Dragonfly do
it_behaves_like 'a generic field type', :string_field, :dragonfly
+ let(:field) do
+ RailsAdmin.config('FieldTest').fields.detect do |f|
+ f.name == :dragonfly_asset
+ end.with(object: record)
+ end
+
+ describe '#image?' do
+ let(:file) { File.open(file_path('test.jpg')) }
+ let(:record) { FactoryBot.create :field_test, dragonfly_asset: file }
+
+ it 'returns true' do
+ expect(field.image?).to be true
+ end
+
+ context 'with non-image' do
+ let(:file) { File.open(file_path('test.txt')) }
+
+ it 'returns false' do
+ expect(field.image?).to be false
+ end
+ end
+ end
+
describe 'with a model which does not extend Dragonfly::Model' do
before do
class NonDragonflyTest < Tableless
diff --git a/spec/rails_admin/config/fields/types/enum_spec.rb b/spec/rails_admin/config/fields/types/enum_spec.rb
index 014d10aae0..6bee0de9b6 100644
--- a/spec/rails_admin/config/fields/types/enum_spec.rb
+++ b/spec/rails_admin/config/fields/types/enum_spec.rb
@@ -126,7 +126,11 @@ def color_list
before do
class TeamWithSerializedEnum < Team
self.table_name = 'teams'
- serialize :color
+ if ActiveRecord.gem_version < Gem::Version.new('7.1')
+ serialize :color
+ else
+ serialize :color, coder: JSON
+ end
def color_enum
%w[blue green red]
end
diff --git a/spec/rails_admin/config/fields/types/file_upload_spec.rb b/spec/rails_admin/config/fields/types/file_upload_spec.rb
index 1beba9b55c..35ffb6280c 100644
--- a/spec/rails_admin/config/fields/types/file_upload_spec.rb
+++ b/spec/rails_admin/config/fields/types/file_upload_spec.rb
@@ -83,4 +83,57 @@ def resource_url
end
end
end
+
+ describe '#image?' do
+ let(:filename) { 'dummy.txt' }
+ let :rails_admin_field do
+ RailsAdmin.config('FieldTest').fields.detect do |f|
+ f.name == :string_field
+ end.with(
+ object: FieldTest.new(string_field: filename),
+ view: ApplicationController.new.view_context,
+ )
+ end
+ before do
+ RailsAdmin.config FieldTest do
+ field :string_field, :file_upload do
+ def resource_url
+ "http://example.com/#{value}"
+ end
+ end
+ end
+ end
+
+ context 'when the file is not an image' do
+ let(:filename) { 'dummy.txt' }
+
+ it 'returns false' do
+ expect(rails_admin_field.image?).to be false
+ end
+ end
+
+ context 'when the file is an image' do
+ let(:filename) { 'dummy.jpg' }
+
+ it 'returns true' do
+ expect(rails_admin_field.image?).to be true
+ end
+ end
+
+ context 'when the file is an image but suffixed with a query string' do
+ let(:filename) { 'dummy.jpg?foo=bar' }
+
+ it 'returns true' do
+ expect(rails_admin_field.image?).to be true
+ end
+ end
+
+ context "when the filename can't be represented as a valid URI" do
+ let(:filename) { 'du mmy.jpg' }
+
+ it 'returns false' do
+ expect(rails_admin_field.image?).to be false
+ end
+ end
+ end
end
diff --git a/spec/rails_admin/config/fields/types/json_spec.rb b/spec/rails_admin/config/fields/types/json_spec.rb
index a1e0395827..a749d93025 100644
--- a/spec/rails_admin/config/fields/types/json_spec.rb
+++ b/spec/rails_admin/config/fields/types/json_spec.rb
@@ -24,7 +24,7 @@
it 'returns correct value for empty json' do
allow(object).to receive(:json_field) { {} }
actual = field.with(bindings).formatted_value
- expect(actual).to match(/{\n+}/)
+ expect(actual).to match(/{\n*}/)
end
it 'retuns correct value' do
@@ -72,7 +72,7 @@
it 'returns correct value for empty json' do
allow(object).to receive(:json_field) { {} }
actual = field.with(bindings).export_value
- expect(actual).to match(/{\n+}/)
+ expect(actual).to match(/{\n*}/)
end
it 'returns correct value' do
diff --git a/spec/rails_admin/config/fields/types/multiple_active_storage_spec.rb b/spec/rails_admin/config/fields/types/multiple_active_storage_spec.rb
index 986dd2c096..39cc09fd36 100644
--- a/spec/rails_admin/config/fields/types/multiple_active_storage_spec.rb
+++ b/spec/rails_admin/config/fields/types/multiple_active_storage_spec.rb
@@ -63,6 +63,15 @@
expect(field.attachments[0].image?).to be_falsy
end
end
+
+ context 'when attachment is a PDF file' do
+ let(:record) { FactoryBot.create :field_test, active_storage_assets: [{io: StringIO.new('dummy'), filename: 'test.pdf', content_type: 'application/pdf'}] }
+ before { allow(ActiveStorage::Previewer::PopplerPDFPreviewer).to receive(:accept?).and_return(true) }
+
+ it 'returns true' do
+ expect(field.attachments[0].image?).to be_truthy
+ end
+ end
end
describe '#resource_url' do
@@ -89,6 +98,15 @@
expect(field.attachments[0].resource_url(true)).not_to match(/representations/)
end
end
+
+ context 'when attachment is a PDF file' do
+ let(:record) { FactoryBot.create :field_test, active_storage_assets: [{io: StringIO.new('dummy'), filename: 'test.pdf', content_type: 'application/pdf'}] }
+ before { allow(ActiveStorage::Previewer::PopplerPDFPreviewer).to receive(:accept?).and_return(true) }
+
+ it 'returns variant\'s url' do
+ expect(field.attachments[0].resource_url(true)).to match(/representations/)
+ end
+ end
end
end
@@ -127,5 +145,17 @@
end
end
end
+
+ describe '#searchable' do
+ it 'is false' do
+ expect(field.searchable).to be false
+ end
+ end
+
+ describe '#sortable' do
+ it 'is false' do
+ expect(field.sortable).to be false
+ end
+ end
end
end
diff --git a/spec/rails_admin/config/fields/types/multiple_carrierwave_spec.rb b/spec/rails_admin/config/fields/types/multiple_carrierwave_spec.rb
index 5c1d3ef627..979d3110c8 100644
--- a/spec/rails_admin/config/fields/types/multiple_carrierwave_spec.rb
+++ b/spec/rails_admin/config/fields/types/multiple_carrierwave_spec.rb
@@ -8,7 +8,6 @@
describe '#thumb_method' do
before do
- allow_any_instance_of(CarrierwaveUploader).to receive(:cache!)
RailsAdmin.config FieldTest do
field :carrierwave_assets, :multiple_carrierwave
end
diff --git a/spec/rails_admin/config/fields/types/multiple_file_upload_spec.rb b/spec/rails_admin/config/fields/types/multiple_file_upload_spec.rb
index e639b400e8..fb24106cb3 100644
--- a/spec/rails_admin/config/fields/types/multiple_file_upload_spec.rb
+++ b/spec/rails_admin/config/fields/types/multiple_file_upload_spec.rb
@@ -146,4 +146,59 @@ def value
expect(rails_admin_field.with(object: FieldTest.new(string_field: 'dummy.txt')).attachments.map(&:value)).to eq ['dummy.txt']
end
end
+
+ describe '#image?' do
+ let(:filename) { 'dummy.txt' }
+ let :rails_admin_field do
+ RailsAdmin.config('FieldTest').fields.detect do |f|
+ f.name == :string_field
+ end.with(
+ object: FieldTest.new(string_field: filename),
+ view: ApplicationController.new.view_context,
+ )
+ end
+ before do
+ RailsAdmin.config FieldTest do
+ field :string_field, :multiple_file_upload do
+ attachment do
+ def resource_url
+ "http://example.com/#{value}"
+ end
+ end
+ end
+ end
+ end
+
+ context 'when the file is not an image' do
+ let(:filename) { 'dummy.txt' }
+
+ it 'returns false' do
+ expect(rails_admin_field.attachments.first.image?).to be false
+ end
+ end
+
+ context 'when the file is an image' do
+ let(:filename) { 'dummy.jpg' }
+
+ it 'returns true' do
+ expect(rails_admin_field.attachments.first.image?).to be true
+ end
+ end
+
+ context 'when the file is an image but suffixed with a query string' do
+ let(:filename) { 'dummy.jpg?foo=bar' }
+
+ it 'returns true' do
+ expect(rails_admin_field.attachments.first.image?).to be true
+ end
+ end
+
+ context "when the filename can't be represented as a valid URI" do
+ let(:filename) { 'du mmy.jpg' }
+
+ it 'returns false' do
+ expect(rails_admin_field.attachments.first.image?).to be false
+ end
+ end
+ end
end
diff --git a/spec/rails_admin/config/fields/types/paperclip_spec.rb b/spec/rails_admin/config/fields/types/paperclip_spec.rb
index 398e51abcd..9b9b5836e2 100644
--- a/spec/rails_admin/config/fields/types/paperclip_spec.rb
+++ b/spec/rails_admin/config/fields/types/paperclip_spec.rb
@@ -5,7 +5,7 @@
RSpec.describe RailsAdmin::Config::Fields::Types::Paperclip do
it_behaves_like 'a generic field type', :string_field, :paperclip
- context 'when a *_file_name field exists but not decleared as has_attached_file' do
+ context 'when a *_file_name field exists but not declared as has_attached_file' do
before do
class PaperclipTest < Tableless
column :some_file_name, :varchar
diff --git a/spec/rails_admin/config/has_fields_spec.rb b/spec/rails_admin/config/has_fields_spec.rb
index 8c7d3ca729..37528bcde6 100644
--- a/spec/rails_admin/config/has_fields_spec.rb
+++ b/spec/rails_admin/config/has_fields_spec.rb
@@ -26,7 +26,7 @@
end
end
expect { RailsAdmin.config(Team).fields.detect { |f| f.name == :division } }.not_to raise_error
- expect { RailsAdmin.config(Team).fields.detect { |f| f.name == :division }.visible? }.to raise_error("undefined method `[]' for nil:NilClass")
+ expect { RailsAdmin.config(Team).fields.detect { |f| f.name == :division }.visible? }.to raise_error(/undefined method `\[\]' for nil/)
end
it 'assigns properties to new one on overriding existing field' do
diff --git a/spec/rails_admin/config_spec.rb b/spec/rails_admin/config_spec.rb
index cabae96792..910ccc1b5a 100644
--- a/spec/rails_admin/config_spec.rb
+++ b/spec/rails_admin/config_spec.rb
@@ -264,6 +264,14 @@ class RecursivelyEmbedsMany
it 'includes models in the directory added by config.eager_load_paths' do
expect(RailsAdmin::Config.models_pool).to include('Basketball')
end
+
+ it 'should include a model which was configured explicitly' do
+ RailsAdmin::Config.model 'PaperTrail::Version' do
+ visible false
+ end
+
+ expect(RailsAdmin::Config.models_pool).to include('PaperTrail::Version')
+ end
end
describe '.parent_controller' do
@@ -286,7 +294,7 @@ class TestController < ActionController::Base; end
describe '.parent_controller=' do
context 'if RailsAdmin::ApplicationController is already loaded' do
before do
- # preload cotrollers (e.g. when config.eager_load = true)
+ # preload controllers (e.g. when config.eager_load = true)
RailsAdmin::MainController
end
diff --git a/spec/rails_admin/install_generator_spec.rb b/spec/rails_admin/install_generator_spec.rb
index 3d5333f3a0..1d819c0a82 100644
--- a/spec/rails_admin/install_generator_spec.rb
+++ b/spec/rails_admin/install_generator_spec.rb
@@ -87,6 +87,18 @@
contains 'webpack --config webpack.config.js'
contains 'sass ./app/assets/stylesheets/rails_admin.scss:./app/assets/builds/rails_admin.css'
end
+ when :vite
+ file 'app/frontend/entrypoints/rails_admin.js' do
+ contains 'import "~/stylesheets/rails_admin.scss"'
+ contains 'import "rails_admin/src/rails_admin/base"'
+ end
+ file 'app/frontend/stylesheets/rails_admin.scss' do
+ contains '$fa-font-path: "@fortawesome/fontawesome-free/webfonts";'
+ contains '@import "rails_admin/src/rails_admin/styles/base"'
+ end
+ file 'package.json' do
+ contains 'sass'
+ end
end
end,
)
diff --git a/spec/rails_admin/support/csv_converter_spec.rb b/spec/rails_admin/support/csv_converter_spec.rb
index e0e1198e9a..f13e4eb5a1 100644
--- a/spec/rails_admin/support/csv_converter_spec.rb
+++ b/spec/rails_admin/support/csv_converter_spec.rb
@@ -68,7 +68,7 @@
before do
case connection_config[:adapter]
when 'postgresql'
- @connection = ActiveRecord::Base.connection.instance_variable_get(:@connection)
+ @connection = ActiveRecord::Base.connection.raw_connection
@connection.set_client_encoding('latin1')
when 'mysql2'
ActiveRecord::Base.connection.execute('SET NAMES latin1;')
diff --git a/spec/rails_admin/support/datetime_spec.rb b/spec/rails_admin/support/datetime_spec.rb
index fae507e556..27bfe757e5 100644
--- a/spec/rails_admin/support/datetime_spec.rb
+++ b/spec/rails_admin/support/datetime_spec.rb
@@ -13,6 +13,7 @@
'%Y-%m-%dT%H:%M:%S%:z' => 'Y-m-d\TH:i:S+00:00',
'%HH%MM%SS' => 'H\Hi\MS\S',
'a%-Ha%-Ma%-Sa%:za' => '\aH\ai\as\a+00:00\a',
+ '%B %-d at %-l:%M %p' => 'F j \a\t h:i K',
}.each do |strftime_format, flatpickr_format|
it "convert strftime_format to flatpickr_format - example #{strftime_format}" do
expect(RailsAdmin::Support::Datetime.to_flatpickr_format(strftime_format)).to eq flatpickr_format
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 96846770d8..d6280259e7 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -68,6 +68,7 @@
config.include Warden::Test::Helpers
config.include Capybara::DSL, type: :request
+ config.include Capybara::RSpecMatchers, type: :request
config.verbose_retry = true
config.display_try_failure_messages = true
@@ -75,11 +76,27 @@
example.run_with_retry retry: (ENV['CI'] && RUBY_ENGINE == 'jruby' ? 3 : 2)
end
config.retry_callback = proc do |example|
- Capybara.reset! if example.metadata[:js]
+ example.metadata[:retry] = 6 if [Ferrum::DeadBrowserError, Ferrum::NoExecutionContextError, Ferrum::TimeoutError].include?(example.exception.class)
+ if example.metadata[:js]
+ attempt = 0
+ begin
+ Capybara.reset!
+ rescue Ferrum::TimeoutError, Ferrum::NoExecutionContextError
+ attempt += 1
+ raise if attempt >= 5
+
+ retry
+ end
+ end
end
config.before(:all) do
- Webpacker.instance.compiler.compile if CI_ASSET == :webpacker
+ case CI_ASSET
+ when :webpacker
+ Webpacker.instance.compiler.compile
+ when :vite
+ ViteRuby.instance.commands.build
+ end
end
config.before do |example|
@@ -102,11 +119,11 @@
CI_TARGET_ORMS.each do |orm|
if orm == CI_ORM
- config.filter_run_excluding "skip_#{orm}".to_sym => true
+ config.filter_run_excluding "skip_#{orm}": true
else
config.filter_run_excluding orm => true
end
end
- config.filter_run_excluding composite_primary_keys: true unless defined?(CompositePrimaryKeys)
+ config.filter_run_excluding composite_primary_keys: true unless defined?(ActiveRecord) && ActiveRecord.gem_version >= Gem::Version.new('7.1') || defined?(CompositePrimaryKeys)
end
diff --git a/src/rails_admin/abstract-select.js b/src/rails_admin/abstract-select.js
new file mode 100644
index 0000000000..161157a615
--- /dev/null
+++ b/src/rails_admin/abstract-select.js
@@ -0,0 +1,30 @@
+import jQuery from "jquery";
+import "jquery-ui/ui/widget.js";
+
+(function ($) {
+ "use strict";
+
+ $.widget("ra.abstractSelect", {
+ options: {
+ createQuery: function (query) {
+ if ($.isEmptyObject(this.scopeBy)) {
+ return { query: query };
+ } else {
+ const filterQuery = {};
+ for (var field in this.scopeBy) {
+ const targetField = this.scopeBy[field];
+ const targetValue = $(`[name$="[${field}]"]`).val();
+ if (!filterQuery[targetField]) {
+ filterQuery[targetField] = [];
+ }
+ filterQuery[targetField].push(
+ targetValue ? { o: "is", v: targetValue } : { o: "_blank" }
+ );
+ }
+ return { query: query, f: filterQuery };
+ }
+ },
+ scopeBy: {},
+ },
+ });
+})(jQuery);
diff --git a/src/rails_admin/base.js b/src/rails_admin/base.js
index ff7bba76cc..8e2685bab5 100644
--- a/src/rails_admin/base.js
+++ b/src/rails_admin/base.js
@@ -17,6 +17,7 @@ import "jquery-ui/ui/widget.js";
import "jquery-ui/ui/widgets/menu.js";
import "jquery-ui/ui/widgets/mouse.js";
+import "./abstract-select";
import "./filter-box";
import "./filtering-multiselect";
import "./filtering-select";
@@ -26,4 +27,6 @@ import "./sidescroll";
import "./ui";
import "./widgets";
-Rails.start();
+if (!window._rails_loaded) {
+ Rails.start();
+}
diff --git a/src/rails_admin/filtering-multiselect.js b/src/rails_admin/filtering-multiselect.js
index 1eba540bc7..871bdfa5a3 100644
--- a/src/rails_admin/filtering-multiselect.js
+++ b/src/rails_admin/filtering-multiselect.js
@@ -1,13 +1,11 @@
import jQuery from "jquery";
import "jquery-ui/ui/widget.js";
import I18n from "./i18n";
+
(function ($) {
- $.widget("ra.filteringMultiselect", {
+ $.widget("ra.filteringMultiselect", $.ra.abstractSelect, {
_cache: {},
options: {
- createQuery: function (query) {
- return { query: query };
- },
sortable: false,
removable: true,
regional: {
diff --git a/src/rails_admin/filtering-select.js b/src/rails_admin/filtering-select.js
index fee6ce625b..7695e545f3 100644
--- a/src/rails_admin/filtering-select.js
+++ b/src/rails_admin/filtering-select.js
@@ -6,11 +6,8 @@ import I18n from "./i18n";
(function ($) {
"use strict";
- $.widget("ra.filteringSelect", {
+ $.widget("ra.filteringSelect", $.ra.abstractSelect, {
options: {
- createQuery: function (query) {
- return { query: query };
- },
minLength: 0,
searchDelay: 200,
remote_source: null,
@@ -300,6 +297,7 @@ import I18n from "./i18n";
destroy: function () {
this.input.remove();
this.button.remove();
+ this.element.html($(''));
this.element.show();
this.filtering_select.remove();
$.Widget.prototype.destroy.call(this);
diff --git a/src/rails_admin/nested-form-hooks.js b/src/rails_admin/nested-form-hooks.js
index 64791a3ae4..9f627c9f37 100644
--- a/src/rails_admin/nested-form-hooks.js
+++ b/src/rails_admin/nested-form-hooks.js
@@ -37,9 +37,8 @@ import * as bootstrap from "bootstrap";
toggler = controls.find(".toggler");
nav.append(new_tab);
- document.dispatchEvent(
- new CustomEvent("rails_admin:dom_ready", { detail: field })
- );
+ const event = new CustomEvent("rails_admin.dom_ready", { detail: field });
+ document.dispatchEvent(event);
new_tab.children("a").each(function (index, element) {
bootstrap.Tab.getOrCreateInstance(element).show();
diff --git a/src/rails_admin/remote-form.js b/src/rails_admin/remote-form.js
index d462763381..15b3fe45cd 100644
--- a/src/rails_admin/remote-form.js
+++ b/src/rails_admin/remote-form.js
@@ -101,9 +101,8 @@ import * as bootstrap from "bootstrap";
})
.html(saveButtonText);
- document.dispatchEvent(
- new CustomEvent("rails_admin:dom_ready", { detail: form })
- );
+ const event = new CustomEvent("rails_admin.dom_ready", { detail: form });
+ document.dispatchEvent(event);
form.bind("ajax:complete", function (event) {
var data = event.detail[0];
diff --git a/src/rails_admin/sidescroll.js b/src/rails_admin/sidescroll.js
index 7a478cab42..d8a3f6a116 100644
--- a/src/rails_admin/sidescroll.js
+++ b/src/rails_admin/sidescroll.js
@@ -1,7 +1,7 @@
"use strict";
{
- document.addEventListener("rails_admin:dom_ready", () => {
+ document.addEventListener("rails_admin.dom_ready", () => {
const scroller = document.getElementById("sidescroll");
if (!scroller) {
return;
diff --git a/src/rails_admin/ui.js b/src/rails_admin/ui.js
index eb7bdb0fe7..243e7b59aa 100644
--- a/src/rails_admin/ui.js
+++ b/src/rails_admin/ui.js
@@ -82,12 +82,13 @@ import I18n from "./i18n";
function triggerDomReady() {
I18n.init($("html").attr("lang"), $("#admin-js").data("i18nOptions"));
- document.dispatchEvent(new CustomEvent("rails_admin:dom_ready"));
+ const event = new CustomEvent("rails_admin.dom_ready");
+ document.dispatchEvent(event);
}
$(document).ready(triggerDomReady);
document.addEventListener("turbo:render", triggerDomReady);
- document.addEventListener("rails_admin:dom_ready", function (event) {
+ document.addEventListener("rails_admin.dom_ready", function (event) {
$(".nav.nav-pills li.active").removeClass("active");
$(
'.nav.nav-pills li[data-model="' + $(".page-header").data("model") + '"]'
@@ -129,10 +130,7 @@ import I18n from "./i18n";
window.Turbo.session.drive = false;
});
- // Trigger with the old event name for compatibility with existing user codes
- document.dispatchEvent(
- new CustomEvent("rails_admin.dom_ready", { detail: event.detail })
- );
+ // Trigger via jQuery for compatibility with existing user codes
$(document).trigger(
"rails_admin.dom_ready",
event.detail ? [event.detail] : null
diff --git a/src/rails_admin/widgets.js b/src/rails_admin/widgets.js
index 5e7b78b20d..aabd4833d4 100644
--- a/src/rails_admin/widgets.js
+++ b/src/rails_admin/widgets.js
@@ -5,7 +5,7 @@ import flatpickr from "flatpickr";
import I18n from "./i18n";
(function ($) {
- document.addEventListener("rails_admin:dom_ready", function (event) {
+ document.addEventListener("rails_admin.dom_ready", function (event) {
var $editors,
array,
config_options,
|