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)