diff --git a/.circleci/config.yml b/.circleci/config.yml
index 8b22ea1412..1e632b9591 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -17,7 +17,7 @@ commands:
- run: google-chrome --version
# Install bundler
- - run: gem install bundler:2.3.5
+ - run: gem install bundler:2.4.22
# Restore Cached Dependencies
- restore_cache:
@@ -51,27 +51,6 @@ default_job: &default_job
working_directory: ~/administrate
jobs:
- ruby-27:
- <<: *default_job
- steps:
- - shared_steps
- # Run the tests against the versions of Rails that support Ruby 2.7
- - run: bundle exec appraisal install
- - run: bundle exec appraisal rails60 rspec
- - run: bundle exec appraisal rails61 rspec
- - run: bundle exec appraisal rails70 rspec
- docker:
- - image: cimg/ruby:2.7-browsers
- environment:
- PGHOST: localhost
- PGUSER: administrate
- RAILS_ENV: test
- - image: cimg/postgres:15.1
- environment:
- POSTGRES_USER: administrate
- POSTGRES_DB: ruby27
- POSTGRES_PASSWORD: ""
-
ruby-30:
<<: *default_job
steps:
@@ -140,4 +119,3 @@ workflows:
- ruby-32
- ruby-31
- ruby-30
- - ruby-27
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index e8f97db17d..5810298de6 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,8 +1,16 @@
+---
version: 2
updates:
-- package-ecosystem: bundler
- directory: "/"
- schedule:
- interval: daily
- open-pull-requests-limit: 10
+ - package-ecosystem: bundler
+ directory: "/"
+ schedule:
+ interval: daily
+ open-pull-requests-limit: 10
+
+ - package-ecosystem: github-actions
+ directory: "/"
+ schedule:
+ interval: weekly
+ time: '02:00'
+ timezone: 'Etc/UTC'
diff --git a/.github/workflows/bundle-audit.yml b/.github/workflows/bundle-audit.yml
index 4a4adaa28c..7fc6e17341 100644
--- a/.github/workflows/bundle-audit.yml
+++ b/.github/workflows/bundle-audit.yml
@@ -6,8 +6,8 @@ jobs:
audit:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: 'Bundler Audit'
- uses: andrewmcodes/bundler-audit-action@main
+ uses: thoughtbot/bundler-audit-action@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index a925cf7993..9bc723b6e0 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -22,15 +22,15 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
- uses: github/codeql-action/autobuild@v1
+ uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000000..4e8bfb500c
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,54 @@
+---
+name: CI
+on: [push]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ ruby: ['3.0', 3.1, 3.2, 3.3]
+ env:
+ PGHOST: localhost
+ PGUSER: administrate
+ PGPASSWORD: administrate
+ services:
+ postgres:
+ image: postgres
+ env:
+ POSTGRES_USER: administrate
+ POSTGRES_DB: administrate_test
+ POSTGRES_PASSWORD: administrate
+ ports:
+ - 5432:5432
+ options: >-
+ --health-cmd pg_isready
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Ruby ${{ matrix.ruby }}
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby }}
+ bundler-cache: true
+ - name: Install dependencies
+ run: bundle install
+ - name: Install Appraisal dependencies
+ run: bundle exec appraisal install
+ - name: Setup the environment
+ run: cp .sample.env .env
+ - run: cp spec/example_app/config/database.yml.sample spec/example_app/config/database.yml
+ - name: Setup the database
+ run: bundle exec rake db:setup
+ - name: Run tests
+ run: bundle exec rspec
+ - name: Appraise Rails 6.0
+ run: bundle exec appraisal rails60 rspec
+ if: ${{ matrix.ruby <= '3.0' }}
+ - name: Appraise Rails 6.1
+ run: bundle exec appraisal rails61 rspec
+ - name: Appraisal Rails 7.0
+ run: bundle exec appraisal rails70 rspec
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 534b89c356..486d098459 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -42,6 +42,7 @@ bundle exec rails s
```
This will start the application defined in `spec/example_app`.
+You can view the `example_app` in the browser by navigating to `/admin`
## Repository Structure
diff --git a/Gemfile b/Gemfile
index 2c04704608..d11178696f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -20,7 +20,7 @@ group :development, :test do
gem "byebug"
gem "dotenv-rails"
gem "factory_bot_rails"
- gem "i18n-tasks", "1.0.12"
+ gem "i18n-tasks", "1.0.13"
gem "pry"
gem "yard"
end
@@ -31,10 +31,9 @@ group :test do
gem "database_cleaner"
gem "formulaic"
gem "launchy"
- gem "selenium-webdriver", "= 4.9.0"
+ gem "selenium-webdriver"
gem "shoulda-matchers"
gem "timecop"
- gem "webdrivers"
gem "webmock"
gem "webrick"
gem "xpath", "3.2.0"
diff --git a/Gemfile.lock b/Gemfile.lock
index 7f0f7f16ae..5c605a40de 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -2,11 +2,11 @@ PATH
remote: .
specs:
administrate (0.19.0)
- actionpack (>= 5.0)
- actionview (>= 5.0)
- activerecord (>= 5.0)
- jquery-rails (>= 4.0)
- kaminari (>= 1.0)
+ actionpack (>= 6.0, < 8.0)
+ actionview (>= 6.0, < 8.0)
+ activerecord (>= 6.0, < 8.0)
+ jquery-rails (~> 4.6.0)
+ kaminari (~> 1.2.2)
sassc-rails (~> 2.1)
selectize-rails (~> 0.6)
@@ -82,7 +82,7 @@ GEM
public_suffix (>= 2.0.2, < 6.0)
administrate-field-image (1.2.0)
administrate (>= 0.2.0.rc1)
- ammeter (1.1.5)
+ ammeter (1.1.6)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-rails (>= 2.2)
@@ -92,7 +92,7 @@ GEM
thor (>= 0.14.0)
ast (2.4.2)
awesome_print (1.9.2)
- better_html (2.0.1)
+ better_html (2.0.2)
actionview (>= 6.0)
activesupport (>= 6.0)
ast (~> 2.0)
@@ -129,12 +129,12 @@ GEM
railties (>= 3.2)
erubi (1.12.0)
execjs (2.8.1)
- factory_bot (6.2.1)
+ factory_bot (6.4.5)
activesupport (>= 5.0.0)
- factory_bot_rails (6.2.0)
- factory_bot (~> 6.2.0)
+ factory_bot_rails (6.4.3)
+ factory_bot (~> 6.4)
railties (>= 5.0.0)
- faker (3.2.1)
+ faker (3.2.2)
i18n (>= 1.8.11, < 2)
faraday (2.7.4)
faraday-net_http (>= 2.0, < 3.1)
@@ -146,20 +146,20 @@ GEM
capybara
i18n
front_matter_parser (1.0.1)
- globalid (1.1.0)
- activesupport (>= 5.0)
+ globalid (1.2.1)
+ activesupport (>= 6.1)
hashdiff (1.0.1)
highline (2.1.0)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
- i18n-tasks (1.0.12)
+ i18n-tasks (1.0.13)
activesupport (>= 4.0.2)
ast (>= 2.1.0)
better_html (>= 1.0, < 3.0)
erubi
highline (>= 2.0.0)
i18n
- parser (>= 2.2.3.0)
+ parser (>= 3.2.2.1)
rails-i18n
rainbow (>= 2.2.2, < 4.0)
terminal-table (>= 1.5.1)
@@ -185,7 +185,7 @@ GEM
kgio (2.11.4)
launchy (2.5.2)
addressable (~> 2.8)
- loofah (2.21.3)
+ loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@@ -197,8 +197,8 @@ GEM
matrix (0.4.2)
method_source (1.0.0)
mini_mime (1.1.2)
- mini_portile2 (2.8.2)
- minitest (5.19.0)
+ mini_portile2 (2.8.5)
+ minitest (5.20.0)
net-imap (0.3.6)
date
net-protocol
@@ -209,20 +209,21 @@ GEM
net-smtp (0.3.3)
net-protocol
nio4r (2.5.9)
- nokogiri (1.15.2)
+ nokogiri (1.16.0)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
- parser (3.2.1.1)
+ parser (3.2.2.4)
ast (~> 2.4.1)
- pg (1.5.3)
+ racc
+ pg (1.5.4)
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (5.0.3)
pundit (2.3.1)
activesupport (>= 3.0.0)
- racc (1.7.1)
- rack (2.2.7)
+ racc (1.7.3)
+ rack (2.2.8)
rack-test (2.1.0)
rack (>= 1.3)
rack-timeout (0.6.3)
@@ -240,13 +241,14 @@ GEM
activesupport (= 7.0.7.2)
bundler (>= 1.15.0)
railties (= 7.0.7.2)
- rails-dom-testing (2.0.3)
- activesupport (>= 4.2.0)
+ rails-dom-testing (2.2.0)
+ activesupport (>= 5.0.0)
+ minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
- rails-i18n (7.0.6)
+ rails-i18n (7.0.8)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
railties (7.0.7.2)
@@ -258,27 +260,27 @@ GEM
zeitwerk (~> 2.5)
rainbow (3.1.1)
raindrops (0.20.1)
- rake (13.0.6)
+ rake (13.1.0)
redcarpet (3.6.0)
regexp_parser (2.8.1)
rexml (3.2.6)
- rspec-core (3.12.1)
+ rspec-core (3.12.2)
rspec-support (~> 3.12.0)
- rspec-expectations (3.12.2)
+ rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
- rspec-mocks (3.12.4)
+ rspec-mocks (3.12.6)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
- rspec-rails (6.0.1)
+ rspec-rails (6.1.0)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
- rspec-core (~> 3.11)
- rspec-expectations (~> 3.11)
- rspec-mocks (~> 3.11)
- rspec-support (~> 3.11)
- rspec-support (3.12.0)
+ rspec-core (~> 3.12)
+ rspec-expectations (~> 3.12)
+ rspec-mocks (~> 3.12)
+ rspec-support (~> 3.12)
+ rspec-support (3.12.1)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
sassc (2.4.0)
@@ -290,13 +292,13 @@ GEM
sprockets-rails
tilt
selectize-rails (0.12.6)
- selenium-webdriver (4.9.0)
+ selenium-webdriver (4.16.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
sentry-raven (3.1.2)
faraday (>= 1.0)
- shoulda-matchers (5.3.0)
+ shoulda-matchers (6.0.0)
activesupport (>= 5.2.0)
smart_properties (1.17.0)
sprockets (4.2.0)
@@ -308,7 +310,7 @@ GEM
sprockets (>= 3.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
- thor (1.2.2)
+ thor (1.3.0)
tilt (2.2.0)
timecop (0.9.8)
timeout (0.4.0)
@@ -316,27 +318,23 @@ GEM
concurrent-ruby (~> 1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
- unicode-display_width (2.4.2)
+ unicode-display_width (2.5.0)
unicorn (6.1.0)
kgio (~> 2.6)
raindrops (~> 0.7)
- webdrivers (5.2.0)
- nokogiri (~> 1.6)
- rubyzip (>= 1.3.0)
- selenium-webdriver (~> 4.0)
webmock (3.19.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.8.1)
- websocket (1.2.9)
+ websocket (1.2.10)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
yard (0.9.34)
- zeitwerk (2.6.8)
+ zeitwerk (2.6.12)
PLATFORMS
ruby
@@ -356,7 +354,7 @@ DEPENDENCIES
formulaic
front_matter_parser
globalid
- i18n-tasks (= 1.0.12)
+ i18n-tasks (= 1.0.13)
kaminari-i18n
launchy
pg
@@ -364,13 +362,12 @@ DEPENDENCIES
pundit
rack-timeout
redcarpet
- selenium-webdriver (= 4.9.0)
+ selenium-webdriver
sentry-raven
shoulda-matchers
timecop
uglifier
unicorn
- webdrivers
webmock
webrick
xpath (= 3.2.0)
@@ -380,4 +377,4 @@ RUBY VERSION
ruby 3.2.2p53
BUNDLED WITH
- 2.3.10
+ 2.4.22
diff --git a/LICENSE.md b/LICENSE.md
index c9ae523ac0..1e4eed3da2 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2015-2019 thoughtbot, inc.
+Copyright (c) 2015-2023 thoughtbot, inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 1081766bc9..a086a8cdee 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@ community](https://github.com/thoughtbot/administrate/graphs/contributors).
## License
-administrate is Copyright © 2015-2019 thoughtbot.
+administrate is Copyright © 2015-2023 thoughtbot.
It is free software, and may be redistributed under the terms specified in the
[LICENSE](/LICENSE.md) file.
diff --git a/administrate.gemspec b/administrate.gemspec
index e05c5f7cab..1ea64c0c4b 100644
--- a/administrate.gemspec
+++ b/administrate.gemspec
@@ -13,9 +13,9 @@ Gem::Specification.new do |s|
s.files = Dir["{app,lib,docs}/**/*", "config/locales/**/*", "LICENSE", "Rakefile"]
- s.add_dependency "actionpack", ">= 5.0"
- s.add_dependency "actionview", ">= 5.0"
- s.add_dependency "activerecord", ">= 5.0"
+ s.add_dependency "actionpack", ">= 6.0", "< 8.0"
+ s.add_dependency "actionview", ">= 6.0", "< 8.0"
+ s.add_dependency "activerecord", ">= 6.0", "< 8.0"
s.add_dependency "jquery-rails", ">= 4.0"
s.add_dependency "kaminari", ">= 1.0"
diff --git a/app/assets/stylesheets/administrate/components/_field-unit.scss b/app/assets/stylesheets/administrate/components/_field-unit.scss
index 235fdca96d..93a88fc3a3 100644
--- a/app/assets/stylesheets/administrate/components/_field-unit.scss
+++ b/app/assets/stylesheets/administrate/components/_field-unit.scss
@@ -2,6 +2,7 @@
@include administrate-clearfix;
align-items: center;
display: flex;
+ flex-wrap: wrap;
margin-bottom: $base-spacing;
position: relative;
width: 100%;
@@ -25,6 +26,12 @@
}
}
+.field-unit__hint {
+ font-size: 90%;
+ margin-left: calc(15% + 2rem);
+ width: 100%;
+}
+
.field-unit--nested {
border: $base-border;
margin-left: 7.5%;
diff --git a/app/controllers/concerns/administrate/punditize.rb b/app/controllers/concerns/administrate/punditize.rb
index 41c81ef03c..9a3841985f 100644
--- a/app/controllers/concerns/administrate/punditize.rb
+++ b/app/controllers/concerns/administrate/punditize.rb
@@ -9,31 +9,27 @@ module Punditize
include Pundit
end
- included do
- private
-
- def policy_namespace
- []
- end
+ private
- def scoped_resource
- namespaced_scope = policy_namespace + [super]
- policy_scope!(pundit_user, namespaced_scope)
- end
+ def policy_namespace
+ []
+ end
- def authorize_resource(resource)
- namespaced_resource = policy_namespace + [resource]
- authorize namespaced_resource
- end
+ def scoped_resource
+ namespaced_scope = policy_namespace + [super]
+ policy_scope!(pundit_user, namespaced_scope)
+ end
- def authorized_action?(resource, action)
- namespaced_resource = policy_namespace + [resource]
- policy = Pundit.policy!(pundit_user, namespaced_resource)
- policy.send("#{action}?".to_sym)
- end
+ def authorize_resource(resource)
+ namespaced_resource = policy_namespace + [resource]
+ authorize namespaced_resource
end
- private
+ def authorized_action?(resource, action)
+ namespaced_resource = policy_namespace + [resource]
+ policy = Pundit.policy!(pundit_user, namespaced_resource)
+ policy.send("#{action}?".to_sym)
+ end
def policy_scope!(user, scope)
policy_scope_class = Pundit::PolicyFinder.new(scope).scope!
diff --git a/app/views/administrate/application/_form.html.erb b/app/views/administrate/application/_form.html.erb
index 5d798eaa10..d08883679a 100644
--- a/app/views/administrate/application/_form.html.erb
+++ b/app/views/administrate/application/_form.html.erb
@@ -36,6 +36,13 @@ and renders all form fields for a resource's editable attributes.
<% page.attributes(controller.action_name).each do |attribute| -%>
<%= render_field attribute, f: f %>
+
+ <% hint_key = "administrate.field_hints.#{page.resource_name}.#{attribute.name}" %>
+ <% if I18n.exists?(hint_key) -%>
+
+ <%= I18n.t(hint_key) %>
+
+ <% end -%>
<% end -%>
diff --git a/docs/customizing_dashboards.md b/docs/customizing_dashboards.md
index d634444de1..fcbd2bf3a6 100644
--- a/docs/customizing_dashboards.md
+++ b/docs/customizing_dashboards.md
@@ -113,6 +113,9 @@ association `belongs_to :country`, from your model.
**Field::HasMany**
+`:collection_attributes` - Set the columns to display in the show view.
+Default is COLLECTION_ATTRIBUTES in dashboard.
+
`:limit` - The number of resources (paginated) to display in the show view. To disable pagination,
set this to `0` or `false`. Default is `5`.
@@ -168,8 +171,7 @@ more results than expected. Default is `false`.
and works by by passing a hash that includes the formatter (`formatter`) and
the options for the formatter (`formatter_options`). Defaults to the locale's
delimiter when `formatter_options` does not include a `delimiter`. See the
-example below. Note that currently only
-`ActiveSupport::NumberHelper.number_to_delimited` is supported.
+example below. All helpers from `ActiveSupport::NumberHelper` are supported.
For example, you might use the following to display U.S. currency:
@@ -383,3 +385,21 @@ FORM_ATTRIBUTES_EDIT = [
```
Or for custom action with constant name `"FORM_ATTRIBUTES_#{action.upcase}"`
+
+### Form Fields' Hints
+
+You can show a brief text element below an input field by setting the
+corresponding translation key using the path:
+
+`administrate.field_hints.#{model_name}.#{field_name}`
+
+For example, with a Customer dashboard with an email field you can add a
+string value that will be used as text hint:
+
+```yml
+en:
+ administrate:
+ field_hints:
+ customer:
+ email: field_hint
+```
diff --git a/docs/getting_started.md b/docs/getting_started.md
index 820d7461ea..b5680b8a99 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -3,7 +3,7 @@ title: Getting Started
---
Administrate is released as a Ruby gem, and can be installed on Rails
-applications version 6.0 or greater. We support Ruby 2.7 and up.
+applications version 6.0 or greater. We support Ruby 3.0 and up.
First, add the following to your Gemfile:
diff --git a/gemfiles/pundit21.gemfile b/gemfiles/pundit21.gemfile
index d6856af0b6..59b22f0547 100644
--- a/gemfiles/pundit21.gemfile
+++ b/gemfiles/pundit21.gemfile
@@ -21,7 +21,7 @@ group :development, :test do
gem "byebug"
gem "dotenv-rails"
gem "factory_bot_rails"
- gem "i18n-tasks", "1.0.12"
+ gem "i18n-tasks", "1.0.13"
gem "pry"
gem "yard"
end
@@ -32,10 +32,9 @@ group :test do
gem "database_cleaner"
gem "formulaic"
gem "launchy"
- gem "selenium-webdriver", "= 4.9.0"
+ gem "selenium-webdriver"
gem "shoulda-matchers"
gem "timecop"
- gem "webdrivers"
gem "webmock"
gem "webrick"
gem "xpath", "3.2.0"
diff --git a/gemfiles/rails60.gemfile b/gemfiles/rails60.gemfile
index 2bbd77b6de..30765854fe 100644
--- a/gemfiles/rails60.gemfile
+++ b/gemfiles/rails60.gemfile
@@ -22,7 +22,7 @@ group :development, :test do
gem "byebug"
gem "dotenv-rails"
gem "factory_bot_rails"
- gem "i18n-tasks", "1.0.12"
+ gem "i18n-tasks", "1.0.13"
gem "pry"
gem "yard"
end
@@ -33,10 +33,9 @@ group :test do
gem "database_cleaner"
gem "formulaic"
gem "launchy"
- gem "selenium-webdriver", "= 4.9.0"
+ gem "selenium-webdriver"
gem "shoulda-matchers"
gem "timecop"
- gem "webdrivers"
gem "webmock"
gem "webrick"
gem "xpath", "3.2.0"
diff --git a/gemfiles/rails61.gemfile b/gemfiles/rails61.gemfile
index d65debfe89..2bd08c25ef 100644
--- a/gemfiles/rails61.gemfile
+++ b/gemfiles/rails61.gemfile
@@ -22,7 +22,7 @@ group :development, :test do
gem "byebug"
gem "dotenv-rails"
gem "factory_bot_rails"
- gem "i18n-tasks", "1.0.12"
+ gem "i18n-tasks", "1.0.13"
gem "pry"
gem "yard"
end
@@ -33,10 +33,9 @@ group :test do
gem "database_cleaner"
gem "formulaic"
gem "launchy"
- gem "selenium-webdriver", "= 4.9.0"
+ gem "selenium-webdriver"
gem "shoulda-matchers"
gem "timecop"
- gem "webdrivers"
gem "webmock"
gem "webrick"
gem "xpath", "3.2.0"
diff --git a/gemfiles/rails70.gemfile b/gemfiles/rails70.gemfile
index 20d902bb9b..5655914283 100644
--- a/gemfiles/rails70.gemfile
+++ b/gemfiles/rails70.gemfile
@@ -22,7 +22,7 @@ group :development, :test do
gem "byebug"
gem "dotenv-rails"
gem "factory_bot_rails"
- gem "i18n-tasks", "1.0.12"
+ gem "i18n-tasks", "1.0.13"
gem "pry"
gem "yard"
end
@@ -33,10 +33,9 @@ group :test do
gem "database_cleaner"
gem "formulaic"
gem "launchy"
- gem "selenium-webdriver", "= 4.9.0"
+ gem "selenium-webdriver"
gem "shoulda-matchers"
gem "timecop"
- gem "webdrivers"
gem "webmock"
gem "webrick"
gem "xpath", "3.2.0"
diff --git a/lib/administrate/field/has_many.rb b/lib/administrate/field/has_many.rb
index 13ddebd9c7..a5b7275603 100644
--- a/lib/administrate/field/has_many.rb
+++ b/lib/administrate/field/has_many.rb
@@ -22,7 +22,11 @@ def self.permitted_attribute(attr, _options = {})
end
def associated_collection(order = self.order)
- Administrate::Page::Collection.new(associated_dashboard, order: order)
+ Administrate::Page::Collection.new(
+ associated_dashboard,
+ order: order,
+ collection_attributes: options[:collection_attributes],
+ )
end
def attribute_key
diff --git a/lib/administrate/field/number.rb b/lib/administrate/field/number.rb
index d4878456e0..433795d12e 100644
--- a/lib/administrate/field/number.rb
+++ b/lib/administrate/field/number.rb
@@ -38,14 +38,8 @@ def format(result)
formatter = options[:format][:formatter]
formatter_options = options[:format][:formatter_options].to_h
- case formatter
- when :number_to_delimited
- ActiveSupport::NumberHelper.number_to_delimited(
- result, **formatter_options
- )
- else
- result
- end
+ ActiveSupport::NumberHelper.
+ try(formatter, result, **formatter_options) || result
end
end
end
diff --git a/lib/administrate/order.rb b/lib/administrate/order.rb
index c819c15248..8ac2ae5df3 100644
--- a/lib/administrate/order.rb
+++ b/lib/administrate/order.rb
@@ -10,9 +10,9 @@ def apply(relation)
return order_by_association(relation) unless
reflect_association(relation).nil?
- order = "#{relation.table_name}.#{attribute} #{direction}"
+ order = relation.arel_table[attribute].public_send(direction)
- return relation.reorder(Arel.sql(order)) if
+ return relation.reorder(order) if
column_exist?(relation, attribute)
relation
@@ -66,11 +66,11 @@ def order_by_association(relation)
def order_by_count(relation)
klass = reflect_association(relation).klass
- query = "COUNT(#{klass.table_name}.#{klass.primary_key}) #{direction}"
+ query = klass.arel_table[klass.primary_key].count.public_send(direction)
relation.
left_joins(attribute.to_sym).
group(:id).
- reorder(Arel.sql(query))
+ reorder(query)
end
def order_by_belongs_to(relation)
@@ -92,15 +92,15 @@ def order_by_has_one(relation)
def order_by_attribute(relation)
relation.joins(
attribute.to_sym,
- ).reorder(Arel.sql(order_by_attribute_query))
+ ).reorder(order_by_attribute_query)
end
def order_by_id(relation)
- relation.reorder(Arel.sql(order_by_id_query(relation)))
+ relation.reorder(order_by_id_query(relation))
end
def order_by_association_id(relation)
- relation.reorder(Arel.sql(order_by_association_id_query))
+ relation.reorder(order_by_association_id_query)
end
def ordering_by_association_column?(relation)
@@ -115,15 +115,16 @@ def column_exist?(table, column_name)
end
def order_by_id_query(relation)
- "#{relation.table_name}.#{foreign_key(relation)} #{direction}"
+ relation.arel_table[foreign_key(relation)].public_send(direction)
end
def order_by_association_id_query
- "#{association_table_name}.id #{direction}"
+ Arel::Table.new(association_table_name)[:id].public_send(direction)
end
def order_by_attribute_query
- "#{association_table_name}.#{association_attribute} #{direction}"
+ table = Arel::Table.new(association_table_name)
+ table[association_attribute].public_send(direction)
end
def relation_type(relation)
diff --git a/lib/administrate/page/collection.rb b/lib/administrate/page/collection.rb
index dfefbb8a8f..60ad26ef79 100644
--- a/lib/administrate/page/collection.rb
+++ b/lib/administrate/page/collection.rb
@@ -4,6 +4,7 @@ module Administrate
module Page
class Collection < Page::Base
def attribute_names
+ options.fetch(:collection_attributes, nil) ||
dashboard.collection_attributes
end
diff --git a/spec/example_app/app/dashboards/order_dashboard.rb b/spec/example_app/app/dashboards/order_dashboard.rb
index c63bce7f48..0d85d91c8b 100644
--- a/spec/example_app/app/dashboards/order_dashboard.rb
+++ b/spec/example_app/app/dashboards/order_dashboard.rb
@@ -11,7 +11,9 @@ class OrderDashboard < Administrate::BaseDashboard
address_state: Field::String,
address_zip: Field::String,
customer: Field::BelongsTo.with_options(order: "name"),
- line_items: Field::HasMany,
+ line_items: Field::HasMany.with_options(
+ collection_attributes: %i[product quantity unit_price total_price],
+ ),
total_price: Field::Number.with_options(prefix: "$", decimals: 2),
shipped_at: Field::DateTime,
payments: Field::HasMany,
diff --git a/spec/features/form_spec.rb b/spec/features/form_spec.rb
index b92e1bd29c..2132c51752 100644
--- a/spec/features/form_spec.rb
+++ b/spec/features/form_spec.rb
@@ -105,4 +105,27 @@
expect(element_selections.first("option").value).not_to eq("")
end
end
+
+ context "fields hints" do
+ it "displays a field hint element within the field unit" do
+ field_hint = "The typology of customer"
+
+ translations = {
+ administrate: {
+ field_hints: {
+ customer: {
+ kind: field_hint,
+ },
+ },
+ },
+ }
+
+ with_translations(:en, translations) do
+ visit new_admin_customer_path
+
+ css_hint_element = ".field-unit > .field-unit__hint"
+ expect(page).to have_css(css_hint_element, text: field_hint)
+ end
+ end
+ end
end
diff --git a/spec/features/show_page_spec.rb b/spec/features/show_page_spec.rb
index 1e1f40e2d7..a069028bf6 100644
--- a/spec/features/show_page_spec.rb
+++ b/spec/features/show_page_spec.rb
@@ -265,6 +265,21 @@
end
end
+ it "displays specified collection_attributes for the has_many association" do
+ line_item = create(:line_item)
+
+ visit admin_order_path(line_item.order)
+
+ within(table_for_attribute(:line_items)) do
+ columns = all("tr th").map do |e|
+ e[:class]&.split&.last&.split("--line_item_")&.last
+ end
+ expect(%w[product quantity unit_price total_price]).to(
+ eq(columns.first(4)),
+ )
+ end
+ end
+
def ids_in_table
all("tr td:first-child").map(&:text).map(&:to_i)
end
diff --git a/spec/lib/administrate/order_spec.rb b/spec/lib/administrate/order_spec.rb
index e76c2041b0..a1e4d87c19 100644
--- a/spec/lib/administrate/order_spec.rb
+++ b/spec/lib/administrate/order_spec.rb
@@ -1,4 +1,4 @@
-require "active_record"
+require "rails_helper"
require "administrate/order"
describe Administrate::Order do
@@ -37,7 +37,9 @@
ordered = order.apply(relation)
- expect(relation).to have_received(:reorder).with("table_name.name asc")
+ expect(relation).to have_received(:reorder).with(
+ to_sql('"table_name"."name" ASC'),
+ )
expect(ordered).to eq(relation)
end
@@ -48,7 +50,9 @@
ordered = order.apply(relation)
- expect(relation).to have_received(:reorder).with("table_name.name desc")
+ expect(relation).to have_received(:reorder).with(
+ to_sql('"table_name"."name" DESC'),
+ )
expect(ordered).to eq(relation)
end
@@ -59,7 +63,9 @@
ordered = order.apply(relation)
- expect(relation).to have_received(:reorder).with("table_name.name asc")
+ expect(relation).to have_received(:reorder).with(
+ to_sql('"table_name"."name" ASC'),
+ )
expect(ordered).to eq(relation)
end
end
@@ -69,7 +75,11 @@
order = Administrate::Order.new(:name)
relation = relation_with_association(
:has_many,
- klass: double(table_name: "users", primary_key: "uid"),
+ klass: double(
+ table_name: "users",
+ arel_table: Arel::Table.new("users"),
+ primary_key: "uid",
+ ),
)
allow(relation).to receive(:reorder).and_return(relation)
allow(relation).to receive(:left_joins).and_return(relation)
@@ -79,7 +89,9 @@
expect(relation).to have_received(:left_joins).with(:name)
expect(relation).to have_received(:group).with(:id)
- expect(relation).to have_received(:reorder).with("COUNT(users.uid) asc")
+ expect(relation).to have_received(:reorder).with(
+ to_sql('COUNT("users"."uid") ASC'),
+ )
expect(ordered).to eq(relation)
end
end
@@ -96,7 +108,7 @@
ordered = order.apply(relation)
expect(relation).to have_received(:reorder).with(
- "table_name.some_foreign_key asc",
+ to_sql('"table_name"."some_foreign_key" ASC'),
)
expect(ordered).to eq(relation)
end
@@ -120,7 +132,7 @@
ordered = order.apply(relation)
expect(relation).to have_received(:reorder).with(
- "users.name asc",
+ to_sql('"users"."name" ASC'),
)
expect(ordered).to eq(relation)
end
@@ -146,7 +158,7 @@
ordered = order.apply(relation)
expect(relation).to have_received(:reorder).with(
- "table_name.belongs_to_id asc",
+ to_sql('"table_name"."belongs_to_id" ASC'),
)
expect(ordered).to eq(relation)
end
@@ -164,7 +176,7 @@
ordered = order.apply(relation)
expect(relation).to have_received(:reorder).with(
- "users.id asc",
+ to_sql('"users"."id" ASC'),
)
expect(ordered).to eq(relation)
end
@@ -188,7 +200,7 @@
ordered = order.apply(relation)
expect(relation).to have_received(:reorder).with(
- "users.name asc",
+ to_sql('"users"."name" ASC'),
)
expect(ordered).to eq(relation)
end
@@ -214,7 +226,7 @@
ordered = order.apply(relation)
expect(relation).to have_received(:reorder).with(
- "users.id asc",
+ to_sql('"users"."id" ASC'),
)
expect(ordered).to eq(relation)
end
@@ -311,6 +323,7 @@ def relation_with_column(column)
klass: double(reflect_on_association: nil),
columns_hash: { column.to_s => :column_info },
table_name: "table_name",
+ arel_table: Arel::Table.new("table_name"),
)
end
@@ -329,6 +342,7 @@ def relation_with_association(
),
),
table_name: "table_name",
+ arel_table: Arel::Table.new("table_name"),
)
end
end
diff --git a/spec/lib/fields/number_spec.rb b/spec/lib/fields/number_spec.rb
index e6810c914e..9ce19b33cc 100644
--- a/spec/lib/fields/number_spec.rb
+++ b/spec/lib/fields/number_spec.rb
@@ -112,6 +112,15 @@
end
end
+ context "when `formatter: :number_to_currency`" do
+ it "includes the currency" do
+ with_currency = number_with_options(
+ 100, format: { formatter: :number_to_currency }
+ )
+ expect(with_currency.to_s).to eq("$100.00")
+ end
+ end
+
context "when passed incorrect `formatter`" do
it "works" do
thousand = number_with_options(1_000, format: { formatter: :rubbish })
diff --git a/spec/support/matchers/to_sql.rb b/spec/support/matchers/to_sql.rb
new file mode 100644
index 0000000000..78ca557bcd
--- /dev/null
+++ b/spec/support/matchers/to_sql.rb
@@ -0,0 +1,5 @@
+RSpec::Matchers.define :to_sql do |sql|
+ match do |actual|
+ actual.to_sql == sql
+ end
+end
diff --git a/spec/support/webdrivers.rb b/spec/support/webdrivers.rb
index 08fdf3dee2..500ed1181f 100644
--- a/spec/support/webdrivers.rb
+++ b/spec/support/webdrivers.rb
@@ -1,7 +1,5 @@
require "selenium/webdriver"
-Webdrivers::Chromedriver.required_version = "114.0.5735.90"
-
Capybara.register_driver :chrome do |app|
Capybara::Selenium::Driver.new(app, browser: :chrome)
end
diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb
index dbca72cb87..0b515bc672 100644
--- a/spec/support/webmock.rb
+++ b/spec/support/webmock.rb
@@ -1,11 +1,7 @@
require "webmock/rspec"
-# Allow downloading webdrivers for Selenium
-driver_hosts = Webdrivers::Common.subclasses.
- map { |driver| URI(driver.base_url).host }
-
# Downloading the Firefox driver involves a redirect
-driver_hosts += ["github-releases.githubusercontent.com"]
+driver_hosts = ["github-releases.githubusercontent.com"]
# Additionally, avoid conflict with Selenium (localhost)
WebMock.disable_net_connect!(allow_localhost: true, allow: driver_hosts)