diff --git a/.document b/.document
deleted file mode 100644
index ecf3673194b..00000000000
--- a/.document
+++ /dev/null
@@ -1,5 +0,0 @@
-README.rdoc
-lib/**/*.rb
-bin/*
-features/**/*.feature
-LICENSE
diff --git a/.gherkin-lintrc b/.gherkin-lintrc
new file mode 100644
index 00000000000..8afd8ef7f5b
--- /dev/null
+++ b/.gherkin-lintrc
@@ -0,0 +1,39 @@
+{
+ "no-files-without-scenarios" : "off",
+ "no-unnamed-features": "on",
+ "no-unnamed-scenarios": "on",
+ "no-dupe-feature-names": "on",
+ "no-partially-commented-tag-lines": "on",
+ "indentation": [
+ "on", {
+ "Feature": 0,
+ "Background": 2,
+ "Scenario": 2,
+ "Step": 2,
+ "Examples": 0,
+ "example": 2,
+ "given": 4,
+ "when": 4,
+ "then": 4,
+ "and": 4,
+ "but": 4,
+ "feature tag": 0,
+ "scenario tag": 2
+ }
+ ],
+ "no-trailing-spaces": "on",
+ "new-line-at-eof": ["on", "yes"],
+ "no-multiple-empty-lines": "on",
+ "no-empty-file": "on",
+ "no-scenario-outlines-without-examples": "on",
+ "name-length": ["on", {"Feature": 50, "Scenario": 85, "Step": 115}],
+ "no-restricted-tags": ["on", {"tags": ["@watch", "@wip"]}],
+ "use-and": "on",
+ "no-duplicate-tags": "on",
+ "no-superfluous-tags": "on",
+ "no-homogenous-tags": "on",
+ "one-space-between-tags": "on",
+ "no-unused-variables": "on",
+ "no-background-only-scenario": "on",
+ "no-empty-background": "on"
+}
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000000..f25b1ebbd6e
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,5 @@
+---
+
+liberapay: Active-Admin
+open_collective: activeadmin
+tidelift: rubygems/activeadmin
diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
new file mode 100644
index 00000000000..a65fee43345
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -0,0 +1,38 @@
+---
+name: Bug report
+about: If you've already asked for help with a problem and confirmed something is broken with ActiveAdmin itself, create a bug report.
+title: ''
+labels: ''
+assignees: ''
+---
+
+
+
+Describe your issue with a **clear title and description**. Make sure to include
+as much relevant information as possible, including a code sample or failing
+test that demonstrates the expected behavior, as well as your system
+configuration. Your goal should be to make it easy for yourself - and others -
+to reproduce the bug and figure out a fix.
+
+### Expected behavior
+
+What do you think should happen?
+
+### Actual behavior
+
+What actually happens?
+
+### How to reproduce
+
+Having a way to reproduce your issue will help people confirm, investigate,
+and ultimately fix your issue. You can do this by providing an executable test
+case. To make this process easier, please use [our bug report template script].
+
+Copy the content of the appropriate template into an `.rb` file and make the
+necessary changes to demonstrate the issue. You can execute it by running
+`ruby the_file.rb` in your terminal. If all goes well, you should see your test
+case failing.
+
+[our bug report template script]: https://github.com/activeadmin/activeadmin/blob/master/tasks/bug_report_template.rb
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000000..3202933f390
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,14 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Get Help
+ url: https://github.com/activeadmin/activeadmin/discussions/new?category=help
+ about: If you can't get something to work the way you expect, open a question in our discussion forums.
+ - name: Feature Request
+ url: https://github.com/activeadmin/activeadmin/discussions/new?category=ideas
+ about: Suggest any ideas you have using our discussion forums.
+ - name: Documentation Issue
+ url: https://github.com/activeadmin/activeadmin/pulls
+ about: For documentation improvements, feel free to create a pull request.
+ - name: Localization Issue
+ url: https://github.com/activeadmin/activeadmin/pulls
+ about: For any localization updates, create a pull request as we rely entirely on the community for these.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000000..f1294ef22ba
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,20 @@
+
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 00000000000..80219a6c70f
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,156 @@
+# Active Admin - GitHub Copilot Instructions
+
+## Project Overview
+
+Active Admin is a Ruby on Rails framework for creating elegant backends for website administration. It provides a DSL for developers to quickly create good-looking administration interfaces.
+
+## Technology Stack
+
+- **Backend**: Ruby on Rails (currently Rails ~> 8.1.0)
+- **Frontend**: JavaScript (ES6+), Tailwind CSS, Flowbite
+- **Testing**: RSpec (unit tests), Cucumber (feature tests), Capybara (integration tests)
+- **Build Tools**: Rollup (JavaScript bundling), cssbundling-rails
+- **Key Dependencies**: Devise (authentication), Ransack (search), Formtastic (forms), Kaminari (pagination)
+
+## Ruby Conventions
+
+- Minimum Ruby version: 3.3+ (required)
+- Minimum Rails version: 7.2+ (required by gemspec)
+- Current development uses Rails ~> 8.1.0
+- Follow RuboCop style guide (configuration in `.rubocop.yml`)
+- RuboCop plugins enabled: capybara, packaging, performance, rails, rspec
+- Use frozen string literals: `# frozen_string_literal: true` at the top of Ruby files
+
+## JavaScript Conventions
+
+- Use ES6+ modern JavaScript syntax
+- Follow ESLint configuration (see `eslint.config.js`)
+- JavaScript source files are in `app/javascript/`
+- Build JavaScript with: `npm run build`
+- Lint JavaScript with: `npm run lint`
+
+## Testing Guidelines
+
+### Ruby Tests (RSpec)
+- Unit tests are in `spec/unit/`
+- Request specs are in `spec/requests/`
+- Helper specs are in `spec/helpers/`
+- Run RSpec tests: `bundle exec rspec`
+
+### Feature Tests (Cucumber)
+- Cucumber features are in `features/`
+- Uses Capybara with Cuprite (headless Chrome)
+- Cucumber scenarios require Chrome to be installed
+- Run Cucumber tests: `bundle exec cucumber`
+- Lint Gherkin files: `npm run gherkin-lint`
+
+### Running All Tests
+- Run the complete test suite: `bin/rake`
+- Tests run against a sample Rails app generated in `tmp/test_apps/`
+
+## Building and Development
+
+### Setup
+```bash
+bundle install
+yarn install
+```
+
+Note: The `bin/rake local server` command requires foreman, which it will invoke automatically. Install with `gem install foreman` if needed.
+
+### Testing Against Different Rails Versions
+```bash
+# Available versions: rails_72, rails_80
+export BUNDLE_GEMFILE=gemfiles/rails_72/Gemfile
+```
+
+### Local Development Server
+```bash
+bin/rake local server
+# Visit http://localhost:3000/admin
+# Login: admin@example.com / password
+```
+
+### Other Local Commands
+```bash
+bin/rake local console # Rails console
+bin/rake local db:migrate # Run migrations
+```
+
+## Code Organization
+
+- `lib/active_admin/` - Core framework code
+- `app/` - Rails application components (controllers, helpers, views, assets)
+- `spec/` - RSpec tests
+- `features/` - Cucumber feature tests
+- `docs/` - VitePress documentation (run with `npm run docs:dev`)
+
+## Important Guidelines
+
+1. **Minimal Changes**: Make surgical, precise changes. Don't refactor unrelated code.
+2. **Backward Compatibility**: Active Admin is a widely-used gem. Maintain backward compatibility unless explicitly breaking changes are intended.
+3. **Test Coverage**: Include tests for new features. Prefer RSpec specs (especially request specs) over Cucumber features when possible, as specs are easier to work with and maintain.
+4. **Documentation**: Update documentation in `docs/` if adding user-facing features.
+5. **Internationalization**: Support i18n - translation files are in `config/locales/`
+6. **Security**: This is an administration framework so be extra cautious with security implications.
+7. **Code Quality**: Always run ALL relevant linters before submitting any PR to ensure code follows project style guidelines:
+ - Ruby code: `bundle exec rubocop`
+ - JavaScript code: `npm run lint`
+ - Gherkin files: `npm run gherkin-lint`
+ - Run these linters BEFORE requesting code review or submitting the PR.
+
+## Contributing Workflow
+
+1. Create feature request discussion before starting significant new features
+2. Fork and create a descriptive branch
+3. Ensure tests pass: `bin/rake`
+4. **Run ALL linters BEFORE submitting PR**:
+ - Ruby code: `bundle exec rubocop`
+ - JavaScript code: `npm run lint`
+ - Gherkin files: `npm run gherkin-lint`
+5. View changes in browser: `bin/rake local server`
+6. Submit pull request with passing CI
+
+## Commit Message Guidelines
+
+Follow the project's commit message standards as outlined in [CONTRIBUTING.md](../CONTRIBUTING.md):
+
+### Format Requirements
+- **Reference**: Follow [How to Write a Git Commit Message](https://cbea.ms/git-commit/#seven-rules)
+- **Format**: Use imperative mood ("Add feature" not "Added feature")
+- **Length**: Limit subject line to 50 characters, body lines to 72 characters
+- **Structure**:
+ ```
+ Short summary (50 chars max)
+
+ Detailed explanation if needed (72 chars per line)
+
+ - Use bullet points for multiple changes
+ - Reference issues with "Fixes #123" or "Closes #456"
+ ```
+
+### Commit Message Examples
+```
+Fix temporal query performance regression
+
+Add support for Rails 8.0 compatibility
+
+Update dependencies for security patches
+
+Fixes #123
+```
+
+### Best Practices
+- Keep commits atomic (one logical change per commit)
+- Write clear, descriptive commit messages
+- Reference related issues and pull requests
+- Avoid generic messages like "Fix bug" or "Update code"
+
+## Additional Context
+
+- This is both a Ruby gem and an npm package
+- Published to RubyGems as `activeadmin`
+- Published to npm as `@activeadmin/activeadmin`
+- The project uses both Ruby and JavaScript tooling
+- CI runs tests against multiple Rails versions
+- Code coverage tracked with SimpleCov and CodeCov
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000000..6fd6d21a4cf
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,65 @@
+version: 2
+multi-ecosystem-groups:
+ bundler:
+ schedule:
+ interval: weekly
+updates:
+ - package-ecosystem: github-actions
+ directory: /
+ schedule:
+ interval: daily
+ cooldown:
+ default-days: 5
+ groups:
+ github_actions:
+ patterns:
+ - "*"
+ - package-ecosystem: bundler
+ directory: /
+ versioning-strategy: lockfile-only
+ cooldown:
+ default-days: 5
+ patterns:
+ - "*"
+ multi-ecosystem-group: bundler
+ - package-ecosystem: npm
+ directory: /
+ schedule:
+ interval: monthly
+ versioning-strategy: lockfile-only
+ cooldown:
+ default-days: 5
+ groups:
+ npm:
+ patterns:
+ - "*"
+ - package-ecosystem: bundler
+ directory: /gemfiles/rails_72
+ versioning-strategy: lockfile-only
+ cooldown:
+ default-days: 5
+ patterns:
+ - "*"
+ multi-ecosystem-group: bundler
+ ignore:
+ - dependency-name: connection_pool
+ versions: ">= 3" # TODO: remove when dropping Rails 7.2 (see rails/rails#56620)
+ - dependency-name: rails
+ versions: ">= 8.0.0"
+ - dependency-name: rails-i18n
+ versions: ">= 8.0.0"
+ - dependency-name: railties
+ versions: ">= 8.0.0"
+ - package-ecosystem: bundler
+ directory: /gemfiles/rails_80
+ versioning-strategy: lockfile-only
+ cooldown:
+ default-days: 5
+ patterns:
+ - "*"
+ multi-ecosystem-group: bundler
+ ignore:
+ - dependency-name: rails
+ versions: ">= 8.1.0"
+ - dependency-name: rails-i18n
+ versions: ">= 8.1.0"
diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 00000000000..fd55d1984b5
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,23 @@
+changelog:
+ categories:
+ - title: Breaking Changes 🚨
+ labels:
+ - type breaking change
+ - title: Template Updates 📝
+ labels:
+ - type template update
+ - title: Enhancements ✨
+ labels:
+ - type enhancement
+ - title: Bug Fixes 🐛
+ labels:
+ - type bug fix
+ - title: Security Fixes 🔒
+ labels:
+ - type security fix
+ - title: Other Changes 🛠
+ labels:
+ - "*"
+ exclude:
+ authors:
+ - dependabot
diff --git a/.github/workflows/bug-report-template.yml b/.github/workflows/bug-report-template.yml
new file mode 100644
index 00000000000..68bee34e65b
--- /dev/null
+++ b/.github/workflows/bug-report-template.yml
@@ -0,0 +1,42 @@
+name: Bug Reports
+
+on:
+ schedule:
+ # Run every day at noon UTC
+ - cron: '0 12 * * *'
+ pull_request:
+
+concurrency:
+ group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+env:
+ RUBY_VERSION: "4.0"
+
+jobs:
+ bug_report_template_test:
+ name: Run bug report template
+ # Don't run scheduled workflow on forks
+ if: ${{ (github.event_name == 'pull_request' || github.repository_owner == 'activeadmin') }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: tj-actions/changed-files@v47.0.6
+ id: changed-files
+ with:
+ files: |
+ app/**
+ config/**
+ lib/*.rb
+ lib/active_admin/**
+ tasks/bug_report_template.rb
+ Gemfile*
+ *.gemspec
+ - uses: ruby/setup-ruby@v1
+ if: steps.changed-files.outputs.any_changed == 'true'
+ with:
+ ruby-version: ${{ env.RUBY_VERSION }}
+ bundler-cache: true
+ - name: Run bug report template
+ if: steps.changed-files.outputs.any_changed == 'true'
+ run: ACTIVE_ADMIN_PATH=. ruby tasks/bug_report_template.rb
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 00000000000..c81492f0144
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,121 @@
+name: ci
+
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+
+concurrency:
+ group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+jobs:
+ test:
+ name: test (${{ matrix.ruby }}, ${{ matrix.rails }})
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 15
+ strategy:
+ fail-fast: false
+ matrix:
+ ruby:
+ - "4.0"
+ - "3.4"
+ - "3.3"
+ os:
+ - ubuntu-latest
+ rails:
+ - rails_81
+ - rails_80
+ - rails_72
+ exclude:
+ - ruby: '4.0'
+ os: ubuntu-latest
+ rails: rails_72
+ steps:
+ - uses: actions/checkout@v7
+ - name: Configure bundler (default)
+ run: |
+ echo "BUNDLE_GEMFILE=Gemfile" >> "$GITHUB_ENV"
+ if: matrix.rails == 'rails_81'
+ - name: Configure bundler (alternative)
+ run: |
+ echo "BUNDLE_GEMFILE=gemfiles/${{ matrix.rails }}/Gemfile" >> "$GITHUB_ENV"
+ if: matrix.rails != 'rails_81'
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby }}
+ bundler-cache: true
+ rubygems: latest
+ - name: Create test app
+ run: bin/rake setup
+ - name: Restore cached RSpec runtimes
+ uses: actions/cache@v6
+ with:
+ path: tmp/parallel_runtime_rspec.log
+ key: runtimes-rspec-${{ matrix.ruby }}-${{ matrix.rails }}-${{ hashFiles('tmp/parallel_runtime_rspec.log') }}
+ - name: Run RSpec tests
+ env:
+ COVERAGE: true
+ run: |
+ bin/parallel_rspec
+ RSPEC_FILESYSTEM_CHANGES=true bin/rspec
+ - name: Restore cached cucumber runtimes
+ uses: actions/cache@v6
+ with:
+ path: tmp/parallel_runtime_cucumber.log
+ key: runtimes-cucumber-${{ matrix.ruby }}-${{ matrix.rails }}-${{ hashFiles('tmp/parallel_runtime_cucumber.log') }}
+ - name: Run Cucumber features
+ env:
+ COVERAGE: true
+ run: |
+ bin/parallel_cucumber --fail-fast
+ bin/cucumber --profile filesystem-changes
+ bin/cucumber --profile class-reloading
+ - name: Rename coverage file by matrix run
+ run: mv coverage/coverage.xml coverage/coverage-ruby-${{ matrix.ruby }}-${{ matrix.rails }}.xml
+ - uses: actions/upload-artifact@v7
+ with:
+ name: coverage-ruby-${{ matrix.ruby }}-${{ matrix.rails }}
+ path: coverage
+ if-no-files-found: error
+
+ upload_coverage:
+ name: Upload Coverage
+ runs-on: ubuntu-latest
+ # Do not run on forks
+ if: ${{ github.repository_owner == 'activeadmin' }}
+ needs: [test]
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/download-artifact@v8
+ with:
+ path: coverage
+ pattern: coverage-ruby-*
+ merge-multiple: true
+ - uses: codecov/codecov-action@v7
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ directory: coverage
+ fail_ci_if_error: true
+
+ test_docs_build:
+ name: Build docs
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: tj-actions/changed-files@v47.0.6
+ id: changed-files
+ with:
+ files: |
+ docs/**
+ package*.json
+ - uses: actions/setup-node@v7
+ if: steps.changed-files.outputs.any_changed == 'true'
+ with:
+ node-version: 24
+ cache: npm
+ - run: npm ci
+ if: steps.changed-files.outputs.any_changed == 'true'
+ - run: npm run docs:test
+ if: steps.changed-files.outputs.any_changed == 'true'
diff --git a/.github/workflows/do-not-merge.yml b/.github/workflows/do-not-merge.yml
new file mode 100644
index 00000000000..142f208a772
--- /dev/null
+++ b/.github/workflows/do-not-merge.yml
@@ -0,0 +1,22 @@
+name: Do Not Merge
+
+on:
+ pull_request:
+ types:
+ - opened
+ - reopened
+ - synchronize
+ - labeled
+ - unlabeled
+
+jobs:
+ do-not-merge:
+ if: ${{ contains(github.event.*.labels.*.name, 'do not merge') }}
+ name: Prevent merging
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check for PR label
+ run: |
+ echo "Pull request has a 'do not merge' label applied."
+ echo "This workflow fails to prevent merging until label is removed."
+ exit 1
diff --git a/.github/workflows/docs-deployment.yml b/.github/workflows/docs-deployment.yml
new file mode 100644
index 00000000000..3a3c672ca01
--- /dev/null
+++ b/.github/workflows/docs-deployment.yml
@@ -0,0 +1,50 @@
+name: Docs Deployment
+
+on:
+ push:
+ branches:
+ - master
+ release:
+ types:
+ - published
+ workflow_dispatch:
+
+# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
+# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
+concurrency:
+ group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: false
+
+jobs:
+ build_docs:
+ name: Build docs
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 24
+ cache: npm
+ - uses: actions/configure-pages@v6
+ - run: npm ci
+ - run: npm run docs:test
+ - uses: actions/upload-pages-artifact@v5
+ with:
+ path: docs/.vitepress/dist
+
+ deploy_docs:
+ name: Deploy docs site
+ runs-on: ubuntu-latest
+ needs: build_docs
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - uses: actions/deploy-pages@v5
+ id: deployment
diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml
new file mode 100644
index 00000000000..087ffc68a23
--- /dev/null
+++ b/.github/workflows/eslint.yml
@@ -0,0 +1,32 @@
+name: ESLint
+
+on:
+ pull_request:
+
+env:
+ NODE_VERSION: ${{ vars.ESLINT_NODE_VERSION || '24.x' }}
+
+jobs:
+ eslint:
+ name: Run eslint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/setup-node@v7
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: npm
+ - uses: tj-actions/changed-files@v47.0.6
+ id: changed-files
+ with:
+ files: |
+ **.js
+ package*.json
+ .github/workflows/eslint.yml
+ - uses: reviewdog/action-eslint@v1
+ if: steps.changed-files.outputs.any_changed == 'true'
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ filter_mode: nofilter # added (default), diff_context, file, nofilter
+ fail_level: any
+ reporter: github-pr-check
diff --git a/.github/workflows/gherkin-lint.yml b/.github/workflows/gherkin-lint.yml
new file mode 100644
index 00000000000..61e4159b4a7
--- /dev/null
+++ b/.github/workflows/gherkin-lint.yml
@@ -0,0 +1,31 @@
+name: Gherkin Lint
+
+on:
+ pull_request:
+
+env:
+ NODE_VERSION: 24.x
+
+jobs:
+ gherkin_lint:
+ name: Run gherkin-lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: tj-actions/changed-files@v47.0.6
+ id: changed-files
+ with:
+ files: |
+ **.feature
+ .gherkin-lintrc
+ package*.json
+ .github/workflows/gherkin-lint.yml
+ - uses: actions/setup-node@v7
+ if: steps.changed-files.outputs.any_changed == 'true'
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: npm
+ - run: npm ci
+ if: steps.changed-files.outputs.any_changed == 'true'
+ - run: npm run gherkin-lint
+ if: steps.changed-files.outputs.any_changed == 'true'
diff --git a/.github/workflows/github-actions-lint.yml b/.github/workflows/github-actions-lint.yml
new file mode 100644
index 00000000000..0e1afc57d9e
--- /dev/null
+++ b/.github/workflows/github-actions-lint.yml
@@ -0,0 +1,24 @@
+name: GitHub Actions Lint
+
+on:
+ pull_request:
+
+jobs:
+ github_actions_lint:
+ name: Run actionlint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: tj-actions/changed-files@v47.0.6
+ id: changed-files
+ with:
+ files: |
+ .github/workflows/*.yaml
+ .github/workflows/*.yml
+ - uses: reviewdog/action-actionlint@v1
+ if: steps.changed-files.outputs.any_changed == 'true'
+ with:
+ fail_level: any
+ filter_mode: nofilter # added (default), diff_context, file, nofilter
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ reporter: github-pr-check
diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml
new file mode 100644
index 00000000000..0f062626a6e
--- /dev/null
+++ b/.github/workflows/markdown-lint.yml
@@ -0,0 +1,30 @@
+name: Markdown Lint
+
+on:
+ pull_request:
+
+env:
+ MARKDOWNLINT_FLAGS: ${{ vars.REVIEWDOG_MARKDOWNLINT_FLAGS || '--git-recurse .' }}
+
+jobs:
+ markdownlint:
+ name: Run markdownlint
+ runs-on: ubuntu-latest
+ if: ${{ vars.SKIP_REVIEWDOG_MARKDOWNLINT != 'true' }}
+ steps:
+ - uses: actions/checkout@v7
+ - uses: tj-actions/changed-files@v47.0.6
+ id: changed-files
+ with:
+ files: |
+ **.md
+ .markdownlint.yml
+ .github/workflows/markdown-lint.yml
+ - uses: reviewdog/action-markdownlint@v0
+ if: steps.changed-files.outputs.any_changed == 'true'
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ filter_mode: nofilter # added (default), diff_context, file, nofilter
+ fail_level: any
+ markdownlint_flags: ${{ env.MARKDOWNLINT_FLAGS }}
+ reporter: github-pr-check
diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml
new file mode 100644
index 00000000000..75a78946cfe
--- /dev/null
+++ b/.github/workflows/rubocop.yml
@@ -0,0 +1,44 @@
+name: Rubocop
+
+on:
+ pull_request:
+
+env:
+ RUBY_VERSION: ${{ vars.RUBOCOP_RUBY_VERSION || '4.0' }}
+
+jobs:
+ rubocop:
+ name: Run rubocop
+ runs-on: ubuntu-latest
+ env:
+ BUNDLE_ONLY: ${{ vars.RUBOCOP_BUNDLE_ONLY || 'rubocop' }}
+ steps:
+ - uses: actions/checkout@v7
+ - uses: tj-actions/changed-files@v47.0.6
+ id: changed-files
+ with:
+ files: |
+ .github/workflows/rubocop.yml
+ .rubocop.yml
+ **.rb
+ **.rake
+ **.arb
+ bin/*
+ gemfiles/**/Gemfile
+ Gemfile*
+ Rakefile
+ *.gemspec
+ .simplecov
+ - uses: ruby/setup-ruby@v1
+ if: steps.changed-files.outputs.any_changed == 'true'
+ with:
+ ruby-version: ${{ env.RUBY_VERSION }}
+ bundler-cache: true
+ - uses: reviewdog/action-rubocop@v2
+ if: steps.changed-files.outputs.any_changed == 'true'
+ with:
+ fail_level: any
+ filter_mode: nofilter # added (default), diff_context, file, nofilter
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ skip_install: true
+ use_bundler: true
diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml
new file mode 100644
index 00000000000..4bb35194abc
--- /dev/null
+++ b/.github/workflows/typos.yml
@@ -0,0 +1,17 @@
+name: Typos
+
+on:
+ pull_request:
+
+jobs:
+ typos:
+ name: Run typos
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: reviewdog/action-typos@v1
+ with:
+ fail_level: any
+ filter_mode: nofilter # added (default), diff_context, file, nofilter
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ reporter: github-pr-check
diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml
new file mode 100644
index 00000000000..5d5954e8f14
--- /dev/null
+++ b/.github/workflows/yaml-lint.yml
@@ -0,0 +1,24 @@
+name: YAML Lint
+
+on:
+ pull_request:
+
+jobs:
+ yaml_lint:
+ name: Run yamllint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: tj-actions/changed-files@v47.0.6
+ id: changed-files
+ with:
+ files: |
+ **.yaml
+ **.yml
+ - uses: reviewdog/action-yamllint@v1
+ if: steps.changed-files.outputs.any_changed == 'true'
+ with:
+ fail_level: any
+ filter_mode: nofilter # added (default), diff_context, file, nofilter
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ reporter: github-pr-check
diff --git a/.gitignore b/.gitignore
index f3b524d6bd3..b6717c75e30 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,39 +1,16 @@
-## MAC OS
-.DS_Store
-
-## TEXTMATE
-*.tmproj
-tmtags
-
-## EMACS
-*~
-\#*
-.\#*
-
-## VIM
-*.swp
-# IDEA / RUBYMINE
-.idea
-
-## PROJECT::GENERAL
-tags
-coverage
-rdoc
-doc
-.yardoc
-pkg
-
-## PROJECT::SPECIFIC
+/tmp
+/tasks/tmp
+/coverage
+/.yardoc
+/.ruby-version
+/pkg
.bundle
-spec/rails
-*.sqlite3-journal
-Gemfile.lock
-Gemfile-*.lock
-capybara*
-viewcumber
-test-rails*
-public
-.rvmrc
-.rspec
-.rails-version
-.rbenv-version
+/.rspec_failures
+/node_modules
+/src
+/vendor/bundle
+/rails_70
+/dist
+docs/.vitepress/cache
+docs/.vitepress/dist
+docs/.vitepress/.temp
diff --git a/.markdownlint.yml b/.markdownlint.yml
new file mode 100644
index 00000000000..9fe39abc0d1
--- /dev/null
+++ b/.markdownlint.yml
@@ -0,0 +1,5 @@
+default: true
+MD002: false
+MD013: false
+MD024: false
+MD041: false
diff --git a/.rspec b/.rspec
new file mode 100644
index 00000000000..03d161c7cc7
--- /dev/null
+++ b/.rspec
@@ -0,0 +1,3 @@
+--format <%= ENV['CI'] ? 'documentation' : 'progress' %>
+--require spec_helper
+<%= "--require #{__dir__}/spec/support/simplecov_changes_env.rb --tag changes_filesystem" if ENV['RSPEC_FILESYSTEM_CHANGES'] %>
diff --git a/.rspec_parallel b/.rspec_parallel
new file mode 100644
index 00000000000..e86512bdfb6
--- /dev/null
+++ b/.rspec_parallel
@@ -0,0 +1,3 @@
+--require <%= "#{__dir__}/spec/support/simplecov_regular_env.rb" %>
+--format progress
+--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 00000000000..6f0b4159bd2
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,420 @@
+---
+
+inherit_mode:
+ merge:
+ - Include
+
+plugins:
+ - rubocop-capybara
+ - rubocop-packaging
+ - rubocop-performance
+ - rubocop-rails
+ - rubocop-rspec
+
+AllCops:
+ DisabledByDefault: true
+ TargetRubyVersion: 3.3
+ TargetRailsVersion: 7.2
+
+ Exclude:
+ - .git/**/*
+ - .github/**/*
+ - bin/**/*
+ - gemfiles/**/vendor/**/*
+ - node_modules/**/*
+ - tmp/**/*
+ - vendor/**/*
+
+ Include:
+ - gemfiles/*/Gemfile
+ - .simplecov
+
+ DisplayCopNames: true
+
+ StyleGuideCopsOnly: false
+
+Capybara/AssertStyle:
+ Enabled: true
+
+Capybara/FindAllFirst:
+ Enabled: true
+
+Capybara/RedundantWithinFind:
+ Enabled: true
+
+Capybara/RSpec/CurrentPathExpectation:
+ Enabled: true
+
+Capybara/RSpec/HaveContent:
+ Enabled: true
+
+Capybara/RSpec/HaveSelector:
+ Enabled: true
+
+Capybara/RSpec/MatchStyle:
+ Enabled: true
+
+Capybara/RSpec/NegationMatcher:
+ Enabled: true
+
+Capybara/RSpec/NegationMatcherAfterVisit:
+ Enabled: true
+
+Capybara/RSpec/PredicateMatcher:
+ Enabled: true
+
+Capybara/RSpec/SpecificMatcher:
+ Enabled: true
+
+Capybara/RSpec/VisibilityMatcher:
+ Enabled: true
+
+Capybara/SpecificActions:
+ Enabled: true
+
+Capybara/SpecificFinders:
+ Enabled: true
+
+Layout/AccessModifierIndentation:
+ Enabled: true
+
+Layout/ArgumentAlignment:
+ Enabled: true
+
+Layout/CaseIndentation:
+ Enabled: true
+
+Layout/ClosingParenthesisIndentation:
+ Enabled: true
+
+Layout/CommentIndentation:
+ Enabled: true
+
+Layout/ElseAlignment:
+ Enabled: true
+
+Layout/EmptyLines:
+ Enabled: true
+
+Layout/EmptyLinesAroundBlockBody:
+ Enabled: true
+
+Layout/EndAlignment:
+ Enabled: true
+
+Layout/EndOfLine:
+ Enabled: true
+
+Layout/ExtraSpacing:
+ AllowForAlignment: false
+ Enabled: true
+
+Layout/FirstArgumentIndentation:
+ Enabled: true
+
+Layout/FirstHashElementIndentation:
+ Enabled: true
+ EnforcedStyle: consistent
+
+Layout/FirstMethodArgumentLineBreak:
+ Enabled: true
+
+Layout/FirstParameterIndentation:
+ Enabled: true
+
+Layout/HashAlignment:
+ Enabled: true
+
+Layout/IndentationConsistency:
+ Enabled: true
+
+Layout/IndentationStyle:
+ Enabled: true
+ EnforcedStyle: spaces
+
+Layout/IndentationWidth:
+ Enabled: true
+
+Layout/ParameterAlignment:
+ Enabled: true
+ EnforcedStyle: with_fixed_indentation
+
+Layout/SpaceAfterComma:
+ Enabled: true
+
+Layout/SpaceAroundEqualsInParameterDefault:
+ Enabled: true
+
+Layout/SpaceAroundOperators:
+ Enabled: true
+
+Layout/SpaceBeforeBlockBraces:
+ Enabled: true
+
+Layout/SpaceBeforeComma:
+ Enabled: true
+
+Layout/SpaceBeforeFirstArg:
+ Enabled: true
+
+Layout/SpaceInsideBlockBraces:
+ Enabled: true
+
+Layout/SpaceInsideHashLiteralBraces:
+ Enabled: true
+
+Layout/SpaceInsideParens:
+ Enabled: true
+
+Layout/TrailingEmptyLines:
+ Enabled: true
+
+Layout/TrailingWhitespace:
+ Enabled: true
+
+Lint/AmbiguousOperator:
+ Enabled: true
+
+Lint/AmbiguousRegexpLiteral:
+ Enabled: true
+
+Lint/ParenthesesAsGroupedExpression:
+ Enabled: true
+
+Lint/RedundantStringCoercion:
+ Enabled: true
+
+Lint/UselessAccessModifier:
+ Enabled: true
+
+Lint/UselessAssignment:
+ Enabled: true
+
+Naming/PredicatePrefix:
+ Enabled: true
+
+ ForbiddenPrefixes:
+ - is_
+ - have_
+
+ AllowedMethods:
+ - has_many
+ - has_many_actions
+
+Packaging/BundlerSetupInTests:
+ Enabled: true
+
+Packaging/GemspecGit:
+ Enabled: true
+
+Packaging/RequireHardcodingLib:
+ Enabled: true
+
+Packaging/RequireRelativeHardcodingLib:
+ Enabled: true
+
+Performance/AncestorsInclude:
+ Enabled: false
+
+Performance/ArraySemiInfiniteRangeSlice:
+ Enabled: false
+
+Performance/BigDecimalWithNumericArgument:
+ Enabled: true
+
+Performance/BindCall:
+ Enabled: true
+
+Performance/BlockGivenWithExplicitBlock:
+ Enabled: true
+
+Performance/Caller:
+ Enabled: true
+
+Performance/Casecmp:
+ Enabled: false
+
+Performance/CaseWhenSplat:
+ Enabled: true
+
+Performance/ChainArrayAllocation:
+ Enabled: false
+
+Performance/CollectionLiteralInLoop:
+ Enabled: true
+ Exclude:
+ - spec/**/*
+
+Performance/CompareWithBlock:
+ Enabled: true
+
+Performance/ConcurrentMonotonicTime:
+ Enabled: true
+
+Performance/ConstantRegexp:
+ Enabled: true
+
+Performance/Count:
+ Enabled: true
+
+Performance/DeletePrefix:
+ Enabled: true
+
+Performance/DeleteSuffix:
+ Enabled: true
+
+Performance/Detect:
+ Enabled: true
+
+Performance/DoubleStartEndWith:
+ Enabled: true
+ IncludeActiveSupportAliases: true
+
+Performance/EndWith:
+ Enabled: true
+
+Performance/FixedSize:
+ Enabled: true
+
+Performance/FlatMap:
+ Enabled: true
+ EnabledForFlattenWithoutParams: false
+
+Performance/InefficientHashSearch:
+ Enabled: true
+
+Performance/IoReadlines:
+ Enabled: true
+
+Performance/MapCompact:
+ Enabled: false
+
+Performance/MapMethodChain:
+ Enabled: false
+
+Performance/MethodObjectAsBlock:
+ Enabled: true
+
+Performance/OpenStruct:
+ Enabled: true
+
+Performance/RangeInclude:
+ Enabled: true
+
+Performance/RedundantBlockCall:
+ Enabled: false
+
+Performance/RedundantEqualityComparisonBlock:
+ Enabled: false
+
+Performance/RedundantMatch:
+ Enabled: true
+
+Performance/RedundantMerge:
+ Enabled: true
+ MaxKeyValuePairs: 2
+
+Performance/RedundantSortBlock:
+ Enabled: true
+
+Performance/RedundantSplitRegexpArgument:
+ Enabled: true
+
+Performance/RedundantStringChars:
+ Enabled: true
+
+Performance/RegexpMatch:
+ Enabled: true
+
+Performance/ReverseEach:
+ Enabled: true
+
+Performance/ReverseFirst:
+ Enabled: true
+
+Performance/SelectMap:
+ Enabled: false
+
+Performance/Size:
+ Enabled: true
+
+Performance/SortReverse:
+ Enabled: true
+
+Performance/Squeeze:
+ Enabled: true
+
+Performance/StartWith:
+ Enabled: true
+
+Performance/StringBytesize:
+ Enabled: true
+
+Performance/StringIdentifierArgument:
+ Enabled: true
+
+Performance/StringInclude:
+ Enabled: true
+
+Performance/StringReplacement:
+ Enabled: true
+
+Performance/Sum:
+ Enabled: false
+
+Performance/TimesMap:
+ Enabled: true
+
+Performance/UnfreezeString:
+ Enabled: true
+
+Performance/UriDefaultParser:
+ Enabled: true
+
+Performance/ZipWithoutBlock:
+ Enabled: true
+
+Rails/FilePath:
+ Enabled: true
+ EnforcedStyle: slashes
+
+Rails/RootPathnameMethods:
+ Enabled: true
+
+RSpec/EmptyLineAfterExample:
+ Enabled: true
+
+RSpec/EmptyLineAfterExampleGroup:
+ Enabled: true
+
+RSpec/HookArgument:
+ Enabled: true
+
+Style/BlockDelimiters:
+ Enabled: true
+
+Style/Dir:
+ Enabled: true
+
+Style/Encoding:
+ Enabled: true
+
+Style/ExpandPathArguments:
+ Enabled: true
+
+Style/FrozenStringLiteralComment:
+ Enabled: true
+ EnforcedStyle: always
+
+Style/HashSyntax:
+ Enabled: true
+ EnforcedShorthandSyntax: never
+
+Style/ParallelAssignment:
+ Enabled: true
+
+Style/StringLiterals:
+ Enabled: false
+
+Style/TrailingCommaInArguments:
+ Enabled: true
diff --git a/.simplecov b/.simplecov
new file mode 100644
index 00000000000..b02b8a08d9d
--- /dev/null
+++ b/.simplecov
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+SimpleCov.profiles.define "activeadmin" do
+ # Opt out of SimpleCov 1.0's default test_frameworks profile to reintroduce
+ # `/features` coverage and surface dead code there.
+ remove_filter %r{\A(test|features|spec|autotest)/}
+ skip %r{\Aspec/}
+ skip "tmp/development_apps/"
+ skip "tmp/test_apps/"
+ skip "tasks/test_application.rb"
+
+ if ENV["COVERAGE"] == "true"
+ require "simplecov-cobertura"
+ formatter SimpleCov::Formatter::CoberturaFormatter
+ end
+end
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 0a4d34e38e2..00000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-script: bundle exec rake test
-rvm:
- - 1.9.3
- - 2.0.0
-before_install:
- - gem update --system
- - gem --version
- - gem install bundler
-env:
- - RAILS=3.0.20
- - RAILS=3.1.12
- - RAILS=3.2.13
-matrix:
- allow_failures:
- - rvm: 2.0.0
diff --git a/.yamllint.yml b/.yamllint.yml
new file mode 100644
index 00000000000..b48d900243b
--- /dev/null
+++ b/.yamllint.yml
@@ -0,0 +1,18 @@
+# https://yamllint.readthedocs.io/en/stable/configuration.html
+extends: default
+ignore: |
+ node_modules/
+ tmp/
+ vendor/
+ cucumber.yml
+rules: # https://yamllint.readthedocs.io/en/stable/rules.html
+ comments:
+ min-spaces-from-content: 1
+ document-start: disable
+ line-length: disable
+ truthy:
+ allowed-values:
+ - "true"
+ - "false"
+ - "on"
+ - "off"
diff --git a/.yardopts b/.yardopts
deleted file mode 100644
index e05df33c368..00000000000
--- a/.yardopts
+++ /dev/null
@@ -1,8 +0,0 @@
-lib/**/*.rb
---protected
---no-private
---asset docs/images:images
--
-README.rdoc
-CHANGELOG.md
-docs/**/*.md
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0ca9e1ba1f2..dfb583197cf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,894 +1,1033 @@
-## Master (unreleased) - [compare](https://github.com/gregbell/active_admin/compare/v0.6.0...master)
+# Changelog
-### Features
+[Future changelogs have moved to GitHub Releases](https://github.com/activeadmin/activeadmin/releases)
-* OmniAuth provider links now automatically appear on the login page [#2088][] by [@henrrrik][]
-* Menu items can now properly overflow [#2046][] by [@maax][]; later updated in [#2125][] by [@ball-hayden][]
+## 3.2.0 [☰](https://github.com/activeadmin/activeadmin/compare/v3.1.0..v3.2.0)
+
+### Security Fixes
+
+* Backport protect against CSV Injection. [#8167] by [@mgrunberg]
+
+### Enhancements
+
+* Backport support citext column type in string filter. [#8165] by [@mgrunberg]
+* Backport provide detail in DB statement timeout error for filters. [#8163] by [@mgrunberg]
### Bug Fixes
-* Fixes problem where extra `/` route was being generated [#2062][] by [@jbhannah][]
-* `IndexAsBlog` now renders title/body procs in the view context [#2087][] by [@macfanatic][]
-* Fixes `route_instance_path` for `belongs_to` resources [#2099][] by [@pcreux][]
-* Fixes breadcrumb links for `belongs_to` resources [#2090][] by [@Daxter][]
-* Forces a `I18n.reload!` to ensure translations are loaded in production [#2072][] [@ericpromislow][]
+* Backport make sure menu creation does not modify menu options. [#8166] by [@mgrunberg]
+* Backport ransack error with filters when ActiveStorage is used. [#8164] by [@mgrunberg]
+
+## 3.1.0 [☰](https://github.com/activeadmin/activeadmin/compare/v3.0.0..v3.1.0)
### Enhancements
-* Adds option to "undecorate" resource when building forms [#2085][] by [@amiel][]
-* Adds [better_errors](https://github.com/charliesome/better_errors) gem for a better AA development experience [#2095][] by [@Daxter][]
-* Scopes now support blocks for the `:default` option [#2084][] by [@macfanatic][]
-* `:if` and `:unless` options added to `scope_to` [#2089][] by [@macfanatic][]
-* German (Switzerland), English (UK) locales added [#1916][] by [@psy-q][]
-* Renames Comment to AdminComment [#2060][] by [@jbhannah][]; later replaced by [#2113][]
-* Improves Comments UI and adds config settings [#2113][] by [@Daxter][]
-```ruby
- config.show_comments_in_menu = false # Defaults to true
- config.comments_registration_name = 'AdminComment' # Defaults to 'Comment'
-```
+* Support Rails 7.1. [#8102] by [@mgrunberg]
+* Remove deprecated usage of ActiveSupport::Deprecation singleton. [#8106] by [@mgrunberg]
+* Replace to_formatted_s with to_s to convert date to string. [#8105] by [@mgrunberg]
+* Remove upper bound dependency limits from gemspec. [#8098] by [@javierjulio]
-* `has_many` forms
- * Adds 'has_many_delete' CSS class to `li` elements [#2054][] by [@shekibobo][]
- * Adds `:heading` option to customize the heading displayed [#2068][] by [@coreyward][]
- * Adds `:allow_destroy` option to add in a checkbox to the form to delete records [#2071][] by [@shekibobo][]
+## 3.0.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.14.0..v3.0.0)
-### Cleanup
+### Breaking Changes
-* Cucumber step definitions refactor [#2015][] by [@Daxter][]
-* Misc cleanup in [#2075][] and [#2107][] by [@Daxter][]
-* Removes messy spacing from `AdminUser` generator file [#2058][] by [@lupinglade][]
-* Fixes documentation formatting [#2083][] by [@amiel][]
+* Remove custom Ransack predicates that were MetaSearch backports. [#8010] by [@javierjulio]
+* Require Ransack v4. [#8009] by [@javierjulio]
-## 0.6.0 - [compare](https://github.com/gregbell/active_admin/compare/v0.5.1...v0.6.0)
+### Enhancements
-### Bug Fixes
+* Use display name fallback if blank display name result. [#6342] by [@javierjulio]
+
+### Translation Improvements
+
+* Improve Swedish translations. [#7993] by [@carlottostromstedt]
-* Fix conflict with Redcloth [#1805][] by [@adrienkohlbecker][]
-* Add missing batch actions translations. [#1788][] by [@EtienneDepaulis][]
-* JS fix for batch actions checkbox toggling [#1947][] by [@ai][]
-* Fixed routing bug for root namespace [#2043][] by [@Daxter][] and [@gregbell][]
+## 2.14.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.13.1..v2.14.0)
### Enhancements
-* Rubinis compatability change over block variables [#1871][] by [@dbussin][]
-* Compatability with Draper 1.0 release [#1896][] by [@hakanensari][]
-* Fixed references to "dashboards.rb" in locales, since file doesn't exist [#1873][] by [@ryansch][]
-* Removing deprecated bourbon box-shadow mixin [#1913][] by [@stereoscott][]
-* More Japanese localizations [#1929][] by [@johnnyshields][]
-* Devise lockable module now supported by default [#1933][] by [@Bishop][]
-* Index table now uses a unique DOM id (`#index_table_posts` instead of `#posts`) [#1966][] by [@TiagoCardoso1983][]
-* Coffeescript 1.5 compatability as constructors no longer return a value [#1940][] by [@ronen][]
-* Allow options to be passed to the Abre element for rows in `attributes_table` [#1439][] by [@Daxter][]
-* Gender neutral Spanish translations [#1973][] by [@laffinkippah][]
-* Adds the ability to use `starts_with` and `ends_with` in string filters [#1962][] by [@rmw][]
-* Adds support for translating resources when registered with `:as` [#2044][] by [@Daxter][]
-* Scopes are no longer hidden when empty filter results [#1804][] by [@Daxter][]
-* Dynamic scope names with procs [#2018][] by [@Daxter][]
-* Filters now support the `:if` optional argument [#1801][] by [@Daxter][]
-* Member & collection actions support multiple HTTP methods for the same action [#2000][] by [@rdsoze][]
-
-### Features
-
-* Authorization DSL including a default CanCan adapter [#1817][] by [@pcreux][] and [@gregbell][]
-* New "actions" DSL for customizing actions on index listing [#1834][] by [@ejholmes][]
-* Index title can now be set via a proc [#1861][] by [@jamesalmond][]
-* Can now disable `download_links` per resource, index collection or globally throughout AA [#1908][] by [@TBAA][]
-* Filters: add ability to search for blank/null fields with boolean search [#1893][] by [@whatcould][]
-* New `navigation_menu` DSL for menu system [#1967][] by [@macfanatic][] and [@gregbell][]
-* Support segmented control switch between different index styles [#1745][] by [@joshuacollins85][]
-
-### Other
-
-* Updated documentation for formtastic deprecated f.buttons [#1867][] by [@ericcumbee][]
-* Copyright updated for 2013 [#1937][] by [@snapapps][]
-
-### Contributors
-
-327 Commits by 42 authors
-
-* Adrien Kohlbecker
-* Alexandr Prudnikov
-* Andrew Pietsch
-* Andrey A.I. Sitnik
-* Andrey Rozhkovsky
-* Anthony Zacharakis
-* Bartlomiej Niemtur
-* David DIDIER
-* David Reese
-* Daxter
-* Dirkjan Bussink
-* Dominik Masur
-* Eric Cumbee
-* Eric J. Holmes
-* Etienne Depaulis
-* Gosha Arinich
-* Greg Bell
-* Ian Carroll
-* James Almond
-* Johnny Shields
-* Joshua Collins
-* Kieran Klaassen
-* Luís Ramalho
-* Matt Brewer
-* Nathaniel Bibler
-* Olek Janiszewski
-* Philippe Creux
-* Raison Dsouza
-* Rebecca Miller-Webster
-* Roman Sklenar
-* Roman Sklenář
-* Ryan Schlesinger
-* Scott Meves
-* Sean Ian Linsley
-* Sergey Pchelincev
-* Simon Menke
-* Tiago Cardoso
-* Travis Pew
-* WU Jun
-* laffinkippah
-* ronen barzel
-* тιηуηυмвєяѕ
-
-## 0.5.1 - [compare](https://github.com/gregbell/active_admin/compare/v0.5.0...v0.5.1)
+* Add csp_meta_tag to layout. [#7986] by [@javierjulio]
+* Update config.register_javascript with options support. [#7002] by [@lanzhiheng]
+* Use `csrf_meta_tags` in place of singular version. [#7985] by [@javierjulio]
+* Allow different new and edit rules in authorization adapters. [#6535] by [@timwis]
+
+### Bug Fixes
+
+* Fix form layout for hints and checkboxes. [#7772] by [@JewelSam]
+* Update filters disabled error to include specific action. [#6195] by [@javawizard]
+* Fix Comments controller destroy declaration. [#6482] by [@bliof]
+* Stop pagination elements from overflowing outside of panel container. [#7599] by [@ray-curran]
+
+### Translation Improvements
+
+* Update vi locale with more translations. [#7984] by [@rs-phunt]
+* Update zh-CN locale with multiple corrections. [#7944] by [@hfl]
+* Fix typo in Vietnamese locale for filter text. [#7920] by [@tvziet]
+* Improve French translation. [#7653] by [@cprodhomme]
+
+### Documentation
+
+* Add more documentation about PORO decorator requirements. [#7556] by [@sanfrecce-osaka]
+* Add Load Paths docs to the active_admin.rb template. [#7541] by [@gabo-cs]
+
+### Performance
+
+* Removes docs from exported gem. [#7013] by [@brunoarueira]
+
+## 2.13.1 [☰](https://github.com/activeadmin/activeadmin/compare/v2.13.0..v2.13.1)
+
+### Bug Fixes
+
+* Honor load paths order when loading admin files. [#7488] by [@tf]
+* Fix passing expected hash payload argument. [#7487] by [@ispyropoulos]
+
+## 2.13.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.12.0..v2.13.0)
+
+### Documentation
+
+* Update validation errors documentation to account for deprecated `ActiveModel::Errors#keys`. [#7475] by [@amit]
+
+### Dependency Changes
+
+* Drop rails 6.0 support. [#7476] by [@deivid-rodriguez]
+
+### Performance
+
+* Fix pundit performance. [#7479] by [@deivid-rodriguez]
+
+## 2.12.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.11.2..v2.12.0)
### Enhancements
-* Developer can pass options for CSV generation. [#1626][] by [@rheaton][]
-```ruby
- ActiveAdmin.register Post do
- csv options: {force_quotes: true} do
- column :title
- end
- end
-```
-
-* Breadcrumb links can be customized by [@simonoff][]
-```ruby
- ActiveAdmin.register Post do
- breadcrumb do
- [
- link_to("My account", account_path(current_user))
- ]
- end
- end
-```
+* Add Ransack 3 compatibility. [#7453] by [@tagliala]
-* Support proc for parent options on menus [#1664][] by [@shell][]
-```ruby
- ActiveAdmin.register Post do
- menu parent: proc { I18n.t("admin") }
- end
-```
+### Bug Fixes
-* Support automatic use of Decorators. [#1117][] by [@amiel][] and [#1647][] by [@dapi][]
-```ruby
- ActiveAdmin.register Post do
- decorate_with PostDecorator
- end
-```
+* Fix pundit namespace detection. [#7144] by [@vlad-psh]
-* Allow blacklisting of filters with 'remove_filter' [#1609][] by [@tracedwax][]
-```ruby
- ActiveAdmin.register Post do
- remove_filter :author
- end
-```
+### Documentation
-* ActiveAdmin i18n translations can be overwritten in your rails
-application locales. [#1775][] by [@caifara][]
-* Add "Powered by" to translations. [#1783][] by [@sunny][]
-* Forms accept two level deeps has_many. [#1699][] by [@kerberoS][] and tests in [#1782][] by [@ptn][]
-* Extract download_format_links into helper [#1752][] by [@randym][]
-* Add support for semantic errors [#905][] by [@robdiciuccio][]
-* Add support for boolean inputs [#1668][] by [@orendon][]
-* Support subURI on logout [#1681][] by [@yawn][]
+* Don't mention webpacker as the default asset generator in Rails. [#7377] by [@jaynetics]
-### Bug fix
-* Apply before_filter to BaseController [#1683][] by [@yorch][]
-* ... and much more.
+### Performance
-### Contributions
+* Avoid duplicate work when downloading CSV. [#7336] by [@deivid-rodriguez]
-156 commits (49 Pull Requests) by 51 contributors.
+## 2.11.2 [☰](https://github.com/activeadmin/activeadmin/compare/v2.11.1..v2.11.2)
-## 0.5.0 - [compare](https://github.com/gregbell/active_admin/compare/v0.4.4...v0.5.0)
+### Bug Fixes
+
+* Fix disappearing BOM option for `CSVBuilder`. [#7170] by [@Karoid]
+
+## 2.11.1 [☰](https://github.com/activeadmin/activeadmin/compare/v2.11.0..v2.11.1)
### Enhancements
-* Created new view components (Footer, TitleBar, Header, UtilityNav) to more
- easily customize the views in Active Admin and per namespace. ([@gregbell][])
-* All CSS is now encapsulated under the `body.active_admin` class. This may
- change the precedence of styles that you created to override or use in
- other areas of your application.
-* Dashboards are now implemented as pages. For more details of how to configure
- a page, checkout http://activeadmin.info/docs/9-custom-pages.html
-* Root route can be set to any controller#action using `#root_to`.
-* Batch Actions allows you to select entries on index page and perform
- an action against them.
-* CSV separators are configurable.
-* Lot of bug fixes.
+* Add turbolinks support to has many js. [#7384] by [@amiel]
-### Deprecations
+### Documentation
-* Removed all references to ActiveAdmin::Renderer. If you were using these
- please update code to use an Arbre component. Removed
- `ActiveAdmin:Views::HeaderRender` and replaced with
- `ActiveAdmin::Views::Header` component.
-* ActiveAdmin::Menu and ActiveAdmin::MenuItem API has changed. If you were
- creating custom menu items, the builder syntax has changed to. Menu#add now
- accepts a MenuItem, instead of building the menu item for you.
-* `ActiveAdmin::Dashboards.build` is deprecated in favour of generating a page
- and using the new `config.root_to` option.
-* Arbre is now a gem on its own.
+* Remove `insert_tag` from Form-Partial docs. [#7394] by [@TonyArra]
-### Contributions
+## 2.11.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.10.1..v2.11.0)
-561 commits (142 Pull Requests) by 88 contributors.
+### Enhancements
-## 0.4.4 - [compare](https://github.com/gregbell/active_admin/compare/v0.4.3...v0.4.4)
+* Add Rails 7 Support. [#7235] by [@tagliala]
-### Dependencies
+### Bug Fixes
+
+* Fix form SCSS variables no longer being defined in the outermost scope, so no longer being accessible. [#7341] by [@gigorok]
-* Use `formtastic` ~> 2.1.1 until AA 0.5.0 is released
-* Use `inherited_resources` >= 1.3.1 (ensure flash messages work)
+## 2.10.1 [☰](https://github.com/activeadmin/activeadmin/compare/v2.10.0..v2.10.1)
+
+### Enhancements
-## 0.4.3 - [compare](https://github.com/gregbell/active_admin/compare/v0.4.2...v0.4.3)
+* Apply `box-sizing: border-box` globally. [#7349] by [@deivid-rodriguez]
+* Vendor normalize 8.0.1. [#7350] by [@deivid-rodriguez]
+* Remove deprecation warning using controller filters inside initializer. [#7340] by [@mgrunberg]
### Bug Fixes
-* [#1063][]: Fix comment issues when using postgres ([@jancel][])
+* Fix frozen string error when downloading CSV and streaming disabled. [#7332] by [@deivid-rodriguez]
-## 0.4.2 - [compare](https://github.com/gregbell/active_admin/compare/v0.4.1...v0.4.2)
+## 2.10.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.9.0..v2.10.0)
### Enhancements
-* [#822][]: Automatically include js and css to precompile list ([@jschwindt][])
-* [#1033][]: Site title accepts a proc that is rendered in the context
- of the view ([@pcreux][])
-* [#70][]: Form blocks are now rendered within the context of the view ([@gregbell][])
-* [#70][]: Filter's collections are now eval'd in the context of the view ([@gregbell][])
-* [#1032][]: The html body now includes a class for the namespace name ([@mattvague][])
-* [#1013][]: Hide the count from one specific scope using `:show_count => false`
- ([@latortuga][])
-* [#1023][]: Add localization support for comments ([@MoritzMoritz][])
+* Load favicon from Webpacker assets when use_webpacker is set to true. [#6954] by [@Fs00]
+* Don't apply sorting to collection until after scoping. [#7205] by [@agrobbin]
+* Resolve dart sass deprecation warning for division. [#7095] by [@tordans]
+* Use `instrument` from the Notifications API instead of low level `publish`. [#7262] by [@sprql]
+* Avoid mutating string literals. [#6936] by [@tomgilligan]
+* Include print styles in main stylesheet. [#6922] by [@deivid-rodriguez]
+* Use `POST` for OmniAuth links. [#6916] by [@deivid-rodriguez]
+* Scope new record instantiation by authorization scope. [#6884] by [@ngouy]
+* Make `permit_params` and `belongs_to` order independent. [#6906] by [@deivid-rodriguez]
+* Use collection length instead of running COUNTs for limited collections. [#5660] by [@MmKolodziej]
### Bug Fixes
-* [#34][]: Comments now work with models using string ids ([@jancel][])
-* [#1041][]: When `table_for` collection is empty it no longer outputs
- a blank array in Ruby 1.9 ([@jancel][], [#1016][])
-* [#983][]: Fixed compatibility with pry-rails ([@pcreux][])
-* [#409][]: Install generator handles custom class names for user ([@gregbell][])
+* Show ransackable_scopes filters in search results. [#7127] by [@vlad-psh]
-### Contributors
+### Translation Improvements
-42 Commits by 10 authors
+* Fix Dutch translation for password reset button. [#7181] by [@mvz]
+* Add few key to RO pagination.entry. [#6915] by [@lubosch]
+* Change misleading Korean translation. [#6873] by [@1000ship]
-* Allen Huang
-* Daniel Lepage
-* Thibaut Barrère
-* Drew Ulmer
-* Juan Schwindt
-* Moritz Behr
-* Jeff Ancel
-* Matt Vague
-* Greg Bell
-* Philippe Creux
+### Documentation
+* Replace deprecated update_attributes! with update!. [#6959] by [@sergey-alekseev]
+* Clarify docs on user setup. [#6872] by [@javawizard]
-## 0.4.1 - [compare](https://github.com/gregbell/active_admin/compare/v0.4.0...v0.4.1)
+### Dependency Changes
+
+* Drop rails 5.2 support. [#7293] by [@deivid-rodriguez]
+* Drop support for Ruby 2.5. [#7236] by [@alejandroperea]
+
+## 2.9.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.8.1..v2.9.0)
### Enhancements
-* [#865][]: Pages support the `#page_action` to add custom controller actions
- to a page ([@BoboFraggins][])
-* Columns component now supports column spans, max and min widths ([@gregbell][])
-* [#497][]: Custom pagination settings per resource ([@pcreux][])
-* [#993][]: Login form now focuses on email ([@mattvague][])
-* [#865][]: Add `:if` support to sidebar sections ([@BoboFraggins][])
-* [#865][]: Added `:scope_count => false` to the index to hide scope counts
- in generated scopes ([@BoboFraggins][])
+* Support for Rails 6.1. [#6548] by [@deivid-rodriguez]
+* Add ability to override "Remove" button text on has_many forms. [#6523] by [@littleforest]
+* Drop git in gemspec. [#6462] by [@utkarsh2102]
### Bug Fixes
-* [#101][]: Global nav now works with RackBaseURI ([@gregbell][])
-* [#960][]: Global nav works when scoped in rails routes ([@gregbell][])
-* [#994][]: Fix index page check collection.limit(1).exists? causes exception when
- ordering by virtual colum ([@latortuga][], [@gregbell][])
-* [#971][]: Fix SQL when sorting tables with a column named "group" ([@ggilder][])
+* Pick up upstream fixes in devise templates. [#6536] by [@munen]
-### Dependencies
+### Documentation
-* [#978][]: Support for Inherited Resources 1.3.0 ([@fabiormoura][])
+* Fix `has_many` syntax in forms documentation. [#6583] by [@krzcho]
+* Add example of using `default_main_content` in show pages. [#6487] by [@sjieg]
-### Contributors
+### Dependency Changes
-75 Commits by 12 authors
+* Remove sassc and sprockets runtime dependencies. [#6584] by [@deivid-rodriguez]
-* Bruno Bonamin
-* David Radcliffe
-* Dinesh Majrekar
-* Erik Michaels-Ober
-* Fábio Maia
-* Gabriel Gilder
-* Greg Bell
-* Kyle Macey
-* Matt Vague
-* Oldani Pablo
-* Peter Fry
-* Philippe Creux
-* Søren Houen
+## 2.8.1 [☰](https://github.com/activeadmin/activeadmin/compare/v2.8.0..v2.8.1)
+### Bug Fixes
-## 0.4.0 - [compare](https://github.com/gregbell/active_admin/compare/v0.3.4...v0.4.0)
+* Fix `permitted_param` generation for `belongs_to` when `:param` is used. [#6460] by [@deivid-rodriguez]
+* Fix streaming CSV export. [#6451] by [@deivid-rodriguez]
+* Fix input string filter no rendering dropdown input when its column name ends with a ransack predicate. [#6422] by [@Fivell]
-### Upgrade Notes
+## 2.8.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.7.0..v2.8.0)
-If you're running on Rails 3.0.x, make sure to run `rails generate active_admin:assets`
-since we've changed both the CSS and JS files.
+### Enhancements
-### Deprecations
+* Allow using PORO decorators. [#6249] by [@brunvez]
+* Make sure `ActiveAdmin.routes` provides routes in a consistent order. [#6124] by [@jiikko]
+* Use proper closing tags for HTML in ModalDialog component. [#6221] by [@javierjulio]
+
+### Bug Fixes
+
+* Fix comment layout so regardless of size, each is aligned and spaced evenly. [#6393] by [@Ivanov-Anton]
+
+### Translation Improvements
+
+* Fix several Arabic translations. [#6368] by [@mshalaby]
+* Add missing `scope/all` italian translation. [#6341] by [@fuzziness]
+* Improve Japanese translation. [#6315] by [@rn0rno]
+* Fix es and es-MX sign_in and sign_up translation. [#6210] by [@roramirez]
+
+### Documentation
+
+* Fix filter_columns_for_large_association and filter_method_for_large_association examples. [#6232] by [@ndbroadbent]
+
+### Dependency Changes
+
+* Allow formtastic 4. [#6318] by [@deivid-rodriguez]
+* Drop Ruby 2.4 support. [#6198] by [@deivid-rodriguez]
+
+## 2.7.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.6.1..v2.7.0)
+
+### Enhancements
+
+* Extend menu to allow for nested submenus. [#5994] by [@taralbass]
+* Add Webpacker compatibility with opt-in config switch and installation generator. [#5855] by [@sgara]
+
+### Bug Fixes
+
+* Fix scopes renderer when resource has only optional scopes and their conditions are false. [#6149] by [@Looooong]
+* Fix some missing wrapper markup in "logged out" layout. [#6086] by [@irmela]
+* Fix some typos in Vietnamese translation. [#6099] by [@giapnhdev]
+
+## 2.6.1 [☰](https://github.com/activeadmin/activeadmin/compare/v2.6.0..v2.6.1)
+
+### Bug Fixes
+
+* Fix some ruby 2.7 warnings about keyword args. [#6000] by [@vcsjones]
+* Missing `create_another` translation in Vietnamese. [#6002] by [@imcvampire]
+* Using "destroy" for user facing message is too robotic, prefer "delete". [#6047] by [@vfonic]
+* Typo in confirmation message for comment deletion. [#6047] by [@vfonic]
+
+## 2.6.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.5.0..v2.6.0)
+
+### Enhancements
+
+* Display multiple flash messages in separate elements. [#5929] by [@mirelon]
+* Make delete confirmation messages in French & Spanish gender-neutral. [#5946] by [@cprodhomme]
-* In the initializer `config.allow_comments_in = []` is now
- `config.allow_comments = true`. Use the new namespace specific configurations
- to allow or disallow configuration within a specific namespace.
-* Removed `Object#to_html` in favour of `to_s`. Any Arbre components
- implementing a `to_html` method need to be updated to use `to_s` instead.
+### Bug Fixes
-### New Features
+* Export ModalDialog component to re-enable client side usage. [#5956] by [@sgara]
+* Use default ActionView options instead of default Formtastic options for DateRangeInput [#5957] by [@mirelon]
+* Fix i18n key in docs example to translate scopes. [#5943] by [@adler99]
-* Namespace specific configurations in the initializer ([@gregbell][])
-* [#624][]: Set an image as the site title using `config.site_title_image` in the
- Active Admin initializer. ([@mattvague][])
-* [#758][]: Create a standalone page in Active Admin using
- `ActiveAdmin.register_page` ([@pcreux][])
-* [#723][]: Register stylesheets with a media type ([@macfanatic][])
+## 2.5.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.4.0..v2.5.0)
### Enhancements
-* [#428][]: Paginated Collection now supports `:param_name` and `:download_links`.
- These two additions allow you to use the `paginated_collection` component multiple
- times on show screens. ([@samvincent][])
-* [#527][]: Refactored all form helpers to use Formtastic 2([@ebeigarts][])
-* [#551][]: Dashboards can now be conditionally displayed using `:if` ([@samvincent][])
-* [#555][]: scopes now accept `:if`. They only get displayed if the proc returns true ([@macfanatic][])
-* [#601][]: Breadcrumbs are internationalized ([@vairix-ssierra][])
-* [#605][]: Validations on ActiveAdmin::Comment should work with
- `accepts_nested_attributes_for` ([@DMajrekar ][])
-* [#623][]: Index table can sort on any table using `:sort => 'table.column'` ([@ZequeZ][])
-* [#638][]: Add `:label` option to `status_tag` component ([@fbuenemann][])
-* [#644][]: Added proper I18n support to pagination ([@fbuenemann][])
-* [#689][]: Scopes preserve title when provided as a string ([@macfanatic][])
-* [#711][]: Styles update. Now sexier and more refined design. Redesigned Scopes. Split
- css into smaller files. ([@mattvague][])
-* [#741][]: Default media type of css is now "all" instead of "screen" ([@sftsang][])
-* [#751][]: Pagination numbers work with a custom `[@per_page][]` ([@DMajrekar][])
-* `default_actions` in an index table only display implemented actions ([@watson][])
+* Azerbaijani translation. [#5078] by [@orkhan]
### Bug Fixes
-* [#590][]: Comments now work in the default namespace ([@jbarket][])
-* [#780][]: Fix stack level too deep exception when logout path is setup to use
- `:logout_path` named route. ([@george][])
-* [#637][]: Fix scope all I18n ([@fbuenemann][])
-* [#496][]: Remove global `Object#to_html` [@ebeigarts][]
-* [#470][], [#154][]: Arbre properly supports blocks that return Numeric values
- ([@bobbytables][], [@utkarshkukreti][], [@gregbell][])
-* [#897][]: Fix count display for collections with GROUP BY [@comboy][]
-
-### Dependencies
-
-* [#468][]: Removed vendored jQuery. Now depends on the jquery-rails gem. If you're
- running Rails 3.0.x (no asset pipeline), make sure to run
- `rails generate active_admin:assets` to generate the correct files. ([@gregbell][])
-* [#527][]: Active Admin now requires Formtastic 2.0 or greater ([@ebeigarts][])
-* [#711][]: Active admin now depends on Bourbon > 1.0.0. If you're using Rails
- 3.0.x, make sure to run `rails generate active_admin:assets` to ensure you
- have the correct css files ([@mattvague][])
-* [#869][]: Upgraded Kaminari to >= 0.13.0 and added support for
- `Kaminari.config.page_method_name`. Active Admin should now be happy if
- `will_paginate` is installed with it. ([@j][]-manu)
-* [#931][]: Support for Rails 3.2 added ([@mperham][])
-
-### Contributors
-
-274 commits by 42 authors
-
- * Greg Bell
- * Philippe Creux
- * Matt Vague
- * Felix Bünemann
- * Matthew Brewer
- * Edgars Beigarts
- * Mike Perham
- * Sam Vincent
- * Kieran Klaassen
- * Jonathan Barket
- * Ankur Sethi
- * Dinesh Majrekar
- * comboy
- * Juan E. Pemberthy
- * Leandro Moreira
- * Manu
- * Marc Riera
- * Radan Skorić
- * Rhys Powell
- * Sebastian Sierra
- * Sherman Tsang
- * Szymon Przybył
- * Thomas Watson Steen
- * Tim Habermaas
- * Yara Mayer
- * Zequez
- * asancio
- * emmek
- * Alexey Noskov
- * igmizo
- * Alli
- * Bendik Lynghaug
- * Douwe Homans
- * Eric Koslow
- * Eunsub Kim
- * Garami Gábor
- * George Anderson
- * Henrik Hodne
- * Ivan Storck
- * Jeff Dickey
- * John Ferlito
- * Josef Šimánek
-
-
-## 0.3.4 - [compare](https://github.com/gregbell/active_admin/compare/v0.3.3...v0.3.4)
-
-2 commits by 2 authors
+* Convert namespace to sym to prevent duplicate namespaces such as :foo and 'foo'. [#5931] by [@westonganger]
+* Use filter label when condition has a predicate. [#5886] by [@ko-lem]
+* Fix error when routing with array containing symbol. [#5870] by [@jwesorick]
+* Fix error when there is a model named `Tag` and `meta_tags` have been configured. [#5895] by [@micred], [@FabioRos] and [@deivid-rodriguez]
+* Allow specifying custom `input_html` for `DateRangeInput`. [#5867] by [@mirelon]
+* Adjust `#main_content` right margin to take into account possible custom values of `$sidebar-width` and `$section-padding`. [#5887] by [@guigs]
+* Improved polymorphic routes generation to avoid problems when multiple `belongs_to` are defined. [#5938] by [@leio10]
+
+### Dependency Changes
+
+* Support for Rails 5.0 and Rails 5.1 has been dropped. [#5877] by [@deivid-rodriguez]
+
+## 2.4.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.3.1..v2.4.0)
+
+### Enhancements
+
+* Make optimization to not use expensive COUNT queries also work for decorated actions. [#5811] by [@irmela]
+* Render a text filter instead of a select for large associations (opt-in). [#5548] by [@DanielHeath]
+* Improve German translations. [#5874] by [@juril33t]
+
+## 2.3.1 [☰](https://github.com/activeadmin/activeadmin/compare/v2.3.0..v2.3.1)
### Bug Fixes
-* Fix reloading issues across operating systems.
-* Fix issue where SASS was recompiling on every request. This can seriously
- decrease the load time of applications when running in development mode.
- Thanks [@dhiemstra][] for tracking this one down!
+* Revert ransack version pinning because 2.3 has an outstanding bug that affects quite a lot of users. See [this ransack issue](https://github.com/activerecord-hackery/ransack/issues/1039) for more information. [#5854] by [@deivid-rodriguez]
-### Contributors
+## 2.3.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.2.0..v2.3.0)
-* Danny Hiemstra
-* Greg Bell
+### Enhancements
+
+* Bump minimum ransack requirement to make sure everyone gets a version that works ok with all supported versions of Rails. [#5831] by [@deivid-rodriguez]
-## 0.3.3 - [compare](https://github.com/gregbell/active_admin/compare/v0.3.2...v0.3.3)
+### Bug Fixes
-1 commit by 1 author
+* Fix CSVBuilder not respecting `ActiveAdmin.application.csv_options = { humanize_name: false }` setting. [#5800] by [@HappyKadaver]
+* Fix crash when displaying current filters after filtering by a nested resource. [#5816] by [@deivid-rodriguez]
+* Fix pagination when `pagination_total` is false to not show a "Last" link, since it's incorrect because we don't have the total pages information. [#5822] by [@deivid-rodriguez]
+* Fix optional nested resources causing incorrect routes to be generated, when renamed resources (through `:as` option) are involved. [#5826] by [@ndbroadbent], [@Kris-LIBIS] and [@deivid-rodriguez]
+* Fix double modal issue in applications using turbolinks 5. [#5842] by [@sgara]
+
+## 2.2.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.1.0..v2.2.0)
### Enhancements
-* Only reload Active Admin when files in the load paths have changed. This is a
- major speed increase in development mode. Also helps with memory consumption
- because we aren't reloading Active admin all the time.
+* The `status_tag` component now supports different labels for `false` and `nil` boolean cases through the locale. Both default to display "No" for backwards compatibility. [#5794] by [@javierjulio]
+* Add Macedonian locale. [#5710] by [@violeta-p]
-### Contributors
+### Bug Fixes
+
+* Fix pundit policy retrieving for static pages when the pundit namespace is `:active_admin`. [#5777] by [@kwent]
+* Fix show page title not being properly escaped if title's content included HTML. [#5802] by [@deivid-rodriguez]
+* Revert [21b6138f] from [#5740] since it actually caused the performance in development to regress. [#5801] by [@deivid-rodriguez]
+
+## 2.1.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.0.0..v2.1.0)
+
+### Bug Fixes
-* Greg Bell
+* Ensure application gets reloaded only once. [#5740] by [@jscheid]
+* Crash when rendering comments from a custom controller block. [#5758] by [@deivid-rodriguez]
+* Switch `sass` dependency to `sassc-rails`, since `sass` is no longer supported and since it restores support for directly importing `css` files. [#5504] by [@deivid-rodriguez]
-## 0.3.2 - [compare](https://github.com/gregbell/active_admin/compare/v0.3.1...v0.3.2)
+### Dependency Changes
-45 commits by 15 contributors
+* Support for ruby 2.3 has been removed. [#5751] by [@deivid-rodriguez]
+
+## 2.0.0 [☰](https://github.com/activeadmin/activeadmin/compare/v2.0.0.rc2..v2.0.0)
+
+_No changes_.
+
+## 2.0.0.rc2 [☰](https://github.com/activeadmin/activeadmin/compare/v2.0.0.rc1..v2.0.0.rc2)
### Enhancements
-* Site title can now be a link. Use config.site_title_link in
- config/initializers/active_admin.rb
-* i18n support for Japanese
-* i18n support for Bulgarian
-* i18n support for Swedish
+* Require arbre `~> 1.2, >= 1.2.1`. [#5726] by [@ionut998], and [#5738] by [@deivid-rodriguez]
+
+## 2.0.0.rc1 [☰](https://github.com/activeadmin/activeadmin/compare/v1.4.3..v2.0.0.rc1)
+
+### Enhancements
+
+* Add your own content to the site `
`, like analytics. [#5590] by [@buren]
+
+ ```ruby
+ ActiveAdmin.setup do |config|
+ config.head = ''.html_safe
+ end
+ ```
+
+* Consider authorization when displaying comments in show page. [#5555] by [@amiuhle]
+* Add better support for rendering lists. [#5370] by [@dkniffin]
+* Undeprecate `config.register_stylesheet` and `config.register_javascript` for lack of better solution for including external assets. It might be reevaluated in the future. [#5662] by [@deivid-rodriguez]
+
+### Security Fixes
+
+* Prevent leaking hashed passwords via user CSV export and adds a config option for sensitive attributes. [#5486] by [@chrp]
### Bug Fixes
-* DeviseGenerator respects singular table names
-* Active Admin no longer assumes sass-rails is installed
-* Arbre::Context passes methods to the underlying html which fixes
- issues on different types of servers (and on Heroku)
-* [#45][]: Fix for multibyte characters ([@tricknotes][])
-* [#505][]: Fix titlebar height when no breadcrumb ([@mattvague][])
-* Fixed vertical align in dashboard
-* Fixed i18n path's for multi-word model names
+* Fix for paginated collections with `per_page: Array, pagination_total: false`. [#5627] by [@bartoszkopinski]
+* Restrict ransack requirement to >= 2.1.1 to play nice with Rails 5.2.2. [#5632] by [@deivid-rodriguez]
+* Bad interpolation variables on pagination keys in Lithuanian translation. [#5631] by [@deivid-rodriguez]
+* Tabs are not correctly created when using non-transliteratable characters as title. [#5650] by [@panasyuk]
+* Sidebar title internationalization. [#5417] by [@WaKeMaTTa]
+* `filter` labels not allowing a `Proc` to be passed. [#5418] by [@WaKeMaTTa]
-### Dependencies
+### Dependency Changes
-* Formtastic 2.0.0 breaks Active Admin. Locking to < 2.0.0
+* Rails 4.2 support has been dropped. [#5104] by [@javierjulio] and [@deivid-rodriguez]
+* Dependency on coffee-rails has been removed. [#5081] by [@javierjulio]
+ If your application uses coffescript but was relying on ActiveAdmin to provide
+ the dependency, you need to add the `coffee-script` gem to your `Gemfile` to
+ restore it. If your only usage of coffescript was the
+ `active_admin.js.coffee` generated by ActiveAdmin's generator, you can also
+ convert that file to plain JS (`//= require active_admin/base` if you
+ didn't add any stuff to it).
+* Devise 3 support has been dropped. [#5608] by [@deivid-rodriguez] and [@javierjulio]
+* `action_item` without a name has been removed. [#5099] by [@javierjulio]
-### Contributors
+## 1.4.3 [☰](https://github.com/activeadmin/activeadmin/compare/v1.4.2..v1.4.3)
-* Amiel Martin
-* Christian Hjalmarsson
-* Edgars Beigarts
-* Greg Bell
-* Jan Dupal
-* Joe Van
-* Mark Roghelia
-* Mathieu Martin
-* Matt Vague
-* Philippe Creux
-* Ryunosuke SATO
-* Sam Vincent
-* Trace Wax
-* Tsvetan Roustchev
-* l4u
+### Bug Fixes
-## 0.3.1 - [compare](https://github.com/gregbell/active_admin/compare/v0.3.0...v0.3.1)
+* Fix `form` parameter to `batch_action` no longer accepting procs. [#5611] by [@buren] and [@deivid-rodriguez]
+* Fix passing a proc to `scope_to`. [#5611] by [@deivid-rodriguez]
-* Only support InheritedResources up to 1.2.2
+## 1.4.2 [☰](https://github.com/activeadmin/activeadmin/compare/v1.4.1..v1.4.2)
-## 0.3.0 - [compare](https://github.com/gregbell/active_admin/compare/v0.2.2...v0.3.0)
+### Bug Fixes
-326 commits by 35 contributors
+* Fix `input_html` filter option evaluated only once. [#5376] by [@kjeldahl]
-### New Features
+## 1.4.1 [☰](https://github.com/activeadmin/activeadmin/compare/v1.4.0..v1.4.1)
-* I18n! Now supported in 10 languages!
-* Customizeable CSV ([@pcreux][], [@gregbell][])
-* Menus now support `if` and `priority` (Moritz Lawitschka)
-* Rails 3.1 support
-* Asset pipeline support ([@gregbell][])
-* `skip_before_filter` now supported in DSL ([@shayfrendt][])
-* Added a blank slate design ([@mattvague][])
-* Collection and Member actions use the Active Admin layout ([@gregbell][])
+### Bug Fixes
+
+* Fix menu item link with method delete. [#5583] by [@tiagotex]
+
+## 1.4.0 [☰](https://github.com/activeadmin/activeadmin/compare/v1.3.1..v1.4.0)
### Enhancements
-* Better I18n config file loading ([@fabiokr][])
-* `TableFor` now supports I18n headers ([@fabiokr][])
-* `AttributesTable` now supports I18n attributes ([@fabiokr][])
-* Member actions all use CSS class `member_link` ([@doug316][])
-* Made `status_tag` an Arbre component ([@pcreux][])
-* CSV downloads have sexy names such as "articles-2011-06-21.csv" ([@pcreux][])
-* Created `ActiveAdmin::Setting` to easily create settings ([@gregbell][])
-* New datepicker styles ([@mattvague][])
-* Set `[@page_title][]` in member action to render a custom title ([@gregbell][])
-* [#248][]: Settable logout link options in initializer ([@gregbell][])
-* Added a DependencyChecker that warns if dependencies aren't met ([@pcreux][])
+* Add missing I18n for comments. [#5458], [#5461] by [@mauriciopasquier]
+* Fix batch_actions.delete_confirmation translation in zh-CN.yml. [#5453] by [@ShallmentMo]
+* Add some missing italian translations. [#5433] by [@stefsava]
+* Enhance some chinese translations. [#5413] by [@shouya]
+* Add missing filter predicate translations to nb. [#5357] by [@rogerkk]
+* Add missing norwegian comment translations. [#5375] by [@rogerkk]
+* Add missing dutch translations. [#5368] by [@dennisvdvliet]
+* Add missing german translations. [#5341] by [@eikes]
+* Add missing spanish translation. [#5336] by [@mconiglio]
+* Add from and to predicates for russian language. [#5330] by [@glebtv]
+* Fix typo in finnish translation. [#5320] by [@JiiHu]
+* Add missing turkish translations. [#5295] by [@kobeumut]
+* Add missing chinese translations. [#5266] by [@jasl]
+* Allow proc label in datepicker input. [#5408] by [@tiagotex]
+* Add `group` attribute to scopes in order to show them in grouped. [#5359] by [@leio10]
+* Add missing polish translations and improve existing ones. [#5537] by [@Wowu]
+* Add `priority` option to `action_item`. [#5334] by [@andreslemik]
### Bug Fixes
-* [#52][]: Fix update action with STI models ([@gregbell][])
-* [#122][]: Fix sortable columns on nested resources ([@knoopx][])
-* Fix so that Dashboard Sections can appear in root namespace ([@knoopx][])
-* [#131][]: Fixed `status_tag` with nil content ([@pcreux][])
-* [#110][]: Fixed dropdown menu floats in Firefox ([@mattvague][])
-* Use quoted table names ([@krug][])
-* Fixed CSS float of `.paginated_collection_contents` bug in Firefox ([@mattvague][])
-* Removed unwanted gradient in IE in attribute table headers ([@emzeq][])
-* [#222][]: Added `Arbre::Context#length` for Rack servers ([@gregbell][])
-* [#255][]: Fixed problem with dropdown menus in IE8 and IE9 ([@mattvague][])
-* [#235][]: Default sort order should use primary_key ([@gregbell][])
-* [#197][]: Fixed issues with #form params disappearing ([@rolfb][])
-* [#186][]: Fixes for when `default_namespace = false` ([@gregbell][])
-* [#135][]: Comments on STI classes redirect correctly ([@gregbell][])
-* [#77][]: Fixed performance issue where ActiveRecord::Base.all was being called ([@pcreux][])
-* [#332][]: Fixed Devise redirection when in false namespace ([@gregbell][])
-* [#171][]: Fixed issue where class names would clash with HTML object names ([@gregbell][])
-* [#381][]: Fixed issues with Devise < 1.2 ([@pcreux][])
-* [#369][]: Added support for pluralized model names such as News ([@gregbell][])
-* [#42][]: Default forms work with polymorphic associations ([@mattvague][])
-
-### Dependencies
-
-* Switched from will_paginate to Kaminari for pagination ([@mwindwer][])
-* Removed dependency on InheritedViews ([@gregbell][])
-* Removed Jeweler. Using Bundler and a gemspec ([@gregbell][])
-
-### Contributors
-
-* Armand du Plessis
-* Aurelio Agundez
-* Bruno Bonamin
-* Chris Ostrowski
-* Corey Woodcox
-* DeLynn Berry
-* Doug Puchalski
-* Fabio Kreusch
-* Greg Bell
-* Ismael G Marin C
-* Jackson Pires
-* Jesper Hvirring Henriksen
-* Josef Šimánek
-* Jørgen Orehøj Erichsen
-* Liborio Cannici
-* Matt Vague
-* Matthew Windwer
-* Moritz Lawitschka
-* Nathan Le Ray
-* Nicolas Mosconi
-* Philippe Creux
-* Rolf Bjaanes
-* Ryan D Johnson
-* Ryan Krug
-* Shay Frendt
-* Steve Klabnik
-* Tiago Rafael Godinho
-* Toby Hede
-* Vijay Dev
-* Víctor Martínez
-* doabit
-* hoverlover
-* nhurst
-* whatthewhat
-* Łukasz Anwajler
-
-
-## 0.2.2 (2011-05-26) - [compare](https://github.com/gregbell/active_admin/compare/v0.2.1...v0.2.2)
-
-68 Commits by 13 Contributors
-
-### Features & Enhancements
-
-* Arbre includes self closing tags ([#100][])
-* Controller class & action added to body as CSS classes ([#99][])
-* HAML is not required by default ([#92][])
-* Devise login now respects Devise.authentication_keys ([#69][])
-* Active Admin no longer uses ActiveRecord::Base#search ([#28][])
-* Resource's can now override the label in the menu ([#48][])
-* Subdirectories are now loaded in the Active Admin load path
+* Fixed the string representation of the resolved `sort_key` when no explicit `sortable` attribute is passed. [#5464] by [@chumakoff]
+* Fixed docs on the column `sortable` attribute (which actually doesn't have to be explicitly specified when a block is passed to column). [#5464] by [@chumakoff]
+* Fixed `if:` scope option when a lambda is passed. [#5501] by [@deivid-rodriguez]
+* Comment validation adding redundant errors when resource is missing. [#5517] by [@deivid-rodriguez]
+* Fixed resource filtering by association when the resource has custom primary key. [#5446] by [@wasifhossain]
+* Fixed "create another" checkbox styling. [#5324] by [@faucct]
+
+## 1.3.1 [☰](https://github.com/activeadmin/activeadmin/compare/v1.3.0..v1.3.1)
### Bug Fixes
-* Sort order now includes table name ([#38][])
-* Fixed table_for 'odd', 'even' row classes ([#96][])
-* Fixed Devise installation if AdminUser already exists ([#95][])
-* Fixed issues when ActiveAdmin.default_namespaces is false ([#32][])
-* Added styles for missing HTML 5 inputs ([#31][])
-* Fixed issue if adding empty Active Admin Comment ([#21][])
-* Fixed layout issues in FF 4 ([#22][])
-* Use Sass::Plugin.options[:css_location] instead of Rails.root ([#55][])
-
-### Test Suite
-
-* Update RSpec to latest & fix specs (Thanks Ben Marini & Jeremt Ruppel!) ([#100][])
-* Added tests for STI models ([#52][])
-
-### Contributors
-
-* Ben Marini
-* Bookis Smuin
-* Caley Woods
-* Doug Puchalski
-* Federico Romero
-* Greg Bell
-* Ian MacLeod
-* Jeremy Ruppel
-* Jordan Sitkin
-* Juha Suuraho
-* Mathieu Martin
-* Paul Annesley
-* Philippe Creux
-
-## 0.2.1 (2011-05-12) - [compare](https://github.com/gregbell/active_admin/compare/v0.2.0...v0.2.1)
+* gemspec should have more permissive ransack dependency. [#5448] by [@varyonic]
+
+## 1.3.0 [☰](https://github.com/activeadmin/activeadmin/compare/v1.2.1..v1.3.0)
+
+### Enhancements
+
+* Rails 5.2 support [#5343] by [@varyonic], [#5399], [#5401] by [@zorab47]
+
+## 1.2.1 [☰](https://github.com/activeadmin/activeadmin/compare/v1.2.0..v1.2.1)
### Bug Fixes
-* Fixed issue with dashboard rendering a sidebar
-## 0.2.0 (2011-05-12) - [compare](https://github.com/gregbell/active_admin/compare/v0.1.1...v0.2.0)
+* Resolve issue with [#5275] preventing XSS in filters sidebar. [#5299] by [@faucct]
+
+## 1.2.0 [☰](https://github.com/activeadmin/activeadmin/compare/v1.1.0..v1.2.0)
-0.2.0 is essentially an entire re-write of Active Admin. Here are some
-of the highlights. 250 commits. Enough said.
+### Enhancements
+
+* Do not display pagination info when there are no comments. [#5119] by [@alex-bogomolov]
+* Revert generated config files to pluralized. [#5120] by [@varyonic], [#5137] by [@deivid-rodriguez]
+* Warn when action definition overwrites controller method. [#5167] by [@aarek]
+* Better performance of comments show view. [#5208] by [@dhyegofernando]
+* Mitigate memory bloat [#4118] with CSV exports. [#5251] by [@f1sherman]
+* Fix issue applying custom decorations. [#5253] by [@faucct]
+* Brazilian locale updated. [#5125] by [@renotocn]
+* Japanese locale updated. [#5143] by [@5t111111], [#5157] by [@innparusu95]
+* Italian locale updated. [#5180] by [@blocknotes]
+* Swedish locale updated. [#5187] by [@jawa]
+* Vietnamese locale updated. [#5194] by [@Nguyenanh]
+* Esperanto locale added. [#5210] by [@RobinvanderVliet]
+
+### Bug Fixes
-### Features & Enhancements
+* Fix a couple of issues rendering filter labels. [#5223] by [@wspurgin]
+* Prevent NameError when filtering on a namespaced association. [#5240] by [@DanielHeath]
+* Fix undefined method error in Ransack when building filters. [#5238] by [@wspurgin]
+* Fixed [#5198] Prevent XSS on sidebar's current filter rendering. [#5275] by [@deivid-rodriguez]
+* Sanitize display_name. [#5284] by [@markstory]
-* Full visual redesign
-* Integrated Devise for authentication
-* Brand new view and component layer called Arbre (Project coming soon)
-* Added ActiveAdmin::Comments
+## 1.1.0 [☰](https://github.com/activeadmin/activeadmin/compare/v1.0.0..v1.1.0)
### Bug Fixes
-* Too many to list! Been in production for close to a year
+* Fixed [#5093] Handle table prefix & table suffix for `ActiveAdminComment` model
+* Fixed [#4173] by including the default Kaminari templates. [#5069] by [@javierjulio]
+* Fixed [#5043]. Do not crash in sidebar rendering when a default scope is not specified. [#5044] by [@Fivell]
+* Fixed [#3894]. Make tab's component work with non-ascii titles. [#5046] by [@Fivell]
+
+### Dependency Changes
+
+* Ruby 2.1 support has been dropped. [#5003] by [@deivid-rodriguez]
+* Replaced `sass-rails` with `sass` dependency. [#5037] by [@javierjulio]
+* Removed `jquery-ui-rails` as a dependency. [#5052] by [@javierjulio]
+ The specific jQuery UI assets used are now within the vendor directory. This
+ will be replaced by alternatives and dropped entirely in a major release.
+ Please remove any direct inclusions of `//= require jquery-ui`. This allows us
+ to upgrade to jquery v3.
+
+### Deprecations
+
+* Deprecated `config.register_stylesheet` and `config.register_javascript`. Import your CSS and JS files in `active_admin.scss` or `active_admin.js`. [#5060] by [@javierjulio]
+* Deprecated `type` param from `status_tag` and related CSS classes [#4989] by [@javierjulio]
+ The method signature has changed from:
+
+ ```ruby
+ status_tag(status, :ok, class: 'completed', label: 'on')
+ ```
+
+ to:
+
+ ```ruby
+ status_tag(status, class: 'completed ok', label: 'on')
+ ```
+
+ The following CSS classes have been deprecated and will be removed in the future:
-## 0.1.1 (2010-09-15) - [compare](https://github.com/gregbell/active_admin/compare/v0.1.0...v0.1.1)
+ ```css
+ .status_tag {
+ &.ok, &.published, &.complete, &.completed, &.green { background: #8daa92; }
+ &.warn, &.warning, &.orange { background: #e29b20; }
+ &.error, &.errored, &.red { background: #d45f53; }
+ }
+ ```
+
+### Enhancements
+
+* Support proc as an input_html option value when declaring filters. [#5029] by [@Fivell]
+* Base localization support, better associations handling for active filters sidebar. [#4951] by [@Fivell]
+* Allow AA scopes to return paginated collections. [#4996] by [@Fivell]
+* Added `scopes_show_count` configuration to setup show_count attribute for scopes globally. [#4950] by [@Fivell]
+* Allow custom panel title given with `attributes_table`. [#4940] by [@ajw725]
+* Allow passing a class to `action_item` block. [#4997] by [@Fivell]
+* Add pagination to the comments section. [#5088] by [@alex-bogomolov]
+
+## 1.0.0 [☰](https://github.com/activeadmin/activeadmin/compare/v0.6.3..v1.0.0)
+
+### Breaking Changes
+
+* Rename `allow_comments` to `comments` for more consistent naming. [#3695] by [@pranas]
+* JavaScript `window.AA` has been removed, use `window.ActiveAdmin`. [#3606] by [@timoschilling]
+* `f.form_buffers` has been removed. [#3486] by [@varyonic]
+* Iconic has been removed. [#3553] by [@timoschilling]
+* `config.show_comments_in_menu` has been removed, see `config.comments_menu`. [#4187] by [@drn]
+* Rails 3.2 & Ruby 1.9.3 support has been dropped. [#4848] by [@deivid-rodriguez]
+* Ruby 2.0.0 support has been dropped. [#4851] by [@deivid-rodriguez]
+* Rails 4.0 & 4.1 support has been dropped. [#4870] by [@deivid-rodriguez]
+
+### Enhancements
+
+* Migration from Metasearch to Ransack. [#1979] by [@seanlinsley]
+* Rails 4 support. [#2326] by many people :heart:
+* Rails 4.2 support. [#3731] by [@gonzedge] and [@timoschilling]
+* Rails 5 support. [#4254] by [@seanlinsley]
+* Rails 5.1 support. [#4882] by [@varyonic]
+* "Create another" checkbox for the new resource page. [#4477] by [@bolshakov]
+* Page supports belongs_to. [#4759] by [@Fivell] and [@zorab47]
+* Support for custom sorting strategies. [#4768] by [@Fivell]
+* Stream CSV downloads as they're generated. [#3038] by [@craigmcnamara]
+* Disable streaming in development for easier debugging. [#3535] by [@seanlinsley]
+* Improved code reloading. [#3783] by [@chancancode]
+* Do not auto link to inaccessible actions. [#3686] by [@pranas]
+* Allow to enable comments on per-resource basis. [#3695] by [@pranas]
+* Unify DSL for index `actions` and `actions dropdown: true`. [#3463] by [@timoschilling]
+* Add DSL method `includes` for `ActiveRecord::Relation#includes`. [#3464] by [@timoschilling]
+* BOM (byte order mark) configurable for CSV download. [#3519] by [@timoschilling]
+* Column block on table index is now sortable by default. [#3075] by [@dmitry]
+* Allow Arbre to be used inside ActiveAdmin forms. [#3486] by [@varyonic]
+* Make AA ORM-agnostic. [#2545] by [@johnnyshields]
+* Add multi-record support to `attributes_table_for`. [#2544] by [@zorab47]
+* Table CSS classes are now prefixed to prevent clashes. [#2532] by [@TimPetricola]
+* Allow Inherited Resources shorthand for redirection. [#2001] by [@seanlinsley]
+
+ ```ruby
+ controller do
+ # Redirects to index page instead of rendering updated resource
+ def update
+ update!{ collection_path }
+ end
+ end
+ ```
+
+* Accept block for download links. [#2040] by [@potatosalad]
+
+ ```ruby
+ index download_links: ->{ can?(:view_all_download_links) || [:pdf] }
+ ```
+
+* Comments menu can be customized via configuration passed to `config.comments_menu`. [#4187] by [@drn]
+* Added `config.route_options` to namespace to customize routes. [#4731] by [@stereoscott]
+
+### Security Fixes
+
+* Prevents access to formats that the user not permitted to see. [#4867] by [@Fivell] and [@timoschilling]
+* Prevents potential DOS attack via Ruby symbols. [#1926] by [@seanlinsley]
+ * [this isn't an issue for those using Ruby >= 2.2](https://rubykaigi.org/2014/presentation/S-NarihiroNakamura)
### Bug Fixes
-* Fixed issues running on Ruby 1.9.2
-
-## 0.1.0
-
-* Initial release
-
-
-[#21]: https://github.com/gregbell/active_admin/issues/21
-[#22]: https://github.com/gregbell/active_admin/issues/22
-[#28]: https://github.com/gregbell/active_admin/issues/28
-[#31]: https://github.com/gregbell/active_admin/issues/31
-[#32]: https://github.com/gregbell/active_admin/issues/32
-[#34]: https://github.com/gregbell/active_admin/issues/34
-[#38]: https://github.com/gregbell/active_admin/issues/38
-[#42]: https://github.com/gregbell/active_admin/issues/42
-[#45]: https://github.com/gregbell/active_admin/issues/45
-[#48]: https://github.com/gregbell/active_admin/issues/48
-[#52]: https://github.com/gregbell/active_admin/issues/52
-[#55]: https://github.com/gregbell/active_admin/issues/55
-[#69]: https://github.com/gregbell/active_admin/issues/69
-[#70]: https://github.com/gregbell/active_admin/issues/70
-[#77]: https://github.com/gregbell/active_admin/issues/77
-[#92]: https://github.com/gregbell/active_admin/issues/92
-[#95]: https://github.com/gregbell/active_admin/issues/95
-[#96]: https://github.com/gregbell/active_admin/issues/96
-[#99]: https://github.com/gregbell/active_admin/issues/99
-[#100]: https://github.com/gregbell/active_admin/issues/100
-[#101]: https://github.com/gregbell/active_admin/issues/101
-[#110]: https://github.com/gregbell/active_admin/issues/110
-[#122]: https://github.com/gregbell/active_admin/issues/122
-[#131]: https://github.com/gregbell/active_admin/issues/131
-[#135]: https://github.com/gregbell/active_admin/issues/135
-[#154]: https://github.com/gregbell/active_admin/issues/154
-[#171]: https://github.com/gregbell/active_admin/issues/171
-[#186]: https://github.com/gregbell/active_admin/issues/186
-[#197]: https://github.com/gregbell/active_admin/issues/197
-[#222]: https://github.com/gregbell/active_admin/issues/222
-[#235]: https://github.com/gregbell/active_admin/issues/235
-[#248]: https://github.com/gregbell/active_admin/issues/248
-[#255]: https://github.com/gregbell/active_admin/issues/255
-[#332]: https://github.com/gregbell/active_admin/issues/332
-[#369]: https://github.com/gregbell/active_admin/issues/369
-[#381]: https://github.com/gregbell/active_admin/issues/381
-[#409]: https://github.com/gregbell/active_admin/issues/409
-[#428]: https://github.com/gregbell/active_admin/issues/428
-[#468]: https://github.com/gregbell/active_admin/issues/468
-[#470]: https://github.com/gregbell/active_admin/issues/470
-[#496]: https://github.com/gregbell/active_admin/issues/496
-[#497]: https://github.com/gregbell/active_admin/issues/497
-[#505]: https://github.com/gregbell/active_admin/issues/505
-[#527]: https://github.com/gregbell/active_admin/issues/527
-[#551]: https://github.com/gregbell/active_admin/issues/551
-[#555]: https://github.com/gregbell/active_admin/issues/555
-[#590]: https://github.com/gregbell/active_admin/issues/590
-[#601]: https://github.com/gregbell/active_admin/issues/601
-[#605]: https://github.com/gregbell/active_admin/issues/605
-[#623]: https://github.com/gregbell/active_admin/issues/623
-[#624]: https://github.com/gregbell/active_admin/issues/624
-[#637]: https://github.com/gregbell/active_admin/issues/637
-[#638]: https://github.com/gregbell/active_admin/issues/638
-[#644]: https://github.com/gregbell/active_admin/issues/644
-[#689]: https://github.com/gregbell/active_admin/issues/689
-[#711]: https://github.com/gregbell/active_admin/issues/711
-[#723]: https://github.com/gregbell/active_admin/issues/723
-[#741]: https://github.com/gregbell/active_admin/issues/741
-[#751]: https://github.com/gregbell/active_admin/issues/751
-[#758]: https://github.com/gregbell/active_admin/issues/758
-[#780]: https://github.com/gregbell/active_admin/issues/780
-[#822]: https://github.com/gregbell/active_admin/issues/822
-[#865]: https://github.com/gregbell/active_admin/issues/865
-[#869]: https://github.com/gregbell/active_admin/issues/869
-[#897]: https://github.com/gregbell/active_admin/issues/897
-[#905]: https://github.com/gregbell/active_admin/issues/905
-[#931]: https://github.com/gregbell/active_admin/issues/931
-[#960]: https://github.com/gregbell/active_admin/issues/960
-[#971]: https://github.com/gregbell/active_admin/issues/971
-[#978]: https://github.com/gregbell/active_admin/issues/978
-[#983]: https://github.com/gregbell/active_admin/issues/983
-[#993]: https://github.com/gregbell/active_admin/issues/993
-[#994]: https://github.com/gregbell/active_admin/issues/994
-[#1013]: https://github.com/gregbell/active_admin/issues/1013
-[#1016]: https://github.com/gregbell/active_admin/issues/1016
-[#1023]: https://github.com/gregbell/active_admin/issues/1023
-[#1032]: https://github.com/gregbell/active_admin/issues/1032
-[#1033]: https://github.com/gregbell/active_admin/issues/1033
-[#1041]: https://github.com/gregbell/active_admin/issues/1041
-[#1063]: https://github.com/gregbell/active_admin/issues/1063
-[#1117]: https://github.com/gregbell/active_admin/issues/1117
-[#1439]: https://github.com/gregbell/active_admin/issues/1439
-[#1609]: https://github.com/gregbell/active_admin/issues/1609
-[#1626]: https://github.com/gregbell/active_admin/issues/1626
-[#1647]: https://github.com/gregbell/active_admin/issues/1647
-[#1664]: https://github.com/gregbell/active_admin/issues/1664
-[#1668]: https://github.com/gregbell/active_admin/issues/1668
-[#1681]: https://github.com/gregbell/active_admin/issues/1681
-[#1683]: https://github.com/gregbell/active_admin/issues/1683
-[#1699]: https://github.com/gregbell/active_admin/issues/1699
-[#1745]: https://github.com/gregbell/active_admin/issues/1745
-[#1752]: https://github.com/gregbell/active_admin/issues/1752
-[#1775]: https://github.com/gregbell/active_admin/issues/1775
-[#1782]: https://github.com/gregbell/active_admin/issues/1782
-[#1783]: https://github.com/gregbell/active_admin/issues/1783
-[#1788]: https://github.com/gregbell/active_admin/issues/1788
-[#1801]: https://github.com/gregbell/active_admin/issues/1801
-[#1804]: https://github.com/gregbell/active_admin/issues/1804
-[#1805]: https://github.com/gregbell/active_admin/issues/1805
-[#1817]: https://github.com/gregbell/active_admin/issues/1817
-[#1834]: https://github.com/gregbell/active_admin/issues/1834
-[#1861]: https://github.com/gregbell/active_admin/issues/1861
-[#1867]: https://github.com/gregbell/active_admin/issues/1867
-[#1871]: https://github.com/gregbell/active_admin/issues/1871
-[#1873]: https://github.com/gregbell/active_admin/issues/1873
-[#1893]: https://github.com/gregbell/active_admin/issues/1893
-[#1896]: https://github.com/gregbell/active_admin/issues/1896
-[#1908]: https://github.com/gregbell/active_admin/issues/1908
-[#1913]: https://github.com/gregbell/active_admin/issues/1913
-[#1929]: https://github.com/gregbell/active_admin/issues/1929
-[#1933]: https://github.com/gregbell/active_admin/issues/1933
-[#1937]: https://github.com/gregbell/active_admin/issues/1937
-[#1940]: https://github.com/gregbell/active_admin/issues/1940
-[#1947]: https://github.com/gregbell/active_admin/issues/1947
-[#1960]: https://github.com/gregbell/active_admin/issues/1960
-[#1961]: https://github.com/gregbell/active_admin/issues/1961
-[#1962]: https://github.com/gregbell/active_admin/issues/1962
-[#1966]: https://github.com/gregbell/active_admin/issues/1966
-[#1967]: https://github.com/gregbell/active_admin/issues/1967
-[#1973]: https://github.com/gregbell/active_admin/issues/1973
-[#2000]: https://github.com/gregbell/active_admin/issues/2000
-[#2018]: https://github.com/gregbell/active_admin/issues/2018
-[#2043]: https://github.com/gregbell/active_admin/issues/2043
-[#2044]: https://github.com/gregbell/active_admin/issues/2044
-[@Bishop]: https://github.com/Bishop
-[@BoboFraggins]: https://github.com/BoboFraggins
-[@DMajrekar]: https://github.com/DMajrekar
-[@Daxter]: https://github.com/Daxter
-[@EtienneDepaulis]: https://github.com/EtienneDepaulis
-[@MoritzMoritz]: https://github.com/MoritzMoritz
-[@TBAA]: https://github.com/TBAA
-[@TiagoCardoso1983]: https://github.com/TiagoCardoso1983
-[@ZequeZ]: https://github.com/ZequeZ
-[@adrienkohlbecker]: https://github.com/adrienkohlbecker
-[@ai]: https://github.com/ai
+* Fixes filters for `has_many :through` relationships. [#2541] by [@shekibobo]
+* "New" action item now only shows up on the index page. bf659bc by [@seanlinsley]
+* Fixes comment creation bug with aliased resources. 9a082486 by [@seanlinsley]
+* Fixes the deletion of `:if` and `:unless` from filters. [#2523] by [@PChambino]
+
+### Deprecations
+
+* `ActiveAdmin::Event` (`ActiveAdmin::EventDispatcher`). [#3435] by [@timoschilling]
+ `ActiveAdmin::Event` will be removed in a future version, ActiveAdmin switched
+ to use `ActiveSupport::Notifications`
+ NOTE: The blog parameters has changed:
+
+ ```ruby
+ ActiveSupport::Notifications.subscribe ActiveAdmin::Application::BeforeLoadEvent do |event, *args|
+ # some code
+ end
+
+ ActiveSupport::Notifications.publish ActiveAdmin::Application::BeforeLoadEvent, "some data"
+ ```
+
+* `action_item` without a name, to introduce a solution for removing action items (`remove_action_item(name)`). [#3091] by [@amiel]
+
+## Previous Changes
+
+Please check [0-6-stable] for previous changes.
+
+[0-6-stable]: https://github.com/activeadmin/activeadmin/blob/0-6-stable/CHANGELOG.md
+
+[#1926]: https://github.com/activeadmin/activeadmin/issues/1926
+[#1979]: https://github.com/activeadmin/activeadmin/issues/1979
+[#2001]: https://github.com/activeadmin/activeadmin/issues/2001
+[#2040]: https://github.com/activeadmin/activeadmin/issues/2040
+[#2326]: https://github.com/activeadmin/activeadmin/issues/2326
+[#2523]: https://github.com/activeadmin/activeadmin/issues/2523
+[#2532]: https://github.com/activeadmin/activeadmin/issues/2532
+[#2541]: https://github.com/activeadmin/activeadmin/issues/2541
+[#2544]: https://github.com/activeadmin/activeadmin/issues/2544
+[#2545]: https://github.com/activeadmin/activeadmin/issues/2545
+[#3038]: https://github.com/activeadmin/activeadmin/issues/3038
+[#3075]: https://github.com/activeadmin/activeadmin/issues/3075
+[#3463]: https://github.com/activeadmin/activeadmin/issues/3463
+[#3464]: https://github.com/activeadmin/activeadmin/issues/3464
+[#3486]: https://github.com/activeadmin/activeadmin/issues/3486
+[#3519]: https://github.com/activeadmin/activeadmin/issues/3519
+[#3535]: https://github.com/activeadmin/activeadmin/issues/3535
+[#3553]: https://github.com/activeadmin/activeadmin/issues/3553
+[#3606]: https://github.com/activeadmin/activeadmin/issues/3606
+[#3686]: https://github.com/activeadmin/activeadmin/issues/3686
+[#3695]: https://github.com/activeadmin/activeadmin/issues/3695
+[#3731]: https://github.com/activeadmin/activeadmin/issues/3731
+[#3783]: https://github.com/activeadmin/activeadmin/issues/3783
+[#3894]: https://github.com/activeadmin/activeadmin/issues/3894
+[#4118]: https://github.com/activeadmin/activeadmin/issues/4118
+[#4173]: https://github.com/activeadmin/activeadmin/issues/4173
+[#4187]: https://github.com/activeadmin/activeadmin/issues/4187
+[#4254]: https://github.com/activeadmin/activeadmin/issues/4254
+[#5043]: https://github.com/activeadmin/activeadmin/issues/5043
+[#5198]: https://github.com/activeadmin/activeadmin/issues/5198
+
+[21b6138f]: https://github.com/activeadmin/activeadmin/pull/5740/commits/21b6138fdcf58cd54c3f1d3f60cb1127b174b40f
+
+[#3091]: https://github.com/activeadmin/activeadmin/pull/3091
+[#3435]: https://github.com/activeadmin/activeadmin/pull/3435
+[#4477]: https://github.com/activeadmin/activeadmin/pull/4477
+[#4731]: https://github.com/activeadmin/activeadmin/pull/4731
+[#4759]: https://github.com/activeadmin/activeadmin/pull/4759
+[#4768]: https://github.com/activeadmin/activeadmin/pull/4768
+[#4848]: https://github.com/activeadmin/activeadmin/pull/4848
+[#4851]: https://github.com/activeadmin/activeadmin/pull/4851
+[#4867]: https://github.com/activeadmin/activeadmin/pull/4867
+[#4870]: https://github.com/activeadmin/activeadmin/pull/4870
+[#4882]: https://github.com/activeadmin/activeadmin/pull/4882
+[#4940]: https://github.com/activeadmin/activeadmin/pull/4940
+[#4950]: https://github.com/activeadmin/activeadmin/pull/4950
+[#4951]: https://github.com/activeadmin/activeadmin/pull/4951
+[#4989]: https://github.com/activeadmin/activeadmin/pull/4989
+[#4996]: https://github.com/activeadmin/activeadmin/pull/4996
+[#4997]: https://github.com/activeadmin/activeadmin/pull/4997
+[#5003]: https://github.com/activeadmin/activeadmin/pull/5003
+[#5029]: https://github.com/activeadmin/activeadmin/pull/5029
+[#5037]: https://github.com/activeadmin/activeadmin/pull/5037
+[#5044]: https://github.com/activeadmin/activeadmin/pull/5044
+[#5046]: https://github.com/activeadmin/activeadmin/pull/5046
+[#5052]: https://github.com/activeadmin/activeadmin/pull/5052
+[#5060]: https://github.com/activeadmin/activeadmin/pull/5060
+[#5069]: https://github.com/activeadmin/activeadmin/pull/5069
+[#5078]: https://github.com/activeadmin/activeadmin/pull/5078
+[#5081]: https://github.com/activeadmin/activeadmin/pull/5081
+[#5088]: https://github.com/activeadmin/activeadmin/pull/5088
+[#5093]: https://github.com/activeadmin/activeadmin/pull/5093
+[#5099]: https://github.com/activeadmin/activeadmin/pull/5099
+[#5104]: https://github.com/activeadmin/activeadmin/pull/5104
+[#5119]: https://github.com/activeadmin/activeadmin/pull/5119
+[#5120]: https://github.com/activeadmin/activeadmin/pull/5120
+[#5125]: https://github.com/activeadmin/activeadmin/pull/5125
+[#5137]: https://github.com/activeadmin/activeadmin/pull/5137
+[#5143]: https://github.com/activeadmin/activeadmin/pull/5143
+[#5157]: https://github.com/activeadmin/activeadmin/pull/5157
+[#5167]: https://github.com/activeadmin/activeadmin/pull/5167
+[#5180]: https://github.com/activeadmin/activeadmin/pull/5180
+[#5187]: https://github.com/activeadmin/activeadmin/pull/5187
+[#5194]: https://github.com/activeadmin/activeadmin/pull/5194
+[#5208]: https://github.com/activeadmin/activeadmin/pull/5208
+[#5210]: https://github.com/activeadmin/activeadmin/pull/5210
+[#5223]: https://github.com/activeadmin/activeadmin/pull/5223
+[#5238]: https://github.com/activeadmin/activeadmin/pull/5238
+[#5240]: https://github.com/activeadmin/activeadmin/pull/5240
+[#5251]: https://github.com/activeadmin/activeadmin/pull/5251
+[#5253]: https://github.com/activeadmin/activeadmin/pull/5253
+[#5266]: https://github.com/activeadmin/activeadmin/pull/5266
+[#5272]: https://github.com/activeadmin/activeadmin/pull/5272
+[#5275]: https://github.com/activeadmin/activeadmin/pull/5275
+[#5284]: https://github.com/activeadmin/activeadmin/pull/5284
+[#5295]: https://github.com/activeadmin/activeadmin/pull/5295
+[#5299]: https://github.com/activeadmin/activeadmin/pull/5299
+[#5320]: https://github.com/activeadmin/activeadmin/pull/5320
+[#5324]: https://github.com/activeadmin/activeadmin/pull/5324
+[#5330]: https://github.com/activeadmin/activeadmin/pull/5330
+[#5334]: https://github.com/activeadmin/activeadmin/pull/5334
+[#5336]: https://github.com/activeadmin/activeadmin/pull/5336
+[#5341]: https://github.com/activeadmin/activeadmin/pull/5341
+[#5343]: https://github.com/activeadmin/activeadmin/pull/5343
+[#5357]: https://github.com/activeadmin/activeadmin/pull/5357
+[#5359]: https://github.com/activeadmin/activeadmin/pull/5359
+[#5368]: https://github.com/activeadmin/activeadmin/pull/5368
+[#5370]: https://github.com/activeadmin/activeadmin/pull/5370
+[#5375]: https://github.com/activeadmin/activeadmin/pull/5375
+[#5376]: https://github.com/activeadmin/activeadmin/pull/5376
+[#5399]: https://github.com/activeadmin/activeadmin/pull/5399
+[#5401]: https://github.com/activeadmin/activeadmin/pull/5401
+[#5408]: https://github.com/activeadmin/activeadmin/pull/5408
+[#5413]: https://github.com/activeadmin/activeadmin/pull/5413
+[#5417]: https://github.com/activeadmin/activeadmin/pull/5417
+[#5418]: https://github.com/activeadmin/activeadmin/pull/5418
+[#5433]: https://github.com/activeadmin/activeadmin/pull/5433
+[#5446]: https://github.com/activeadmin/activeadmin/pull/5446
+[#5448]: https://github.com/activeadmin/activeadmin/pull/5448
+[#5453]: https://github.com/activeadmin/activeadmin/pull/5453
+[#5458]: https://github.com/activeadmin/activeadmin/pull/5458
+[#5461]: https://github.com/activeadmin/activeadmin/pull/5461
+[#5464]: https://github.com/activeadmin/activeadmin/pull/5464
+[#5486]: https://github.com/activeadmin/activeadmin/pull/5486
+[#5501]: https://github.com/activeadmin/activeadmin/pull/5501
+[#5504]: https://github.com/activeadmin/activeadmin/pull/5504
+[#5517]: https://github.com/activeadmin/activeadmin/pull/5517
+[#5537]: https://github.com/activeadmin/activeadmin/pull/5537
+[#5548]: https://github.com/activeadmin/activeadmin/pull/5548
+[#5555]: https://github.com/activeadmin/activeadmin/pull/5555
+[#5583]: https://github.com/activeadmin/activeadmin/pull/5583
+[#5590]: https://github.com/activeadmin/activeadmin/pull/5590
+[#5608]: https://github.com/activeadmin/activeadmin/pull/5608
+[#5611]: https://github.com/activeadmin/activeadmin/pull/5611
+[#5627]: https://github.com/activeadmin/activeadmin/pull/5627
+[#5631]: https://github.com/activeadmin/activeadmin/pull/5631
+[#5632]: https://github.com/activeadmin/activeadmin/pull/5632
+[#5650]: https://github.com/activeadmin/activeadmin/pull/5650
+[#5660]: https://github.com/activeadmin/activeadmin/pull/5660
+[#5662]: https://github.com/activeadmin/activeadmin/pull/5662
+[#5710]: https://github.com/activeadmin/activeadmin/pull/5710
+[#5726]: https://github.com/activeadmin/activeadmin/pull/5726
+[#5738]: https://github.com/activeadmin/activeadmin/pull/5738
+[#5740]: https://github.com/activeadmin/activeadmin/pull/5740
+[#5751]: https://github.com/activeadmin/activeadmin/pull/5751
+[#5758]: https://github.com/activeadmin/activeadmin/pull/5758
+[#5777]: https://github.com/activeadmin/activeadmin/pull/5777
+[#5794]: https://github.com/activeadmin/activeadmin/pull/5794
+[#5800]: https://github.com/activeadmin/activeadmin/pull/5800
+[#5801]: https://github.com/activeadmin/activeadmin/pull/5801
+[#5802]: https://github.com/activeadmin/activeadmin/pull/5802
+[#5811]: https://github.com/activeadmin/activeadmin/pull/5811
+[#5816]: https://github.com/activeadmin/activeadmin/pull/5816
+[#5822]: https://github.com/activeadmin/activeadmin/pull/5822
+[#5826]: https://github.com/activeadmin/activeadmin/pull/5826
+[#5831]: https://github.com/activeadmin/activeadmin/pull/5831
+[#5842]: https://github.com/activeadmin/activeadmin/pull/5842
+[#5854]: https://github.com/activeadmin/activeadmin/pull/5854
+[#5855]: https://github.com/activeadmin/activeadmin/pull/5855
+[#5867]: https://github.com/activeadmin/activeadmin/pull/5867
+[#5870]: https://github.com/activeadmin/activeadmin/pull/5870
+[#5874]: https://github.com/activeadmin/activeadmin/pull/5874
+[#5877]: https://github.com/activeadmin/activeadmin/pull/5877
+[#5886]: https://github.com/activeadmin/activeadmin/pull/5886
+[#5887]: https://github.com/activeadmin/activeadmin/pull/5887
+[#5894]: https://github.com/activeadmin/activeadmin/pull/5894
+[#5895]: https://github.com/activeadmin/activeadmin/pull/5895
+[#5929]: https://github.com/activeadmin/activeadmin/pull/5929
+[#5931]: https://github.com/activeadmin/activeadmin/pull/5931
+[#5938]: https://github.com/activeadmin/activeadmin/pull/5938
+[#5943]: https://github.com/activeadmin/activeadmin/pull/5943
+[#5946]: https://github.com/activeadmin/activeadmin/pull/5946
+[#5956]: https://github.com/activeadmin/activeadmin/pull/5956
+[#5957]: https://github.com/activeadmin/activeadmin/pull/5957
+[#5994]: https://github.com/activeadmin/activeadmin/pull/5994
+[#6000]: https://github.com/activeadmin/activeadmin/pull/6000
+[#6002]: https://github.com/activeadmin/activeadmin/pull/6002
+[#6047]: https://github.com/activeadmin/activeadmin/pull/6047
+[#6086]: https://github.com/activeadmin/activeadmin/pull/6086
+[#6099]: https://github.com/activeadmin/activeadmin/pull/6099
+[#6124]: https://github.com/activeadmin/activeadmin/pull/6124
+[#6149]: https://github.com/activeadmin/activeadmin/pull/6149
+[#6195]: https://github.com/activeadmin/activeadmin/pull/6195
+[#6198]: https://github.com/activeadmin/activeadmin/pull/6198
+[#6210]: https://github.com/activeadmin/activeadmin/pull/6210
+[#6221]: https://github.com/activeadmin/activeadmin/pull/6221
+[#6232]: https://github.com/activeadmin/activeadmin/pull/6232
+[#6249]: https://github.com/activeadmin/activeadmin/pull/6249
+[#6315]: https://github.com/activeadmin/activeadmin/pull/6315
+[#6318]: https://github.com/activeadmin/activeadmin/pull/6318
+[#6341]: https://github.com/activeadmin/activeadmin/pull/6341
+[#6342]: https://github.com/activeadmin/activeadmin/pull/6342
+[#6368]: https://github.com/activeadmin/activeadmin/pull/6368
+[#6393]: https://github.com/activeadmin/activeadmin/pull/6393
+[#6422]: https://github.com/activeadmin/activeadmin/pull/6422
+[#6451]: https://github.com/activeadmin/activeadmin/pull/6451
+[#6460]: https://github.com/activeadmin/activeadmin/pull/6460
+[#6462]: https://github.com/activeadmin/activeadmin/pull/6462
+[#6482]: https://github.com/activeadmin/activeadmin/pull/6482
+[#6487]: https://github.com/activeadmin/activeadmin/pull/6487
+[#6523]: https://github.com/activeadmin/activeadmin/pull/6523
+[#6535]: https://github.com/activeadmin/activeadmin/pull/6535
+[#6536]: https://github.com/activeadmin/activeadmin/pull/6536
+[#6548]: https://github.com/activeadmin/activeadmin/pull/6548
+[#6583]: https://github.com/activeadmin/activeadmin/pull/6583
+[#6584]: https://github.com/activeadmin/activeadmin/pull/6584
+[#6872]: https://github.com/activeadmin/activeadmin/pull/6872
+[#6873]: https://github.com/activeadmin/activeadmin/pull/6873
+[#6884]: https://github.com/activeadmin/activeadmin/pull/6884
+[#6906]: https://github.com/activeadmin/activeadmin/pull/6906
+[#6915]: https://github.com/activeadmin/activeadmin/pull/6915
+[#6916]: https://github.com/activeadmin/activeadmin/pull/6916
+[#6922]: https://github.com/activeadmin/activeadmin/pull/6922
+[#6936]: https://github.com/activeadmin/activeadmin/pull/6936
+[#6954]: https://github.com/activeadmin/activeadmin/pull/6954
+[#6959]: https://github.com/activeadmin/activeadmin/pull/6959
+[#7002]: https://github.com/activeadmin/activeadmin/pull/7002
+[#7013]: https://github.com/activeadmin/activeadmin/pull/7013
+[#7095]: https://github.com/activeadmin/activeadmin/pull/7095
+[#7127]: https://github.com/activeadmin/activeadmin/pull/7127
+[#7144]: https://github.com/activeadmin/activeadmin/pull/7144
+[#7170]: https://github.com/activeadmin/activeadmin/pull/7170
+[#7181]: https://github.com/activeadmin/activeadmin/pull/7181
+[#7205]: https://github.com/activeadmin/activeadmin/pull/7205
+[#7235]: https://github.com/activeadmin/activeadmin/pull/7235
+[#7236]: https://github.com/activeadmin/activeadmin/pull/7236
+[#7262]: https://github.com/activeadmin/activeadmin/pull/7262
+[#7293]: https://github.com/activeadmin/activeadmin/pull/7293
+[#7332]: https://github.com/activeadmin/activeadmin/pull/7332
+[#7336]: https://github.com/activeadmin/activeadmin/pull/7336
+[#7340]: https://github.com/activeadmin/activeadmin/pull/7340
+[#7341]: https://github.com/activeadmin/activeadmin/pull/7341
+[#7349]: https://github.com/activeadmin/activeadmin/pull/7349
+[#7350]: https://github.com/activeadmin/activeadmin/pull/7350
+[#7377]: https://github.com/activeadmin/activeadmin/pull/7377
+[#7384]: https://github.com/activeadmin/activeadmin/pull/7384
+[#7394]: https://github.com/activeadmin/activeadmin/pull/7394
+[#7453]: https://github.com/activeadmin/activeadmin/pull/7453
+[#7475]: https://github.com/activeadmin/activeadmin/pull/7475
+[#7476]: https://github.com/activeadmin/activeadmin/pull/7476
+[#7479]: https://github.com/activeadmin/activeadmin/pull/7479
+[#7487]: https://github.com/activeadmin/activeadmin/pull/7487
+[#7488]: https://github.com/activeadmin/activeadmin/pull/7488
+[#7541]: https://github.com/activeadmin/activeadmin/pull/7541
+[#7556]: https://github.com/activeadmin/activeadmin/pull/7556
+[#7599]: https://github.com/activeadmin/activeadmin/pull/7599
+[#7653]: https://github.com/activeadmin/activeadmin/pull/7653
+[#7772]: https://github.com/activeadmin/activeadmin/pull/7772
+[#7920]: https://github.com/activeadmin/activeadmin/pull/7920
+[#7944]: https://github.com/activeadmin/activeadmin/pull/7944
+[#7984]: https://github.com/activeadmin/activeadmin/pull/7984
+[#7985]: https://github.com/activeadmin/activeadmin/pull/7985
+[#7986]: https://github.com/activeadmin/activeadmin/pull/7986
+[#7993]: https://github.com/activeadmin/activeadmin/pull/7993
+[#8009]: https://github.com/activeadmin/activeadmin/pull/8009
+[#8010]: https://github.com/activeadmin/activeadmin/pull/8010
+[#8098]: https://github.com/activeadmin/activeadmin/pull/8098
+[#8102]: https://github.com/activeadmin/activeadmin/pull/8102
+[#8105]: https://github.com/activeadmin/activeadmin/pull/8105
+[#8106]: https://github.com/activeadmin/activeadmin/pull/8106
+
+
+[@1000ship]: https://github.com/1000ship
+[@5t111111]: https://github.com/5t111111
+[@aarek]: https://github.com/aarek
+[@adler99]: https://github.com/adler99
+[@agrobbin]: https://github.com/agrobbin
+[@ajw725]: https://github.com/ajw725
+[@alejandroperea]: https://github.com/alejandroperea
+[@alex-bogomolov]: https://github.com/alex-bogomolov
[@amiel]: https://github.com/amiel
-[@bobbytables]: https://github.com/bobbytables
-[@caifara]: https://github.com/caifara
-[@comboy]: https://github.com/comboy
-[@dapi]: https://github.com/dapi
-[@dbussin]: https://github.com/dbussin
-[@dhiemstra]: https://github.com/dhiemstra
-[@doug316]: https://github.com/doug316
-[@ebeigarts]: https://github.com/ebeigarts
-[@ejholmes]: https://github.com/ejholmes
-[@emzeq]: https://github.com/emzeq
-[@ericcumbee]: https://github.com/ericcumbee
-[@fabiokr]: https://github.com/fabiokr
-[@fabiormoura]: https://github.com/fabiormoura
-[@fbuenemann]: https://github.com/fbuenemann
-[@george]: https://github.com/george
-[@ggilder]: https://github.com/ggilder
-[@gregbell]: https://github.com/gregbell
-[@hakanensari]: https://github.com/hakanensari
-[@j]: https://github.com/j
-[@jamesalmond]: https://github.com/jamesalmond
-[@jancel]: https://github.com/jancel
-[@jbarket]: https://github.com/jbarket
+[@amit]: https://github.com/amit
+[@amiuhle]: https://github.com/amiuhle
+[@andreslemik]: https://github.com/andreslemik
+[@bartoszkopinski]: https://github.com/bartoszkopinski
+[@bliof]: https://github.com/bliof
+[@blocknotes]: https://github.com/blocknotes
+[@bolshakov]: https://github.com/bolshakov
+[@brunoarueira]: https://github.com/brunoarueira
+[@brunvez]: https://github.com/brunvez
+[@buren]: https://github.com/buren
+[@carlottostromstedt]: https://github.com/carlottostromstedt
+[@chancancode]: https://github.com/chancancode
+[@chrp]: https://github.com/chrp
+[@chumakoff]: https://github.com/chumakoff
+[@cprodhomme]: https://github.com/cprodhomme
+[@craigmcnamara]: https://github.com/craigmcnamara
+[@DanielHeath]: https://github.com/DanielHeath
+[@deivid-rodriguez]: https://github.com/deivid-rodriguez
+[@dennisvdvliet]: https://github.com/dennisvdvliet
+[@dhyegofernando]: https://github.com/dhyegofernando
+[@dkniffin]: https://github.com/dkniffin
+[@dmitry]: https://github.com/dmitry
+[@drn]: https://github.com/drn
+[@eikes]: https://github.com/eikes
+[@f1sherman]: https://github.com/f1sherman
+[@FabioRos]: https://github.com/FabioRos
+[@faucct]: https://github.com/faucct
+[@Fivell]: https://github.com/Fivell
+[@Fs00]: https://github.com/Fs00
+[@fuzziness]: https://github.com/fuzziness
+[@gabo-cs]: https://github.com/gabo-cs
+[@giapnhdev]: https://github.com/giapnhdev
+[@gigorok]: https://github.com/gigorok
+[@glebtv]: https://github.com/glebtv
+[@gonzedge]: https://github.com/gonzedge
+[@guigs]: https://github.com/guigs
+[@HappyKadaver]: https://github.com/HappyKadaver
+[@hfl]: https://github.com/hfl
+[@imcvampire]: https://github.com/imcvampire
+[@innparusu95]: https://github.com/innparusu95
+[@ionut998]: https://github.com/ionut998
+[@irmela]: https://github.com/irmela
+[@ispyropoulos]: https://github.com/ispyropoulos
+[@Ivanov-Anton]: https://github.com/Ivanov-Anton
+[@jasl]: https://github.com/jasl
+[@javawizard]: https://github.com/javawizard
+[@javierjulio]: https://github.com/javierjulio
+[@jawa]: https://github.com/jawa
+[@jaynetics]: https://github.com/jaynetics
+[@JewelSam]: https://github.com/JewelSam
+[@JiiHu]: https://github.com/JiiHu
+[@jiikko]: https://github.com/jiikko
[@johnnyshields]: https://github.com/johnnyshields
-[@joshuacollins85]: https://github.com/joshuacollins85
-[@jschwindt]: https://github.com/jschwindt
-[@kerberoS]: https://github.com/kerberoS
-[@knoopx]: https://github.com/knoopx
-[@krug]: https://github.com/krug
-[@laffinkippah]: https://github.com/laffinkippah
-[@latortuga]: https://github.com/latortuga
-[@macfanatic]: https://github.com/macfanatic
-[@mattvague]: https://github.com/mattvague
-[@mperham]: https://github.com/mperham
-[@mwindwer]: https://github.com/mwindwer
-[@orendon]: https://github.com/orendon
-[@page_title]: https://github.com/page_title
-[@pcreux]: https://github.com/pcreux
-[@per_page]: https://github.com/per_page
-[@ptn]: https://github.com/ptn
-[@randym]: https://github.com/randym
-[@rdsoze]: https://github.com/rdsoze
-[@rheaton]: https://github.com/rheaton
-[@rmw]: https://github.com/rmw
-[@robdiciuccio]: https://github.com/robdiciuccio
-[@rolfb]: https://github.com/rolfb
-[@ronen]: https://github.com/ronen
-[@ryansch]: https://github.com/ryansch
-[@samvincent]: https://github.com/samvincent
-[@sftsang]: https://github.com/sftsang
-[@shayfrendt]: https://github.com/shayfrendt
-[@shell]: https://github.com/shell
-[@simonoff]: https://github.com/simonoff
-[@snapapps]: https://github.com/snapapps
+[@jscheid]: https://github.com/jscheid
+[@juril33t]: https://github.com/juril33t
+[@jwesorick]: https://github.com/jwesorick
+[@Karoid]: https://github.com/Karoid
+[@kjeldahl]: https://github.com/kjeldahl
+[@ko-lem]: https://github.com/ko-lem
+[@kobeumut]: https://github.com/kobeumut
+[@Kris-LIBIS]: https://github.com/Kris-LIBIS
+[@krzcho]: https://github.com/krzcho
+[@kwent]: https://github.com/kwent
+[@lanzhiheng]: https://github.com/lanzhiheng
+[@leio10]: https://github.com/leio10
+[@littleforest]: https://github.com/littleforest
+[@Looooong]: https://github.com/Looooong
+[@lubosch]: https://github.com/lubosch
+[@markstory]: https://github.com/markstory
+[@mauriciopasquier]: https://github.com/mauriciopasquier
+[@mconiglio]: https://github.com/mconiglio
+[@mgrunberg]: https://github.com/mgrunberg
+[@micred]: https://github.com/micred
+[@mirelon]: https://github.com/mirelon
+[@MmKolodziej]: https://github.com/MmKolodziej
+[@mshalaby]: https://github.com/mshalaby
+[@munen]: https://github.com/munen
+[@mvz]: https://github.com/mvz
+[@ndbroadbent]: https://github.com/ndbroadbent
+[@ngouy]: https://github.com/ngouy
+[@Nguyenanh]: https://github.com/Nguyenanh
+[@orkhan]: https://github.com/orkhan
+[@panasyuk]: https://github.com/panasyuk
+[@PChambino]: https://github.com/PChambino
+[@potatosalad]: https://github.com/potatosalad
+[@pranas]: https://github.com/pranas
+[@ray-curran]: https://github.com/ray-curran
+[@renotocn]: https://github.com/renotocn
+[@rn0rno]: https://github.com/rn0rno
+[@RobinvanderVliet]: https://github.com/RobinvanderVliet
+[@rogerkk]: https://github.com/rogerkk
+[@roramirez]: https://github.com/roramirez
+[@rs-phunt]: https://github.com/rs-phunt
+[@sanfrecce-osaka]: https://github.com/sanfrecce-osaka
+[@seanlinsley]: https://github.com/seanlinsley
+[@sergey-alekseev]: https://github.com/sergey-alekseev
+[@sgara]: https://github.com/sgara
+[@ShallmentMo]: https://github.com/ShallmentMo
+[@shekibobo]: https://github.com/shekibobo
+[@shouya]: https://github.com/shouya
+[@sjieg]: https://github.com/sjieg
+[@sprql]: https://github.com/sprql
+[@stefsava]: https://github.com/stefsava
[@stereoscott]: https://github.com/stereoscott
-[@sunny]: https://github.com/sunny
-[@tracedwax]: https://github.com/tracedwax
-[@tricknotes]: https://github.com/tricknotes
-[@utkarshkukreti]: https://github.com/utkarshkukreti
-[@vairix]: https://github.com/vairix
-[@vairix-ssierra]: https://github.com/vairix-ssierra
-[@watson]: https://github.com/watson
-[@whatcould]: https://github.com/whatcould
-[@yawn]: https://github.com/yawn
-[@yorch]: https://github.com/yorch
+[@tagliala]: https://github.com/tagliala
+[@taralbass]: https://github.com/taralbass
+[@tf]: https://github.com/tf
+[@tiagotex]: https://github.com/tiagotex
+[@timoschilling]: https://github.com/timoschilling
+[@TimPetricola]: https://github.com/TimPetricola
+[@timwis]: https://github.com/timwis
+[@tomgilligan]: https://github.com/tomgilligan
+[@TonyArra]: https://github.com/TonyArra
+[@tordans]: https://github.com/tordans
+[@tvziet]: https://github.com/tvziet
+[@utkarsh2102]: https://github.com/utkarsh2102
+[@varyonic]: https://github.com/varyonic
+[@vcsjones]: https://github.com/vcsjones
+[@vfonic]: https://github.com/vfonic
+[@violeta-p]: https://github.com/violeta-p
+[@vlad-psh]: https://github.com/vlad-psh
+[@WaKeMaTTa]: https://github.com/WaKeMaTTa
+[@wasifhossain]: https://github.com/wasifhossain
+[@westonganger]: https://github.com/westonganger
+[@Wowu]: https://github.com/Wowu
+[@wspurgin]: https://github.com/wspurgin
+[@zorab47]: https://github.com/zorab47
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000000..a016f4e9c9f
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,74 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting one or more of the project maintainers. All complaints
+will be reviewed and investigated and will result in a response that is deemed
+necessary and appropriate to the circumstances. The project team is obligated to
+maintain confidentiality with regard to the reporter of an incident. Further
+details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available [here][source].
+
+[homepage]: https://www.contributor-covenant.org
+[source]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e14063b3754..a485eb7a510 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,121 +1,123 @@
-## Contributing
+# Contributing
-This is a guide to contributing to Active Admin. It should walk you through the
-major steps to contributing code to the project.
+Thanks for your interest in contributing to ActiveAdmin! Please take a moment to review this document **before submitting a pull request**.
-### 0. 'The test suite is failing locally!'
+## Pull requests
-If your test suite is failing locally and the
-[Travis build](https://secure.travis-ci.org/gregbell/active_admin.png?branch=master)
- is passing, you've likely already generated the rails app that is used by
-ActiveAdmin's test suite and the generated rails app has erroneous files.
+**Please ask first before starting work on any significant new features.**
-To fix this, remove the generated rails app by deleting the directory and its
-contents (`rm -rf spec/rails`) and then re-running the test suite.
+It's never a fun experience to have your pull request declined after investing a lot of time and effort into a new feature. To avoid this from happening, we request that contributors create [a feature request](https://github.com/activeadmin/activeadmin/discussions/new?category=ideas) to first discuss any new ideas. Your ideas and suggestions are welcome!
-### 1. Create an Issue on GitHub
+Please ensure that the tests are passing when submitting a pull request. If you're adding new features to ActiveAdmin, please include tests.
-The first step to contributing to Active Admin is creating a ticket in our
-[ticketing system on GitHub](https://github.com/gregbell/active_admin/issues).
-The community has been hard at work already, so please take a second to search
-for the issue or feature before creating a new one.
+## Where do I go from here?
-All bug fixes should have a ticket. This makes it easy for everyone
-to discuss the code and know if a fix is already in progress for an issue.
+For any questions, support, or ideas, etc. [please create a GitHub discussion](https://github.com/activeadmin/activeadmin/discussions/new). If you've noticed a bug, [please submit an issue][new issue].
+### Fork and create a branch
-### 2. Fork & Create a Feature Branch
+If this is something you think you can fix, then [fork Active Admin] and create
+a branch with a descriptive name.
-The next step is to fork Active Admin (if you haven't already done so) and
-create a new git branch based on the feature or issue you're working on. Please
-use a descriptive name for your branch.
+### Get the test suite running
-For example a great branch name would be (where issue #325 is the ticket you're
-working on):
+Make sure you're using a recent Ruby and Node version. You'll also need Chrome installed in order to run Cucumber scenarios.
- $> git checkout -b 325-add-japanese-translations
+Now install the development dependencies:
+```sh
+gem install foreman
+bundle install --all
+npm install
+```
-### 3. Get the test suite running
-
-Active Admin is a gem that many people and businesses rely on for managing data
-in their production applications. Bugs are not cool. Although we're not perfect,
-we pride ourselves on writing well tested code. I hope you do too :)
-
-Active Admin uses rspec and cucumber for it's test suite.
-
-Make sure you have a recent version of bundler:
-
- $> gem install bundler
+Now you should be able to run the entire suite using:
-Then install the development the development dependencies:
+```sh
+bin/rake
+```
- $> bundle install
+The task will generate a sample Rails application in `tmp/test_apps` to run the
+test suite against.
-Now you should be able to run the entire suite using:
+If you want to test against a Rails version different from the latest, make sure
+you use the correct Gemfile, for example:
- $> rake test
+```sh
+export BUNDLE_GEMFILE=gemfiles/rails_61/Gemfile
+```
-`rake test` runs the unit specs, integration specs and cucumber scenarios. The
-test suite will generate a rails application in `spec/rails` to run the tests
-against.
+### Implement your fix or feature
+At this point, you're ready to make your changes. Feel free to ask for help.
-### 4. Implement your fix or feature
+### View your changes in a Rails application
-At this point, you should be ready to implement your feature!
+Make sure to take a look at your changes in a browser. To boot up a test Rails app:
+```sh
+bin/rake local server
+```
-### 5. View your changes in a Rails application
+This will automatically create a Rails app if none already exists, and store it
+in the `tmp/development_apps` folder.
-Active Admin is meant to be used by humans, not cucumbers. So make sure to take
-a look at your changes in a browser (preferably a few browsers if you made view
-changes).
+You should now be able to open in your browser and log in using `admin@example.com` and `password`.
-To boot up a test rails application, use the provided script:
+If you need to perform any other commands on the test application, just pass
+them to the `local` rake task. For example, to boot the rails console:
- $> ./script/local server
+```sh
+bin/rake local console
+```
-This will generate a rails application at ./test-rails-app with some sane
-defaults and use your local version of Active Admin.
+Or to migrate the database for a new migration:
-If you have any Bundler issues, call the provided `use_rails` script then prepend
-the version of rails you would like to use in an environment variable:
+```sh
+bin/rake local db:migrate
+```
- $> ./script/use_rails 3.1.0
- $> RAILS=3.1.0 ./script/local server
+### Documentation
-You should be able to open `http://localhost:3000/admin` and view a test
-environment.
+Run `npm run docs:dev` and open `/docs/` to preview v4 beta documentation in VitePress.
+The version selector opens the matching page under `/docs/v3/`.
-If you need to perform any other commands on the test application, use the
-`local` script. For example to boot the rails console:
+Edit the current documentation in `docs/` and the archived v3 documentation
+in `docs/v3/`. The site includes `UPGRADING.md` directly as its v4 upgrade guide.
- $> ./script/local console
+Run `npm run docs:test` to build the site and check version links and legacy
+redirects. The deployment artifact is `docs/.vitepress/dist`: it contains the
+site in `docs/` and generated HTML redirects for the former root URLs. These
+redirects target the matching pages under `/docs/v3/` and are generated from
+the v3 archive, including all five files in `docs/v3/3-index-pages/`. These
+redirects do not depend on the layout of the current v4 pages.
+Publish this entire directory so links such as `/3-index-pages.html` and
+`/3-index-pages/index-as-grid.html` keep working.
-Or to migrate the database:
+### Create a Pull Request
- $> ./script/local rake db:migrate
+At this point, if your changes look good and tests are passing, you are ready to create a pull request.
+Github Actions will run our test suite against all supported Rails versions. It's possible that your changes pass tests in one Rails version but fail in another. In that case, you'll have to setup your development
+environment with the Gemfile for the problematic Rails version, and investigate what's going on.
-### 6. Run tests against major supported rails versions
+## Commit messages
-Once you've implemented your code, got the tests passing, previewed it in a
-browser, you're ready to test it against multiple versions of Rails.
+Try your best to follow these [seven rules for a great commit message](https://cbea.ms/git-commit/#seven-rules).
- $> rake test:major_supported_rails
+## Merging a PR (maintainers only)
-This command runs the cukes and specs against a couple of major versions of
-Rails. We will run this command when we review your pull request, if this
-rake task isn't passing, the pull request will not be merged in.
+A PR can only be merged into master by a maintainer if: CI is passing, approved by another maintainer and is up to date with the default branch. Any maintainer is allowed to merge a PR if all of these conditions ae met.
+## Shipping a release (maintainers only)
-### 7. Make a pull request
+Maintainers need to do the following to push out a release:
-At this point, you should switch back to your master branch and make sure it's
-up to date with Active Admin's master branch. If there were any changes, you
-should rebase your feature branch and make sure that it will merge correctly. If
-there are any merge conflicts, your pull request will not be merged in.
+* Create a feature branch from master and make sure it's up to date.
+* Run `bin/prep-release [version]` and commit the changes. Use Ruby version format. NPM is handled automatically.
+* Optional: To confirm the release contents, run `gem build` (extract contents) and `npm publish --dry-run`.
+* Review and merge the PR.
+* Run `bin/rake release` from the default branch once the PR is merged.
+* [Create a GitHub Release](https://github.com/activeadmin/activeadmin/releases/new) by selecting the tag and generating the release notes.
-Now push your changes up to your feature branch on GitHub and make a pull request!
-We will pull your changes, run the test suite, review the code and merge it in.
+[new issue]: https://github.com/activeadmin/activeadmin/issues/new
diff --git a/Gemfile b/Gemfile
index e9563bcc817..a9fc7947740 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,53 +1,52 @@
-source 'http://rubygems.org'
+# frozen_string_literal: true
+source "https://rubygems.org", cooldown: 5
-gemspec
+group :development, :test do
+ gem "rake"
-ACTIVE_ADMIN_PATH = File.dirname(__FILE__) unless defined?(ACTIVE_ADMIN_PATH)
+ gem "cancancan"
+ gem "pundit"
-require File.expand_path('spec/support/detect_rails_version', ACTIVE_ADMIN_PATH)
+ gem "draper"
+ gem "devise"
-rails_version = detect_rails_version
-gem 'rails', rails_version
-gem 'bourbon'
+ gem "rails", "~> 8.1.0"
-case rails_version
-when /^3\.0/
- # Do nothing, bundler should figure it out
-when /^3\.(1|2)/
- # These are the gems you have to have for Rails 3.1 to be happy
- gem 'sass-rails'
- gem 'uglifier'
-else
- raise "Rails #{rails_version} is not supported yet"
-end
+ gem "sprockets-rails"
+ gem "ransack", ">= 4.2.0"
+ gem "formtastic", ">= 6.0.0"
-group :development do
- gem 'better_errors', '~> 0.9.0' # Web UI to debug exceptions. Go to /__better_errors to access the latest one
- gem 'binding_of_caller', '~> 0.7.1' # Retrieve the binding of a method's caller in MRI Ruby >= 1.9.2
+ gem "cssbundling-rails"
+ gem "importmap-rails"
end
-group :development, :test do
- gem 'haml', '~> 3.1.7', :require => false
- gem 'rake', '~> 10.0.2', :require => false
- gem 'rails-i18n' # Provides default i18n for many languages
- gem 'rdiscount' # Markdown implementation (for yard)
- gem 'sprockets'
- gem 'yard'
+group :test do
+ gem "cuprite"
+ gem "capybara"
+ gem "webrick"
+
+ gem "simplecov", require: false # Test coverage generator. Go to /coverage/ after running tests
+ gem "simplecov-cobertura", require: false
+ gem "cucumber-rails", require: false
+ gem "database_cleaner-active_record"
+ gem "launchy"
+ gem "parallel_tests"
+ gem "rspec-rails"
+ gem "sqlite3", platform: :mri
+
+ # Translations
+ gem "i18n-tasks"
+ gem "i18n-spec"
+ gem "rails-i18n" # Provides default i18n for many languages
end
-group :test do
- gem 'cancan'
- gem 'capybara', '1.1.2'
- gem 'coveralls', '~> 0.6.7', require: false # Test coverage tool: www.coveralls.io
- gem 'cucumber-rails', '1.3.0', :require => false
- gem 'database_cleaner'
- gem 'guard-coffeescript'
- gem 'guard-rspec'
- gem 'jasmine'
- gem 'jslint_on_rails', '~> 1.1.1'
- gem 'launchy'
- gem 'parallel_tests'
- gem 'rspec-rails', '~> 2.9.0'
- gem 'shoulda-matchers'
- gem 'sqlite3'
+group :rubocop do
+ gem "rubocop"
+ gem "rubocop-capybara"
+ gem "rubocop-packaging"
+ gem "rubocop-performance"
+ gem "rubocop-rspec"
+ gem "rubocop-rails"
end
+
+gemspec path: "."
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 00000000000..54a9524a074
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,500 @@
+PATH
+ remote: .
+ specs:
+ activeadmin (4.0.0.beta23)
+ arbre (~> 2.0)
+ csv
+ formtastic (>= 6.0)
+ formtastic_i18n (>= 0.7)
+ inherited_resources (~> 2.0)
+ kaminari (>= 1.2.1)
+ railties (>= 7.2)
+ ransack (>= 4.0)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ action_text-trix (2.1.19)
+ railties
+ actioncable (8.1.4)
+ actionpack (= 8.1.4)
+ activesupport (= 8.1.4)
+ nio4r (~> 2.0)
+ websocket-driver (>= 0.6.1)
+ zeitwerk (~> 2.6)
+ actionmailbox (8.1.4)
+ actionpack (= 8.1.4)
+ activejob (= 8.1.4)
+ activerecord (= 8.1.4)
+ activestorage (= 8.1.4)
+ activesupport (= 8.1.4)
+ mail (>= 2.8.0)
+ actionmailer (8.1.4)
+ actionpack (= 8.1.4)
+ actionview (= 8.1.4)
+ activejob (= 8.1.4)
+ activesupport (= 8.1.4)
+ mail (>= 2.8.0)
+ rails-dom-testing (~> 2.2)
+ actionpack (8.1.4)
+ actionview (= 8.1.4)
+ activesupport (= 8.1.4)
+ nokogiri (>= 1.8.5)
+ rack (>= 2.2.4)
+ rack-session (>= 1.0.1)
+ rack-test (>= 0.6.3)
+ rails-dom-testing (~> 2.2)
+ rails-html-sanitizer (~> 1.6)
+ useragent (~> 0.16)
+ actiontext (8.1.4)
+ action_text-trix (~> 2.1.15)
+ actionpack (= 8.1.4)
+ activerecord (= 8.1.4)
+ activestorage (= 8.1.4)
+ activesupport (= 8.1.4)
+ globalid (>= 0.6.0)
+ nokogiri (>= 1.8.5)
+ actionview (8.1.4)
+ activesupport (= 8.1.4)
+ builder (~> 3.1)
+ erubi (~> 1.11)
+ rails-dom-testing (~> 2.2)
+ rails-html-sanitizer (~> 1.6)
+ activejob (8.1.4)
+ activesupport (= 8.1.4)
+ globalid (>= 0.3.6)
+ activemodel (8.1.4)
+ activesupport (= 8.1.4)
+ activemodel-serializers-xml (1.0.3)
+ activemodel (>= 5.0.0.a)
+ activesupport (>= 5.0.0.a)
+ builder (~> 3.1)
+ activerecord (8.1.4)
+ activemodel (= 8.1.4)
+ activesupport (= 8.1.4)
+ timeout (>= 0.4.0)
+ activestorage (8.1.4)
+ actionpack (= 8.1.4)
+ activejob (= 8.1.4)
+ activerecord (= 8.1.4)
+ activesupport (= 8.1.4)
+ marcel (~> 1.0)
+ activesupport (8.1.4)
+ base64
+ bigdecimal
+ concurrent-ruby (~> 1.0, >= 1.3.1)
+ connection_pool (>= 2.2.5)
+ drb
+ i18n (>= 1.6, < 2)
+ json
+ logger (>= 1.4.2)
+ minitest (>= 5.1)
+ securerandom (>= 0.3)
+ tzinfo (~> 2.0, >= 2.0.5)
+ uri (>= 0.13.1)
+ addressable (2.9.0)
+ public_suffix (>= 2.0.2, < 8.0)
+ arbre (2.2.1)
+ activesupport (>= 7.0)
+ ast (2.4.3)
+ base64 (0.3.0)
+ bcrypt (3.1.22)
+ bigdecimal (4.1.3)
+ builder (3.3.0)
+ cancancan (3.6.1)
+ capybara (3.40.0)
+ addressable
+ matrix
+ mini_mime (>= 0.1.3)
+ nokogiri (~> 1.11)
+ rack (>= 1.6.0)
+ rack-test (>= 0.6.3)
+ regexp_parser (>= 1.5, < 3.0)
+ xpath (~> 3.2)
+ childprocess (5.1.0)
+ logger (~> 1.5)
+ concurrent-ruby (1.3.8)
+ connection_pool (3.0.2)
+ crass (1.0.7)
+ cssbundling-rails (1.4.3)
+ railties (>= 6.0.0)
+ csv (3.3.6)
+ cucumber (11.1.1)
+ base64 (~> 0.2)
+ builder (~> 3.2)
+ cucumber-ci-environment (> 9, < 12)
+ cucumber-core (>= 16.2.0, < 17)
+ cucumber-cucumber-expressions (> 17, < 20)
+ cucumber-html-formatter (> 21, < 24)
+ diff-lcs (~> 1.5)
+ logger (~> 1.6)
+ mini_mime (~> 1.1)
+ multi_test (~> 1.1)
+ sys-uname (~> 1.5)
+ cucumber-ci-environment (11.0.0)
+ cucumber-core (16.2.0)
+ cucumber-gherkin (> 36, < 40)
+ cucumber-messages (> 31, < 33)
+ cucumber-tag-expressions (> 6, < 9)
+ cucumber-cucumber-expressions (19.0.1)
+ bigdecimal
+ cucumber-gherkin (39.1.0)
+ cucumber-messages (>= 31, < 33)
+ cucumber-html-formatter (23.1.0)
+ cucumber-messages (> 23, < 33)
+ cucumber-messages (32.3.1)
+ cucumber-rails (4.1.0)
+ capybara (>= 3.25, < 4)
+ cucumber (>= 7, < 12)
+ railties (>= 6.1, < 9)
+ cucumber-tag-expressions (8.1.0)
+ cuprite (0.18)
+ capybara (~> 3.0)
+ ferrum (~> 0.18.0)
+ database_cleaner-active_record (2.2.2)
+ activerecord (>= 5.a)
+ database_cleaner-core (~> 2.0)
+ database_cleaner-core (2.1.0)
+ date (3.5.1)
+ devise (5.0.4)
+ bcrypt (~> 3.0)
+ orm_adapter (~> 0.1)
+ railties (>= 7.0)
+ responders
+ warden (~> 1.2.3)
+ diff-lcs (1.6.2)
+ draper (4.0.6)
+ actionpack (>= 5.0)
+ activemodel (>= 5.0)
+ activemodel-serializers-xml (>= 1.0)
+ activesupport (>= 5.0)
+ request_store (>= 1.0)
+ ruby2_keywords
+ drb (2.2.3)
+ erb (6.0.7)
+ erubi (1.13.1)
+ ferrum (0.18.0)
+ addressable (~> 2.5)
+ base64 (~> 0.2)
+ concurrent-ruby (~> 1.1)
+ websocket-driver (~> 0.7)
+ ffi (1.17.4-aarch64-linux-gnu)
+ ffi (1.17.4-arm64-darwin)
+ ffi (1.17.4-x86_64-darwin)
+ ffi (1.17.4-x86_64-linux-gnu)
+ formtastic (6.0.0)
+ actionpack (>= 7.2.0)
+ formtastic_i18n (0.7.0)
+ globalid (1.4.0)
+ activesupport (>= 6.1)
+ has_scope (0.9.0)
+ actionpack (>= 7.0)
+ activesupport (>= 7.0)
+ highline (3.1.2)
+ reline
+ i18n (1.15.2)
+ concurrent-ruby (~> 1.0)
+ i18n-spec (0.6.0)
+ iso
+ i18n-tasks (1.1.2)
+ activesupport (>= 4.0.2)
+ ast (>= 2.1.0)
+ erubi
+ highline (>= 3.0.0)
+ i18n
+ parser (>= 3.2.2.1)
+ prism
+ rails-i18n
+ rainbow (>= 2.2.2, < 4.0)
+ ruby-progressbar (~> 1.8, >= 1.8.1)
+ terminal-table (>= 1.5.1)
+ importmap-rails (2.2.3)
+ actionpack (>= 6.0.0)
+ activesupport (>= 6.0.0)
+ railties (>= 6.0.0)
+ inherited_resources (2.1.0)
+ actionpack (>= 7.0)
+ has_scope (>= 0.6)
+ railties (>= 7.0)
+ responders (>= 2)
+ io-console (0.9.4)
+ irb (1.18.0)
+ pp (>= 0.6.0)
+ prism (>= 1.3.0)
+ rdoc (>= 4.0.0)
+ reline (>= 0.4.2)
+ iso (0.4.0)
+ i18n
+ json (3.0.2)
+ kaminari (1.2.2)
+ activesupport (>= 4.1.0)
+ kaminari-actionview (= 1.2.2)
+ kaminari-activerecord (= 1.2.2)
+ kaminari-core (= 1.2.2)
+ kaminari-actionview (1.2.2)
+ actionview
+ kaminari-core (= 1.2.2)
+ kaminari-activerecord (1.2.2)
+ activerecord
+ kaminari-core (= 1.2.2)
+ kaminari-core (1.2.2)
+ language_server-protocol (3.17.0.6)
+ launchy (3.1.1)
+ addressable (~> 2.8)
+ childprocess (~> 5.0)
+ logger (~> 1.6)
+ lint_roller (1.1.0)
+ logger (1.7.0)
+ loofah (2.25.2)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.12.0)
+ mail (2.9.1)
+ logger
+ mini_mime (>= 0.1.1)
+ net-imap
+ net-pop
+ net-smtp
+ marcel (1.2.1)
+ matrix (0.4.3)
+ memoist3 (1.0.0)
+ mini_mime (1.1.5)
+ minitest (6.0.6)
+ drb (~> 2.0)
+ prism (~> 1.5)
+ multi_test (1.1.0)
+ net-imap (0.6.7)
+ date
+ net-protocol
+ net-pop (0.1.2)
+ net-protocol
+ net-protocol (0.4.0)
+ timeout
+ net-smtp (0.5.2)
+ net-protocol
+ nio4r (2.7.5)
+ nokogiri (1.19.4-aarch64-linux-gnu)
+ racc (~> 1.4)
+ nokogiri (1.19.4-arm64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.19.4-x86_64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.19.4-x86_64-linux-gnu)
+ racc (~> 1.4)
+ orm_adapter (0.5.0)
+ parallel (2.3.0)
+ parallel_tests (5.8.0)
+ parallel
+ parser (3.3.12.0)
+ ast (~> 2.4.1)
+ racc
+ pp (0.6.4)
+ prettyprint
+ prettyprint (0.2.0)
+ prism (1.9.0)
+ public_suffix (7.0.5)
+ pundit (2.5.2)
+ activesupport (>= 3.0.0)
+ racc (1.8.1)
+ rack (3.2.7)
+ rack-session (2.1.2)
+ base64 (>= 0.1.0)
+ rack (>= 3.0.0)
+ rack-test (2.2.0)
+ rack (>= 1.3)
+ rackup (2.3.1)
+ rack (>= 3)
+ rails (8.1.4)
+ actioncable (= 8.1.4)
+ actionmailbox (= 8.1.4)
+ actionmailer (= 8.1.4)
+ actionpack (= 8.1.4)
+ actiontext (= 8.1.4)
+ actionview (= 8.1.4)
+ activejob (= 8.1.4)
+ activemodel (= 8.1.4)
+ activerecord (= 8.1.4)
+ activestorage (= 8.1.4)
+ activesupport (= 8.1.4)
+ bundler (>= 1.15.0)
+ railties (= 8.1.4)
+ rails-dom-testing (2.3.0)
+ activesupport (>= 5.0.0)
+ minitest
+ nokogiri (>= 1.6)
+ rails-html-sanitizer (1.7.1)
+ loofah (~> 2.25, >= 2.25.2)
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
+ rails-i18n (8.1.0)
+ i18n (>= 0.7, < 2)
+ railties (>= 8.0.0, < 9)
+ railties (8.1.4)
+ actionpack (= 8.1.4)
+ activesupport (= 8.1.4)
+ irb (~> 1.13)
+ rackup (>= 1.0.0)
+ rake (>= 12.2)
+ thor (~> 1.0, >= 1.2.2)
+ tsort (>= 0.2)
+ zeitwerk (~> 2.6)
+ rainbow (3.1.1)
+ rake (13.4.2)
+ ransack (5.0.2)
+ activerecord (>= 7.2)
+ activesupport (>= 7.2)
+ i18n
+ rbs (4.2.0)
+ logger
+ prism (>= 1.6.0)
+ tsort
+ rdoc (8.1.0)
+ erb
+ prism (>= 1.6.0)
+ rbs (>= 4.0.0)
+ tsort
+ regexp_parser (2.13.1)
+ reline (0.7.0)
+ io-console (~> 0.5)
+ request_store (1.7.0)
+ rack (>= 1.4)
+ responders (3.2.1)
+ actionpack (>= 7.0)
+ railties (>= 7.0)
+ rexml (3.4.4)
+ rspec-core (3.13.6)
+ rspec-support (~> 3.13.0)
+ rspec-expectations (3.13.5)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.13.0)
+ rspec-mocks (3.13.8)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.13.0)
+ rspec-rails (8.0.4)
+ actionpack (>= 7.2)
+ activesupport (>= 7.2)
+ railties (>= 7.2)
+ rspec-core (>= 3.13.0, < 5.0.0)
+ rspec-expectations (>= 3.13.0, < 5.0.0)
+ rspec-mocks (>= 3.13.0, < 5.0.0)
+ rspec-support (>= 3.13.0, < 5.0.0)
+ rspec-support (3.13.7)
+ rubocop (1.91.0)
+ json (>= 2.3)
+ language_server-protocol (~> 3.17.0.2)
+ lint_roller (~> 1.1.0)
+ parallel (>= 1.10)
+ parser (>= 3.3.0.2)
+ rainbow (>= 2.2.2, < 4.0)
+ regexp_parser (>= 2.9.3, < 3.0)
+ rubocop-ast (>= 1.49.0, < 2.0)
+ ruby-progressbar (~> 1.7)
+ unicode-display_width (>= 2.4.0, < 4.0)
+ rubocop-ast (1.50.0)
+ parser (>= 3.3.7.2)
+ prism (~> 1.7)
+ rubocop-capybara (3.0.0)
+ lint_roller (~> 1.1)
+ rubocop (~> 1.81)
+ rubocop-packaging (0.6.0)
+ lint_roller (~> 1.1.0)
+ rubocop (>= 1.72.1, < 2.0)
+ rubocop-performance (1.27.0)
+ lint_roller (~> 1.1)
+ rubocop (>= 1.89.0, < 2.0)
+ rubocop-ast (>= 1.47.1, < 2.0)
+ rubocop-rails (2.38.0)
+ activesupport (>= 4.2.0)
+ lint_roller (~> 1.1)
+ rack (>= 1.1)
+ rubocop (>= 1.89.0, < 2.0)
+ rubocop-ast (>= 1.44.0, < 2.0)
+ rubocop-rspec (3.10.2)
+ lint_roller (~> 1.1)
+ regexp_parser (>= 2.0)
+ rubocop (~> 1.86, >= 1.86.2)
+ ruby-progressbar (1.13.0)
+ ruby2_keywords (0.0.5)
+ securerandom (0.4.1)
+ simplecov (1.3.2)
+ simplecov-cobertura (4.0.0)
+ rexml
+ simplecov (~> 1.0)
+ sprockets (4.4.1)
+ concurrent-ruby (~> 1.1)
+ logger
+ rack (>= 2.2.4, < 4)
+ sprockets-rails (3.5.2)
+ actionpack (>= 6.1)
+ activesupport (>= 6.1)
+ sprockets (>= 3.0.0)
+ sqlite3 (2.9.6-aarch64-linux-gnu)
+ sqlite3 (2.9.6-arm64-darwin)
+ sqlite3 (2.9.6-x86_64-darwin)
+ sqlite3 (2.9.6-x86_64-linux-gnu)
+ sys-uname (1.5.1)
+ ffi (~> 1.1)
+ memoist3 (~> 1.0.0)
+ terminal-table (4.0.0)
+ unicode-display_width (>= 1.1.1, < 4)
+ thor (1.5.0)
+ timeout (0.6.1)
+ tsort (0.2.0)
+ tzinfo (2.0.6)
+ concurrent-ruby (~> 1.0)
+ unicode-display_width (3.3.0)
+ unicode-emoji (~> 4.3)
+ unicode-emoji (4.3.0)
+ uri (1.1.1)
+ useragent (0.16.11)
+ warden (1.2.9)
+ rack (>= 2.0.9)
+ webrick (1.9.2)
+ websocket-driver (0.8.2)
+ base64
+ websocket-extensions (>= 0.1.0)
+ websocket-extensions (0.1.5)
+ xpath (3.2.0)
+ nokogiri (~> 1.8)
+ zeitwerk (2.8.3)
+
+PLATFORMS
+ aarch64-linux
+ arm64-darwin
+ x86_64-darwin
+ x86_64-linux
+
+DEPENDENCIES
+ activeadmin!
+ cancancan
+ capybara
+ cssbundling-rails
+ cucumber-rails
+ cuprite
+ database_cleaner-active_record
+ devise
+ draper
+ formtastic (>= 6.0.0)
+ i18n-spec
+ i18n-tasks
+ importmap-rails
+ launchy
+ parallel_tests
+ pundit
+ rails (~> 8.1.0)
+ rails-i18n
+ rake
+ ransack (>= 4.2.0)
+ rspec-rails
+ rubocop
+ rubocop-capybara
+ rubocop-packaging
+ rubocop-performance
+ rubocop-rails
+ rubocop-rspec
+ simplecov
+ simplecov-cobertura
+ sprockets-rails
+ sqlite3
+ webrick
+
+BUNDLED WITH
+ 4.0.22
diff --git a/Guardfile b/Guardfile
deleted file mode 100644
index 882515b251f..00000000000
--- a/Guardfile
+++ /dev/null
@@ -1,7 +0,0 @@
-# More info at https://github.com/guard/guard#readme
-
-guard 'rspec', :all_on_start => false, :version => 2 do
- watch(%r{^spec/.+_spec\.rb$})
- watch(%r{^lib/active_admin/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
- watch('spec/spec_helper.rb') { "spec/" }
-end
diff --git a/LICENSE b/LICENSE
index 414eaed980f..065d975dbb8 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2013 Greg Bell, VersaPay Corporation
+Copyright (c) Greg Bell, VersaPay Corporation
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -18,8 +18,3 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-Iconic Icons are designed by P.J. Onori and are shared under
-the Creative Commons Attribution-Share Alike 3.0 license:
-http://creativecommons.org/licenses/by-sa/3.0/us
-http://somerandomdude.com/projects/iconic/
diff --git a/README.md b/README.md
new file mode 100644
index 00000000000..90b76bb70b3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,104 @@
+# Active Admin
+
+[Active Admin](https://activeadmin.info) is a Ruby on Rails framework for
+creating elegant backends for website administration.
+
+[![Version][rubygems_badge]][rubygems]
+[![Github Actions][actions_badge]][actions]
+[![Coverage][coverage_badge]][coverage]
+[![Tidelift][tidelift_badge]][tidelift]
+
+## Goals
+
+* Enable developers to quickly create good-looking administration interfaces.
+* Build a DSL for developers and an interface for businesses.
+* Ensure that developers can easily customize every nook and cranny.
+
+## Getting started
+
+* Review [the documentation][docs].
+* For help, questions, etc. [discuss ActiveAdmin on GitHub](https://github.com/activeadmin/activeadmin/discussions) or use [StackOverflow][stackoverflow]
+* The [wiki] includes links to tutorials, articles and sample projects.
+
+## For enterprise
+
+Active Admin for enterprise is available via the Tidelift subscription. [Learn
+More][tidelift_enterprise].
+
+## Want to contribute?
+
+If you want to contribute through code or documentation, the [Contributing
+guide is the best place to start][contributing]. If you have questions, feel free
+to ask.
+
+## Want to support us?
+
+If you want to support us financially, you can [help fund the project
+through a Tidelift subscription][tidelift_support]. By buying a Tidelift subscription
+you make sure your whole dependency stack is properly maintained, while also
+getting a comprehensive view of outdated dependencies, new releases, security
+alerts, and licensing compatibility issues.
+
+You can also support us with a weekly tip via [Liberapay].
+
+Finally, we have an [Open Collective][opencollective page] where you can become a backer or
+sponsor for the project, and also submit expenses to it.
+
+## Dependencies
+
+We try not to reinvent the wheel, so Active Admin is built with other open source projects:
+
+* [Arbre]
+* [Devise]
+* [Flowbite](https://flowbite.com)
+* [Formtastic]
+* [Inherited Resources]
+* [Kaminari]
+* [Ransack]
+* [Tailwind CSS](https://tailwindcss.com)
+
+## Security contact information
+
+Please use the Tidelift security contact to [report a security vulnerability][Tidelift security contact].
+Tidelift will coordinate the fix and disclosure.
+
+## Acknowledgements
+
+Thanks to [Greg Bell][Greg] for creating and sharing this project with the open source community.
+
+Thanks to [all the people that ever contributed through code][contributors] or
+other means such as bug reports, issue triaging, feature suggestions, code
+snippet tips, Slack discussions and so on.
+
+Thanks to [Tidelift][tidelift] and all our Tidelift subscribers.
+
+Thanks to [Open Collective][opencollective contributors] and all our Open Collective contributors.
+
+[Arbre]: https://github.com/activeadmin/arbre
+[Devise]: https://github.com/heartcombo/devise
+[Formtastic]: https://github.com/formtastic/formtastic
+[Inherited Resources]: https://github.com/activeadmin/inherited_resources
+[Kaminari]: https://github.com/kaminari/kaminari
+[Ransack]: https://github.com/activerecord-hackery/ransack
+
+[rubygems_badge]: https://img.shields.io/gem/v/activeadmin.svg
+[rubygems]: https://rubygems.org/gems/activeadmin
+[actions_badge]: https://github.com/activeadmin/activeadmin/workflows/ci/badge.svg
+[actions]: https://github.com/activeadmin/activeadmin/actions
+[coverage_badge]: https://codecov.io/gh/activeadmin/activeadmin/branch/master/graph/badge.svg?token=NAjeBdkQXW
+[coverage]: https://codecov.io/gh/activeadmin/activeadmin
+[tidelift_badge]: https://tidelift.com/badges/github/activeadmin/activeadmin
+[tidelift]: https://tidelift.com/subscription/pkg/rubygems-activeadmin?utm_source=rubygems-activeadmin&utm_medium=readme
+[tidelift_enterprise]: https://tidelift.com/subscription/pkg/rubygems-activeadmin?utm_source=rubygems-activeadmin&utm_medium=referral&utm_campaign=enterprise
+[tidelift_support]: https://tidelift.com/subscription/pkg/rubygems-activeadmin?utm_source=rubygems-activeadmin&utm_medium=referral&utm_campaign=github&utm_content=support
+
+[docs]: https://activeadmin.info/
+[wiki]: https://github.com/activeadmin/activeadmin/wiki
+[stackoverflow]: https://stackoverflow.com/questions/tagged/activeadmin
+[contributing]: https://github.com/activeadmin/activeadmin/blob/master/CONTRIBUTING.md
+[Liberapay]: https://liberapay.com/Active-Admin/donate
+[Tidelift security contact]: https://tidelift.com/security
+[Greg]: https://github.com/gregbell
+[contributors]: https://github.com/activeadmin/activeadmin/graphs/contributors
+[opencollective page]: https://opencollective.com/activeadmin
+[opencollective contributors]: https://opencollective.com/activeadmin#contributors
diff --git a/README.rdoc b/README.rdoc
deleted file mode 100644
index 238bffbfadd..00000000000
--- a/README.rdoc
+++ /dev/null
@@ -1,118 +0,0 @@
-= Active Admin
-
-Active Admin is a framework for creating administration style interfaces. It
-abstracts common business application patterns to make it simple for developers
-to implement beautiful and elegant interfaces with very little effort.
-
-{}[http://travis-ci.org/gregbell/active_admin]
-{}[https://codeclimate.com/github/gregbell/active_admin]
-{}[https://gemnasium.com/gregbell/active_admin]
-{}[https://coveralls.io/r/gregbell/active_admin?branch=master]
-
-== Documentation & Support
-
-* Documentation & Guides: http://activeadmin.info/documentation.html
-* Wiki: https://github.com/gregbell/active_admin/wiki
-* RDoc: http://rubydoc.info/gems/activeadmin/frames
-* Live demo: http://demo.activeadmin.info/admin
-* Website: http://www.activeadmin.info
-* Need Support? Ask the Mailing list: http://groups.google.com/group/activeadmin
-
-== Goals
-
-1. Allow developers to quickly create gorgeous administration interfaces
- (Not Just CRUD)
-2. Build a DSL for developers and an interface for businesses.
-3. Ensure that developers can easily customize every nook and cranny of the interface.
-4. Build common interfaces as shareable gems so that the entire community benefits.
-
-== Bugs Reports & Contributing
-
-* Bug Reports: https://github.com/gregbell/active_admin/issues
-* Want to Contribute? Read the Guide: https://github.com/gregbell/active_admin/blob/master/CONTRIBUTING.md
-
-== Feature Requests
-
-Please don't put feature requests in Github Issues. They will be closed as soon as they
-are reviewed by one of the core team members. If you would like a feature in Active Admin,
-please submit a well tested pull request with the desired changes. If you're not a coder,
-then the mailing list may be a good place to try to convince someone to help you out
-with your cause.
-
-If you are going to submit a pull request, please read the contributing guide:
-https://github.com/gregbell/active_admin/blob/master/CONTRIBUTING.md
-
-
-== Getting Started
-
-Active Admin is released as a Ruby Gem. The gem is to be installed within a Ruby
-on Rails 3 application. To install, simply add the following to your Gemfile:
-
- gem 'activeadmin'
-
-After updating your bundle, run the installer
-
- $> rails generate active_admin:install
-
-The installer creates an initializer used for configuring defaults used by Active Admin as well
-as a new folder at app/admin to put all your admin configurations.
-
-Migrate your db and start the server:
-
- $> rake db:migrate
- $> rails server
-
-Visit http://localhost:3000/admin and log in using:
-
-* *User*: admin@example.com
-* *Password*: password
-
-Voila! You're on your brand new Active Admin dashboard.
-
-To register your first model, run:
-
- $> rails generate active_admin:resource [MyModelName]
-
-This creates a file at app/admin/my_model_names.rb for configuring the resource.
-Refresh your web browser to see the interface.
-
-== Upgrading
-
-When upgrading to a new version of ActiveAdmin you may need to run
- $> rails generate active_admin:assets
-
-If you get:
- uninitialized constant Admin::DashboardController
-when trying to view the dashboard (at +/admin+), ensure +app/admin/dashboards.rb+ looks like the current {default}[https://raw.github.com/gregbell/active_admin/master/lib/generators/active_admin/install/templates/dashboard.rb].
-
-
-== Next Steps
-
-The best place to get documentation is at http://activeadmin.info/documentation.html.
-
-To view a sample Active Admin application, checkout http://demo.activeadmin.info
-
-If you have any questions, please email the mailing list at http://groups.google.com/group/activeadmin
-
-
-== Tools Being Used
-
-We believe strongly in not writing code unless we have to, so Active Admin is built using many
-other open source projects:
-
-InheritedResources::
- Inherited Resources speeds up development by making your controllers inherit all restful
- actions so you just have to focus on what is important.
-Formtastic::
- A DSL for semantically building amazing forms.
-Devise::
- User authentication is done using Devise
-Kaminari::
- Pagination for rails apps
-Iconic Icons::
- Excellent SVG icon set designed by P.J. Onori: http://somerandomdude.com/projects/iconic
-
-
-== Copyright
-
-Copyright (c) 2011 Greg Bell, VersaPay Corporation. See LICENSE for details.
diff --git a/Rakefile b/Rakefile
index ce8f13a3693..e26229820b9 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,27 +1,21 @@
-require "bundler"
-require 'rake'
-Bundler.setup
-Bundler::GemHelper.install_tasks
+# frozen_string_literal: true
+require "bundler/gem_tasks"
+import "tasks/local.rake"
+import "tasks/test.rake"
+import "tasks/dependencies.rake"
-def cmd(command)
- puts command
- raise unless system command
-end
-
-require File.expand_path('../spec/support/detect_rails_version', __FILE__)
+gemfile = ENV["BUNDLE_GEMFILE"]
-# Import all our rake tasks
-FileList['tasks/**/*.rake'].each { |task| import task }
+if gemfile.nil? || File.expand_path(gemfile) == File.expand_path("Gemfile")
+ import "tasks/release.rake"
+end
-# Run the specs & cukes using parallel_tests
-task :default => :parallel_tests
+task default: :test
-begin
- require 'jasmine'
- load 'jasmine/tasks/jasmine.rake'
-rescue LoadError
- task :jasmine do
- abort "Jasmine is not available. In order to run jasmine, you must: (sudo) gem install jasmine"
- end
+task :console do
+ require "irb"
+ require "irb/completion"
+ ARGV.clear
+ IRB.start
end
diff --git a/UPGRADING.md b/UPGRADING.md
new file mode 100644
index 00000000000..d3e54a5cf25
--- /dev/null
+++ b/UPGRADING.md
@@ -0,0 +1,274 @@
+# Upgrading Guide
+
+## From v3 to v4 (beta)
+
+ActiveAdmin v4 uses Tailwind CSS v4. It has **mobile web, dark mode and RTL support** with a default theme that can be customized through partials and CSS. This release assumes `cssbundling-rails` and `importmap-rails` is installed and configured in the host app. Partials can be modified to include a different asset library, e.g. shakapacker.
+**IMPORTANT**: there is **no sortable functionality for has-many forms** in this release so if needed, **do not upgrade**. We are [open to community proposals](https://github.com/activeadmin/activeadmin/discussions/new?category=ideas). The add/remove functionality for has-many forms remains supported.
+
+These instructions assume the `cssbundling-rails` and `importmap-rails` gems are already installed and you have run their install commands in your app. If you haven't done so, please do before continuing.
+
+Update your `Gemfile` with `gem "activeadmin", "4.0.0.beta23"` and then run `gem install activeadmin --pre`.
+
+Now, run `rails generate active_admin:assets` to replace the old assets with the new files.
+
+Then add the npm package and update the `build:css` script.
+
+```
+npm install @activeadmin/activeadmin@4.0.0-beta23
+npm pkg set scripts.build:css="npx @tailwindcss/cli -i ./app/assets/stylesheets/active_admin.css -o ./app/assets/builds/active_admin.css --minify"
+```
+
+If you are already using Tailwind in your app, then update the `build:css` script to chain the above command to your existing one, e.g. `"npx @tailwindcss/cli ... && npx @tailwindcss/cli ..."`, so both stylesheets are generated.
+
+Many configs have been removed (meta tags, asset registration, utility nav, etc.) that can now be modified more naturally through partials.
+
+Open the `config/initializers/active_admin.rb` file and remove these deleted configs.
+
+```
+site_title_link
+site_title_image
+logout_link_method
+favicon
+meta_tags
+meta_tags_for_logged_out_pages
+register_stylesheet
+register_javascript
+head
+footer
+use_webpacker
+```
+
+Now, run `rails g active_admin:views` which will copy the partials to your app so you can customize if needed.
+
+Note that the templates can and will change across releases. There are additional partials that can be copied but they are considered private so you do so at your own risk. You will have to keep those up to date per release.
+
+**IMPORTANT**: if your project has copied any ActiveAdmin, Devise, or Kaminari templates from earlier releases, those templates must be updated from this release to avoid potential errors. Path helpers in Devise templates may require using the `main_app` proxy. The Kaminari templates have moved to `app/views/active_admin/kaminari`.
+
+With the setup complete, please review the Breaking Changes section and resolve any that may or may not impact your integration.
+
+### Upgrading from an earlier 4.x beta release
+
+When upgrading from any earlier 4.0.0 beta release, please apply the changes outlined below.
+
+There were important template changes in 4.0.0.beta16. See [PR #8727](https://github.com/activeadmin/activeadmin/pull/8727) for details.
+- The `_site_header.html.erb` partial has changed its main container class from `sticky` to `fixed`.
+- The main layout for `active_admin.html.erb` now includes the `pt-16` utility class.
+
+Starting with 4.0.0.beta19, we've migrated to Tailwind CSS v4 which requires several updates.
+
+Update your `active_admin.css` file:
+
+```diff
+-@tailwind base;
+-@tailwind components;
+-@tailwind utilities;
++@import "tailwindcss";
++
++@config "../../../tailwind-active_admin.config.js";
+```
+
+Update the `build:css` script in your `package.json`:
+
+```diff
+-"build:css": "tailwindcss -i ./app/assets/stylesheets/active_admin.css -o ./app/assets/builds/active_admin.css --minify -c tailwind-active_admin.config.js"
++"build:css": "npx @tailwindcss/cli -i ./app/assets/stylesheets/active_admin.css -o ./app/assets/builds/active_admin.css --minify"
+```
+
+You may see the following warning when upgrading:
+
+```
+[MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of tailwind-active_admin.config.js is not specified and it doesn't parse as CommonJS.
+Reparsing as ES module because module syntax was detected. This incurs a performance overhead.
+To eliminate this warning, add "type": "module" to ./package.json.
+```
+
+The Tailwind config file now uses ES module syntax. To fix it, either:
+- Rename `tailwind-active_admin.config.js` to `tailwind-active_admin.config.mjs`; or
+- Add `"type": "module"` to your `package.json` (your application may already be compatible with ESM).
+
+### Breaking Changes
+- jQuery and jQuery UI have been removed.
+- The `columns` component has been removed. Use `div`'s with Tailwind classes for modern, responsive layout.
+
+
+ Columns Component Migration Alternative
+
+ If you did not specify any parameters for `column` and if all you need is equal width columns, then this single component will restore that functionality for any number of columns.
+
+ ```ruby
+ # app/admin/components/columns.rb
+ class Columns < ActiveAdmin::Component
+ builder_method :columns
+
+ def build(*args)
+ super
+ add_class "grid auto-cols-fr grid-flow-col gap-4 mb-4"
+ end
+
+ def column(*args, &block)
+ insert_tag Arbre::HTML::Div, *args, &block
+ end
+ end
+ ```
+
+ Using Tailwind modifiers you can further customize the number of columns for responsive/mobile support.
+
+
+- The `tabs` component has been removed. Use a CSS based or third party alternative.
+- Replace `default_main_content` with `render "show_default"`.
+
+
+ Show Default Alternative
+
+ If block form `default_main_content do ... end` was used or looking for a partial file
+ alternative, then replace with existing, public methods.
+
+ ```ruby
+ attributes_table_for(resource) do
+ rows *active_admin_config.resource_columns
+ row :a
+ row :b
+ # ...
+ end
+ active_admin_comments_for(resource) if active_admin_config.comments?
+ ```
+
+
+- Replace `as: :datepicker` with Formtastic's `as: :date_picker` for native HTML date input.
+- Replace `active_admin_comments` with `active_admin_comments_for(resource)`.
+- In a sidebar section, replace `attributes_table` with `attributes_table_for(resource)`.
+- The `IndexAsBlog`, `IndexAsBlock` and `IndexAsGrid` components have been removed. Please create your own custom index-as components which remain supported.
+- Batch Actions Form DSL has been replaced with Rails partial support so you can supply your own custom form and modal.
+
+
+ Batch Action Partial Example
+
+ Assuming a Post resource (in the default namespace) with a `mark_published` batch action, we set the partial name and a set of HTML data attributes to trigger a modal using Flowbite which is included by default.
+
+ Note that you can use any modal JS library you want as long as it can be triggered to open using data attributes. Flowbite usage is not a requirement.
+
+ ```ruby
+ batch_action(
+ :mark_published,
+ partial: "mark_published_batch_action",
+ link_html_options: {
+ "data-modal-target": "mark-published-modal",
+ "data-modal-show": "mark-published-modal"
+ }
+ ) do |ids, inputs|
+ # ...
+ end
+ ```
+
+ In the `app/views/admin/posts` directory, create a `_mark_published_batch_action.html.erb` partial file which will be rendered and included automatically in the posts index admin page.
+
+ Now add the modal HTML where the `id` attribute must match the data attributes supplied in the `batch_action` example. The form must have an empty `data-batch-action-form` attribute.
+
+ ```
+
+
+ <%= form_tag false, "data-batch-action-form": "" do %>
+
+ <% end %>
+
+ ```
+
+ The `data-batch-action-form` attribute is a hook for a delegated JS event so when you submit the form, it will post and run your batch action block with the supplied form data, functioning as it did before.
+
+
+- Deeply nested submenus has been reverted. Only one level nested menu, e.g. `menu parent: "Administrative"`, is supported.
+- Removed `Panel#header_action` method.
+- Removed `index_column` method from index table.
+
+
+ Implementation Example
+
+ You can re-implement this column with the following:
+
+ ```ruby
+ column "Number", sortable: false do |item|
+ @collection.offset_value + @collection.index(item) + 1
+ end
+ ```
+
+- Using `row 'name'` in an `attributes_table` block, now prints the header text as is. For a localized lookup of the header with titlized fallback, use `row :name` instead. This matches the `column 'name'` behavior of index tables (`TableFor`).
+
+#### Resource named methods
+
+With the extraction to partials, resource named methods, e.g. `post` or `posts`, used in blocks for `action_item` and `sidebar` will raise an error. You must use the `resource` or `collection` public helper method instead. For example:
+
+```ruby
+action_item :view, if: ->{ post.published? } do link_to(resource) end
+sidebar :author, if: ->{ post.published? }
+# The above must now change to the following:
+action_item :view, if: ->{ resource.published? } do link_to(resource) end
+sidebar :author, if: ->{ resource.published? }
+```
+
+Note that `@post` can also be used here but make sure to call `authorize!` on it if using the authorization feature. The `post` usage would continue to work for `sidebar :name do ... end` content blocks because they can include Arbre but we advise using `resource` or `collection` instead where possible. This may impact other DSL's.
+
+### Visual Related Changes
+- The `sidebar do ... end` contents and the show resource `attributes_table`, are no longer wrapped in a panel so they can be customized.
+- Links in custom `action_item`'s have no default styles. Apply your own or use the library's default `action-item-button` class.
+- The index table `actions dropdown: true` option will be ignored, reverting to original output.
+- An `Arbre::Component` will no longer add a CSS class using the component class name by default.
+- Typographic elements (other than links in main content) [are not styled by default](https://tailwindcss.com/docs/preflight). Use the `@tailwindcss/typography` plugin or apply your own CSS alternative.
+
+### Enhancements
+- Dark mode support.
+- Mobile web support. For responsive `table_for`'s, wrap them in a div with overflow for horizontal scrolling.
+- Customizable admin theme, including main menu and user menu, all through partials.
+- RTL support improved. Now using CSS Logical Properties.
+- Kaminari templates now consolidated into a single set you can customize.
+- Datepicker's now use the native HTML date input. Apply a custom datepicker of your choosing.
+- Batch Actions Form DSL has been replaced with partials and form builder for more customization. Please refer to earlier example.
+- The `status_tag` component now uses unique labels for `false` and `nil` values.
+- Several components: `table_for`, `status_tag`, etc. now use data attributes instead of classes for metadata: status, sort direction, column, etc.
+- Arbre builder methods have been reduced to the minimum so you can use elements or DSLs without clashing e.g. `header`, `footer`, `columns`, etc.
+- The [app-helpers-not-reloading bug has been fixed](https://github.com/activeadmin/activeadmin/pull/8180) and the engine namespace is now isolated.
+
+### Localization Updates
+
+This release includes several locale changes. Please [review the en.yml locale](https://github.com/activeadmin/activeadmin/blob/master/config/locales/en.yml) for the latest translations.
+
+- Removed keys: `dashboard_welcome`, `dropdown_actions`, `main_content` and `unsupported_browser`.
+- New keys: `toggle_dark_mode`, `toggle_main_navigation_menu`, `toggle_section`, and `toggle_user_menu` have been added.
+- The `active_admin.pagination` keys have been rewritten to be less verbose and include new entries: next and previous.
+
+ ```diff
+ - one: "Displaying 1 %{model}"
+ + one: "Showing 1 of 1"
+ - one_page: "Displaying all %{n} %{model}"
+ + one_page: "Showing all %{n}"
+ - multiple: "Displaying %{model} %{from} - %{to} of %{total} in total"
+ + multiple: "Showing %{from}-%{to} of %{total}"
+ - multiple_without_total: "Displaying %{model} %{from} - %{to}"
+ + multiple_without_total: "Showing %{from}-%{to}"
+ - per_page: "Per page: "
+ + per_page: "Per page "
+ + previous: "Previous"
+ + next: "Next"
+ ```
+
+- The `search_status` key contents has multiple, breaking changes:
+
+ ```diff
+ - headline: "Search status:"
+ - current_scope: "Scope:"
+ - current_filters: "Current filters:"
+ + title: "Active Search"
+ + title_with_scope: "Active Search for %{name}"
+ - no_current_filters: "None"
+ + no_current_filters: "No filters applied"
+ ```
+
+- The value for the `status_tag.unset` key has changed from "No" to "Unknown".
+- The `comments.title_content` text has been updated with an "All " prefix.
+- The `comments.delete_confirmation` text has been fixed to use singular form.
+- The `batch_actions.succesfully_destroyed` key has been renamed to fix a typo.
+
+ ```diff
+ - succesfully_destroyed:
+ + successfully_destroyed:
+ ```
+- Inconsistent use of login/sign-in related terms so text now uses "Sign in", Sign out", and "Sign up" throughout.
diff --git a/_typos.toml b/_typos.toml
new file mode 100644
index 00000000000..a89d7205417
--- /dev/null
+++ b/_typos.toml
@@ -0,0 +1,20 @@
+# https://github.com/crate-ci/typos#false-positives
+[default]
+
+[default.extend-identifiers]
+
+[default.extend-words]
+rememberable = "rememberable"
+
+[type.md]
+extend-ignore-identifiers-re = [
+ "succesfully_destroyed"
+]
+
+[files]
+extend-exclude = [
+ "config/locales/*",
+ "!config/locales/en*.yml",
+ "features/step_definitions/batch_action_steps.rb",
+ "vendor/*"
+]
diff --git a/activeadmin.gemspec b/activeadmin.gemspec
index a6554756158..805a955ddd2 100644
--- a/activeadmin.gemspec
+++ b/activeadmin.gemspec
@@ -1,32 +1,42 @@
-# -*- encoding: utf-8 -*-
-$:.push File.expand_path("../lib", __FILE__)
-require "active_admin/version"
+# frozen_string_literal: true
+require File.join(__dir__, "lib", "active_admin", "version")
Gem::Specification.new do |s|
- s.name = %q{activeadmin}
- s.license = "MIT"
- s.version = ActiveAdmin::VERSION
- s.platform = Gem::Platform::RUBY
- s.homepage = %q{http://activeadmin.info}
- s.authors = ["Greg Bell"]
- s.email = ["gregdbell@gmail.com"]
- s.description = %q{The administration framework for Ruby on Rails.}
- s.summary = %q{The administration framework for Ruby on Rails.}
+ s.name = "activeadmin"
+ s.license = "MIT"
+ s.version = ActiveAdmin::VERSION
+ s.homepage = "https://activeadmin.info"
+ s.authors = ["Charles Maresh", "David Rodríguez", "Greg Bell", "Igor Fedoronchuk", "Javier Julio", "Piers C", "Sean Linsley", "Timo Schilling"]
+ s.email = ["deivid.rodriguez@riseup.net"]
+ s.description = "The administration framework for Ruby on Rails."
+ s.summary = "Active Admin is a Ruby on Rails plugin for generating " \
+ "administration style interfaces. It abstracts common business " \
+ "application patterns to make it simple for developers to implement " \
+ "beautiful and elegant interfaces with very little effort."
- s.files = `git ls-files`.split("\n").sort
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
- s.require_paths = ["lib"]
+ s.files = Dir["LICENSE", "plugin.js", 'config/importmap.rb', "{app,config/locales,lib,vendor}/**/{.*,*}"].reject { |f| File.directory?(f) }
- s.add_dependency("rails", ">= 3.0.0")
- s.add_dependency("jquery-rails", ">= 1.0.0", "< 3.0.0")
- s.add_dependency("bourbon", ">= 1.0.0")
- s.add_dependency("meta_search", ">= 0.9.2")
- s.add_dependency("devise", ">= 1.1.2")
- s.add_dependency("formtastic", ">= 2.0.0")
- s.add_dependency("inherited_resources", ">= 1.3.1")
- s.add_dependency("kaminari", ">= 0.13.0")
- s.add_dependency("sass", ">= 3.1.0")
- s.add_dependency("fastercsv", ">= 0")
- s.add_dependency("arbre", ">= 1.0.1")
+ s.extra_rdoc_files = %w[CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md README.md UPGRADING.md]
+
+ s.metadata = {
+ "bug_tracker_uri" => "https://github.com/activeadmin/activeadmin/issues",
+ "changelog_uri" => "https://github.com/activeadmin/activeadmin/releases",
+ "documentation_uri" => "https://activeadmin.info",
+ "homepage_uri" => "https://activeadmin.info",
+ "mailing_list_uri" => "https://groups.google.com/group/activeadmin",
+ "rubygems_mfa_required" => "true",
+ "source_code_uri" => "https://github.com/activeadmin/activeadmin",
+ "wiki_uri" => "https://github.com/activeadmin/activeadmin/wiki"
+ }
+
+ s.required_ruby_version = ">= 3.3"
+
+ s.add_dependency "arbre", "~> 2.0"
+ s.add_dependency "csv"
+ s.add_dependency "formtastic", ">= 6.0"
+ s.add_dependency "formtastic_i18n", ">= 0.7"
+ s.add_dependency "inherited_resources", "~> 2.0"
+ s.add_dependency "kaminari", ">= 1.2.1"
+ s.add_dependency "railties", ">= 7.2"
+ s.add_dependency "ransack", ">= 4.0"
end
diff --git a/app/assets/config/active_admin_manifest.js b/app/assets/config/active_admin_manifest.js
new file mode 100644
index 00000000000..5d4ceea5b82
--- /dev/null
+++ b/app/assets/config/active_admin_manifest.js
@@ -0,0 +1,2 @@
+//= link_tree ../../javascript .js
+//= link_tree ../../../vendor/javascript .js
diff --git a/app/assets/images/active_admin/admin_notes_icon.png b/app/assets/images/active_admin/admin_notes_icon.png
deleted file mode 100644
index 1d240a1a1ee..00000000000
Binary files a/app/assets/images/active_admin/admin_notes_icon.png and /dev/null differ
diff --git a/app/assets/images/active_admin/datepicker/datepicker-header-bg.png b/app/assets/images/active_admin/datepicker/datepicker-header-bg.png
deleted file mode 100644
index ccecd966842..00000000000
Binary files a/app/assets/images/active_admin/datepicker/datepicker-header-bg.png and /dev/null differ
diff --git a/app/assets/images/active_admin/datepicker/datepicker-input-icon.png b/app/assets/images/active_admin/datepicker/datepicker-input-icon.png
deleted file mode 100644
index 40ad6f74074..00000000000
Binary files a/app/assets/images/active_admin/datepicker/datepicker-input-icon.png and /dev/null differ
diff --git a/app/assets/images/active_admin/datepicker/datepicker-next-link-icon.png b/app/assets/images/active_admin/datepicker/datepicker-next-link-icon.png
deleted file mode 100644
index eae8bbe0939..00000000000
Binary files a/app/assets/images/active_admin/datepicker/datepicker-next-link-icon.png and /dev/null differ
diff --git a/app/assets/images/active_admin/datepicker/datepicker-nipple.png b/app/assets/images/active_admin/datepicker/datepicker-nipple.png
deleted file mode 100644
index b04c8f00b8f..00000000000
Binary files a/app/assets/images/active_admin/datepicker/datepicker-nipple.png and /dev/null differ
diff --git a/app/assets/images/active_admin/datepicker/datepicker-prev-link-icon.png b/app/assets/images/active_admin/datepicker/datepicker-prev-link-icon.png
deleted file mode 100644
index 03c1a0adece..00000000000
Binary files a/app/assets/images/active_admin/datepicker/datepicker-prev-link-icon.png and /dev/null differ
diff --git a/app/assets/images/active_admin/index_list_icons/block_icon.svg b/app/assets/images/active_admin/index_list_icons/block_icon.svg
deleted file mode 100755
index 70bf534cd2e..00000000000
--- a/app/assets/images/active_admin/index_list_icons/block_icon.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/app/assets/images/active_admin/index_list_icons/blog_icon.svg b/app/assets/images/active_admin/index_list_icons/blog_icon.svg
deleted file mode 100755
index b9d3216d6a7..00000000000
--- a/app/assets/images/active_admin/index_list_icons/blog_icon.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/app/assets/images/active_admin/index_list_icons/grid_icon.svg b/app/assets/images/active_admin/index_list_icons/grid_icon.svg
deleted file mode 100755
index b1645fad641..00000000000
--- a/app/assets/images/active_admin/index_list_icons/grid_icon.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
diff --git a/app/assets/images/active_admin/index_list_icons/table_icon.svg b/app/assets/images/active_admin/index_list_icons/table_icon.svg
deleted file mode 100755
index 13b8068e15a..00000000000
--- a/app/assets/images/active_admin/index_list_icons/table_icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/app/assets/images/active_admin/loading.gif b/app/assets/images/active_admin/loading.gif
deleted file mode 100644
index da33c3e03b6..00000000000
Binary files a/app/assets/images/active_admin/loading.gif and /dev/null differ
diff --git a/app/assets/images/active_admin/nested_menu_arrow.gif b/app/assets/images/active_admin/nested_menu_arrow.gif
deleted file mode 100644
index 878357fe4c3..00000000000
Binary files a/app/assets/images/active_admin/nested_menu_arrow.gif and /dev/null differ
diff --git a/app/assets/images/active_admin/nested_menu_arrow_dark.gif b/app/assets/images/active_admin/nested_menu_arrow_dark.gif
deleted file mode 100644
index 006372c8243..00000000000
Binary files a/app/assets/images/active_admin/nested_menu_arrow_dark.gif and /dev/null differ
diff --git a/app/assets/images/active_admin/orderable.png b/app/assets/images/active_admin/orderable.png
deleted file mode 100644
index 427009e7207..00000000000
Binary files a/app/assets/images/active_admin/orderable.png and /dev/null differ
diff --git a/app/assets/javascripts/active_admin/application.js b/app/assets/javascripts/active_admin/application.js
deleted file mode 100644
index 7860adf1cc2..00000000000
--- a/app/assets/javascripts/active_admin/application.js
+++ /dev/null
@@ -1,3 +0,0 @@
-//= require_tree ./lib/
-//= require_tree ./components/
-//= require_tree ./pages/
diff --git a/app/assets/javascripts/active_admin/base.js b/app/assets/javascripts/active_admin/base.js
deleted file mode 100644
index f88ab6c237f..00000000000
--- a/app/assets/javascripts/active_admin/base.js
+++ /dev/null
@@ -1,5 +0,0 @@
-//= require jquery
-//= require jquery-ui
-//= require jquery_ujs
-
-//= require active_admin/application
diff --git a/app/assets/javascripts/active_admin/components/jquery.aa.checkbox-toggler.js.coffee b/app/assets/javascripts/active_admin/components/jquery.aa.checkbox-toggler.js.coffee
deleted file mode 100644
index fcbad1236ee..00000000000
--- a/app/assets/javascripts/active_admin/components/jquery.aa.checkbox-toggler.js.coffee
+++ /dev/null
@@ -1,37 +0,0 @@
-window.AA.CheckboxToggler = class AA.CheckboxToggler
- constructor: (@options, @container)->
- defaults = {}
- @options = $.extend {}, defaults, options
- @_init()
- @_bind()
-
- _init: ->
- if not @container
- throw new Error('Container element not found')
- else
- @$container = $(@container)
-
- if not @$container.find('.toggle_all').length
- throw new Error('"toggle all" checkbox not found')
- else
- @toggle_all_checkbox = @$container.find '.toggle_all'
-
- @checkboxes = @$container.find(':checkbox').not @toggle_all_checkbox
-
- _bind: ->
- @checkboxes.change (e)=> @_didChangeCheckbox e.target
- @toggle_all_checkbox.change => @_didChangeToggleAllCheckbox()
-
- _didChangeCheckbox: (checkbox)->
- switch @checkboxes.filter(':checked').length
- when @checkboxes.length - 1 then @toggle_all_checkbox.prop checked: null
- when @checkboxes.length then @toggle_all_checkbox.prop checked: true
-
- _didChangeToggleAllCheckbox: ->
- setting = if @toggle_all_checkbox.prop 'checked' then true else null
- @checkboxes.each (index, el)=>
- $(el).prop checked: setting
- @_didChangeCheckbox(el)
-
-jQuery ($)->
- $.widget.bridge 'checkboxToggler', AA.CheckboxToggler
diff --git a/app/assets/javascripts/active_admin/components/jquery.aa.dropdown-menu.js.coffee b/app/assets/javascripts/active_admin/components/jquery.aa.dropdown-menu.js.coffee
deleted file mode 100644
index f23d7c25925..00000000000
--- a/app/assets/javascripts/active_admin/components/jquery.aa.dropdown-menu.js.coffee
+++ /dev/null
@@ -1,105 +0,0 @@
-window.AA.DropdownMenu = class AA.DropdownMenu
-
- constructor: (@options, @element) ->
-
- @$element = $(@element)
-
- defaults = {
- fadeInDuration: 20,
- fadeOutDuration: 100,
- onClickActionItemCallback: null
- }
-
- @options = $.extend({}, defaults, options)
-
- @$menuButton = @$element.find(".dropdown_menu_button")
- @$menuList = @$element.find(".dropdown_menu_list_wrapper")
-
- @isOpen = false
-
- @_buildMenuList()
- @_bind()
-
- open: ->
- @isOpen = true
- @$menuList.fadeIn @options.fadeInDuration
-
- @_positionMenuList()
- @_positionNipple()
-
- return @
-
-
- close: ->
- @isOpen = false
- @$menuList.fadeOut this.options.fadeOutDuration
-
- return @
-
- destroy: ->
- @$element.unbind()
- @$element = null
-
- return @
-
- isDisabled: ->
- @$menuButton.hasClass("disabled")
-
- disable: ->
- @$menuButton.addClass("disabled")
-
- enable: ->
- @$menuButton.removeClass("disabled")
-
- option: (key, value) ->
- if $.isPlainObject(key)
- return @options = $.extend(true, @options, key)
-
- else if key?
- return @options[key]
-
- else
- return @options[key] = value
-
- # Private
-
- _buildMenuList: ->
- @$menuList.prepend("")
- @$menuList.hide()
-
- _bind: ->
- $("body").bind 'click', () =>
- if @isOpen is true
- @close()
-
- @$menuButton.bind 'click', () =>
- unless @isDisabled()
- if @isOpen is true
- @close()
- else
- @open()
-
- # Return false so that the event is stopped
- false
-
- _positionMenuList: ->
- centerOfButtonFromLeft = @$menuButton.position().left + @$menuButton.outerWidth() / 2
- centerOfmenuListFromLeft = @$menuList.outerWidth() / 2
- menuListLeftPos = centerOfButtonFromLeft - centerOfmenuListFromLeft
- @$menuList.css "left", menuListLeftPos
-
- _positionNipple: ->
- centerOfmenuListFromLeft = @$menuList.outerWidth() / 2
- bottomOfButtonFromTop = @$menuButton.position().top + @$menuButton.outerHeight() + 10
- @$menuList.css "top", bottomOfButtonFromTop
- $nipple = @$menuList.find(".dropdown_menu_nipple")
- centerOfnippleFromLeft = $nipple.outerWidth() / 2
- nippleLeftPos = centerOfmenuListFromLeft - centerOfnippleFromLeft
- $nipple.css "left", nippleLeftPos
-
-(($) ->
- $.widget.bridge 'aaDropdownMenu', AA.DropdownMenu
-
- $ ->
- $(".dropdown_menu").aaDropdownMenu()
-)(jQuery)
diff --git a/app/assets/javascripts/active_admin/components/jquery.aa.popover.js.coffee b/app/assets/javascripts/active_admin/components/jquery.aa.popover.js.coffee
deleted file mode 100644
index 0b63f3637ba..00000000000
--- a/app/assets/javascripts/active_admin/components/jquery.aa.popover.js.coffee
+++ /dev/null
@@ -1,97 +0,0 @@
-
-window.AA.Popover = class AA.Popover
-
- constructor: (@options, @element) ->
-
- @$element = $(@element)
-
- defaults = {
- fadeInDuration: 20,
- fadeOutDuration: 100,
- autoOpen: true,
- pageWrapperElement: "#wrapper",
- onClickActionItemCallback: null
- }
-
- @options = $.extend({}, defaults, options)
-
- @$popover = null
- @isOpen = false
-
- if $(@$element.attr("href")).length > 0
- @$popover = $(@$element.attr("href"))
- else
- @$popover = @$element.next(".popover")
-
-
- @_buildPopover()
- @_bind()
-
- open: ->
- @isOpen = true
- @$popover.fadeIn @options.fadeInDuration
-
- @_positionPopover()
- @_positionNipple()
-
- return @
-
-
- close: ->
- @isOpen = false;
- @$popover.fadeOut this.options.fadeOutDuration;
-
- return @
-
- destroy: ->
- @$element.removeData('popover');
- @$element.unbind();
- @$element = null;
-
- return @
-
- option: ->
- # ??
-
- # Private
-
- _buildPopover: ->
- @$popover.prepend("")
-
- @$popover.hide()
-
- @$popover.addClass "popover"
-
-
- _bind: ->
- $(@options.pageWrapperElement).bind 'click', (e) =>
- if @isOpen is true
- @close()
-
- if @options.autoOpen is true
- @$element.bind 'click', () =>
- if @isOpen is true
- @close()
- else
- @open()
-
- false
-
- _positionPopover: ->
- centerOfButtonFromLeft = @$element.offset().left + @$element.outerWidth() / 2
- centerOfPopoverFromLeft = @$popover.outerWidth() / 2
- popoverLeftPos = centerOfButtonFromLeft - centerOfPopoverFromLeft
- @$popover.css "left", popoverLeftPos
-
- _positionNipple: ->
- centerOfPopoverFromLeft = @$popover.outerWidth() / 2
- bottomOfButtonFromTop = @$element.offset().top + @$element.outerHeight() + 10
- @$popover.css "top", bottomOfButtonFromTop
- $nipple = @$popover.find(".popover_nipple")
- centerOfnippleFromLeft = $nipple.outerWidth() / 2
- nippleLeftPos = centerOfPopoverFromLeft - centerOfnippleFromLeft
- $nipple.css "left", nippleLeftPos
-
-(($) ->
- $.widget.bridge 'popover', AA.Popover
-)(jQuery)
diff --git a/app/assets/javascripts/active_admin/components/jquery.aa.table-checkbox-toggler.js.coffee b/app/assets/javascripts/active_admin/components/jquery.aa.table-checkbox-toggler.js.coffee
deleted file mode 100644
index f397cfa95a0..00000000000
--- a/app/assets/javascripts/active_admin/components/jquery.aa.table-checkbox-toggler.js.coffee
+++ /dev/null
@@ -1,25 +0,0 @@
-window.AA.TableCheckboxToggler = class AA.TableCheckboxToggler extends AA.CheckboxToggler
- _init: ->
- super
-
- _bind: ->
- super
-
- @$container.find('tbody td').click (e)=>
- @_didClickCell(e.target) if e.target.type isnt 'checkbox'
-
- _didChangeCheckbox: (checkbox) ->
- super
-
- $row = $(checkbox).parents 'tr'
-
- if checkbox.checked
- $row.addClass 'selected'
- else
- $row.removeClass 'selected'
-
- _didClickCell: (cell) ->
- $(cell).parent('tr').find(':checkbox').click()
-
-jQuery ($)->
- $.widget.bridge 'tableCheckboxToggler', AA.TableCheckboxToggler
diff --git a/app/assets/javascripts/active_admin/lib/namespace.js.coffee b/app/assets/javascripts/active_admin/lib/namespace.js.coffee
deleted file mode 100644
index 72ab2d31361..00000000000
--- a/app/assets/javascripts/active_admin/lib/namespace.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Make a namespace for ActiveAdmin plugins
-
-window.AA = {}
diff --git a/app/assets/javascripts/active_admin/pages/application.js.coffee b/app/assets/javascripts/active_admin/pages/application.js.coffee
deleted file mode 100644
index 5c74d6559fd..00000000000
--- a/app/assets/javascripts/active_admin/pages/application.js.coffee
+++ /dev/null
@@ -1,21 +0,0 @@
-# Initializers
-$ ->
- # jQuery datepickers (also evaluates dynamically added HTML)
- $(document).on 'focus', '.datepicker:not(.hasDatepicker)', ->
- $(@).datepicker dateFormat: 'yy-mm-dd'
-
- # Clear Filters button
- $('.clear_filters_btn').click ->
- window.location.search = ''
-
- # Batch Actions dropdown
- $('.dropdown_button').popover()
-
- # Filter form: don't send any inputs that are empty
- $('.filter_form').submit ->
- $(@).find(':input').filter(-> @value is '').prop 'disabled', true
-
- # Filter form: for filters that let you choose the query method from
- # a dropdown, apply that choice to the filter input field.
- $('.filter_form_field.select_and_search select').change ->
- $(@).siblings('input').prop name: "q[#{@value}]"
diff --git a/app/assets/javascripts/active_admin/pages/batch_actions.js.coffee b/app/assets/javascripts/active_admin/pages/batch_actions.js.coffee
deleted file mode 100644
index 413532aed06..00000000000
--- a/app/assets/javascripts/active_admin/pages/batch_actions.js.coffee
+++ /dev/null
@@ -1,26 +0,0 @@
-jQuery ($) ->
-
- #
- # Use Rails.js click handler to allow for Rails' confirm dialogs
- #
-
- $(document).delegate "#batch_actions_selector li a", "click.rails", ->
- $("#batch_action").val $(@).attr("data-action")
- $("#collection_selection").submit()
-
- #
- # Add checkbox selection to resource tables and lists if batch actions are enabled
- #
-
- if $("#batch_actions_selector").length && $(":checkbox.toggle_all").length
-
- if $(".paginated_collection table.index_table").length
- $(".paginated_collection table.index_table").tableCheckboxToggler()
- else
- $(".paginated_collection").checkboxToggler()
-
- $(".paginated_collection").find(":checkbox").bind "change", ->
- if $(".paginated_collection").find(":checkbox").filter(":checked").length > 0
- $("#batch_actions_selector").aaDropdownMenu("enable")
- else
- $("#batch_actions_selector").aaDropdownMenu("disable")
diff --git a/app/assets/stylesheets/active_admin/_base.css.scss b/app/assets/stylesheets/active_admin/_base.css.scss
deleted file mode 100644
index 9489c33d14a..00000000000
--- a/app/assets/stylesheets/active_admin/_base.css.scss
+++ /dev/null
@@ -1,41 +0,0 @@
-/* Active Admin CSS */
-// Reset Away!
-@include global-reset;
-
-// Partials
-@import "active_admin/typography";
-@import "active_admin/header";
-@import "active_admin/forms";
-@import "active_admin/components/comments";
-@import "active_admin/components/flash_messages";
-@import "active_admin/components/date_picker";
-@import "active_admin/components/popovers";
-@import "active_admin/components/tables";
-@import "active_admin/components/batch_actions";
-@import "active_admin/components/blank_slates";
-@import "active_admin/components/breadcrumbs";
-@import "active_admin/components/dropdown_menu";
-@import "active_admin/components/buttons";
-@import "active_admin/components/grid";
-@import "active_admin/components/links";
-@import "active_admin/components/pagination";
-@import "active_admin/components/panels";
-@import "active_admin/components/columns";
-@import "active_admin/components/scopes";
-@import "active_admin/components/status_tags";
-@import "active_admin/components/table_tools";
-@import "active_admin/components/index_list";
-@import "active_admin/pages/logged_out";
-@import "active_admin/structure/footer";
-@import "active_admin/structure/main_structure";
-@import "active_admin/structure/title_bar";
-
-body {
- @include sans-family;
- line-height: 150%;
- font-size: 72%;
- background: #fff;
- margin: 0;
- padding: 0;
- color: $text-color;
-}
diff --git a/app/assets/stylesheets/active_admin/_forms.css.scss b/app/assets/stylesheets/active_admin/_forms.css.scss
deleted file mode 100644
index 6bf0e206ef9..00000000000
--- a/app/assets/stylesheets/active_admin/_forms.css.scss
+++ /dev/null
@@ -1,281 +0,0 @@
-// -------------------------------------- Active Admin Forms
-form {
- /* Reset margins & Padding */
- ul, ol, li, fieldset, legend, input, textarea, select, p { margin:0; padding:0; }
- ol, ul { list-style: none }
-
- fieldset {
- border: 0;
- padding: 10px 0;
- margin-bottom: 20px;
-
- &.inputs { @include section-background; }
-
- legend {
- width: 100%;
- span { display: block; @include section-header; }
- }
-
- ol > li {
- padding: 10px;
- label {
- display: block;
- width: 20%;
- float: left;
- font-size: 1.0em;
- font-weight: bold;
- color: $section-header-text-color;
- abbr { border: none; color: #aaa; }
- }
- }
-
- ol > div.has_many {
- padding: 20px 10px;
- h3 {
- font-size: 12px;
- font-weight: bold;
- }
- }
-
- ol > li > li label {
- line-height:100%;
- padding-top:0;
- input {
- line-height:100%;
- vertical-align:middle;
- margin-top:-0.1em;
- }
- }
- }
-
- /* Nested Fieldsets and Legends */
- fieldset > ol > li {
- fieldset {
- position:relative;
- padding: 0;
- margin-bottom: 0;
-
- legend {
- position:absolute;
- width:95%;
- padding-top:0.1em;
- left: 0px;
- font-size: 100%;
- font-weight: normal;
- span { position:absolute; }
- &.label label { position:absolute; }
- }
-
- ol {
- float:left;
- width:74%;
- margin:0;
- padding:0 0 0 20%;
-
- li {
- padding:0;
- border:0;
- }
- }
- }
- }
-
- /* Text Fields */
- input[type=text], input[type=password], input[type=email], input[type=number], input[type=url], input[type=tel], textarea {
- width: 76%;
- border: 1px solid #c9d0d6;
- @include rounded;
- font-size: 0.95em;
- @include sans-family;
- outline: none;
- padding: 8px $text-input-horizontal-padding 7px;
-
- &:focus {
- border: 1px solid #99a2aa;
- @include shadow(0,0,4px,#99a2aa);
- }
- }
-
- fieldset > ol > li {
-
- /* Hints */
- p.inline-hints {
- font-size: 0.95em;
- font-style: italic;
- color:#666;
- margin: 0.5em 0 0 20%;
- }
-
- /* Date and Time Fields */
- &.date, &.time, &.datetime, &.date_select {
- fieldset ol li {
- float:left; width:auto; margin:0 0.5em 0 0;
- label { display: none; }
- input { display:inline; margin:0; padding:0; }
- }
- }
-
- /* Check Boxes or Radio fields */
- &.check_boxes, &.radio {
- fieldset ol {
- margin-bottom:-0.6em;
- li {
- margin:0.1em 0 0.5em 0;
- label {
- float:none;
- width:100%;
- input { margin-right:0.2em; }
- }
- }
- }
- }
-
- /* Boolean Field */
- &.boolean {
- height: 1.1em;
- label {
- width: 80%;
- padding-left:20%;
- padding-right: 10px;
- text-transform: none !important;
- font-weight: normal;
- input { margin:0 0.5em 0 0.2em; }
- }
- }
-
- /* Errors */
- p.inline-errors {
- color: $error-color;
- font-weight: bold;
- margin:0.3em 0 0 20%;
- }
- ul.errors {
- color: $error-color;
- margin:0.5em 0 0 20%;
- list-style:square;
- li { padding:0; border:none; display:list-item; }
- }
-
- &.error {
- input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel], textarea {
- border: 1px solid $error-color;
- }
- }
- }
-
- /* semantic_errors */
- ul.errors {
- background: lighten($error-color, 60%);
- @include rounded(4px);
- color: $error-color;
- font-weight: bold;
- margin-bottom: 10px;
- padding: 10px;
- list-style:square;
- li { margin-left:15px; padding:0; border:none; display:list-item; }
- }
-
- /* Buttons */
-
- input[type=submit] {
- @include dark-button;
- cursor: pointer;
-
- }
-
- .buttons, .actions {
- margin-top: 15px;
- input[type=submit] { margin-right: 10px; }
- }
-
- fieldset.buttons li, fieldset.actions li {
- float:left;
- padding: 0;
-
- &.cancel {
- a {
- @include light-button; @include icon(#777, 9px);
- }
- }
- }
-
-}
-
-// -------------------------------------- Sidebar Forms
-
-$sidebar-inner-content-width: $sidebar-width - ($section-padding * 2);
-
-.sidebar_section {
-
- label {
- display: block;
- text-transform: uppercase;
- color: $section-header-text-color;
- font-size: 0.9em;
- font-weight: bold;
- }
-
- select {
- width: $sidebar-inner-content-width;
- }
-
- input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel], textarea {
- width: $sidebar-inner-content-width - ($text-input-horizontal-padding * 2);
- }
-
-}
-
-// -------------------------------------- Filter Forms
-
-$filter-field-seperator-width: 12px;
-
-$side-by-side-filter-input-width: ($sidebar-inner-content-width / 2) - ($text-input-horizontal-padding * 2) - $filter-field-seperator-width;
-$side-by-side-filter-select-width: ($sidebar-inner-content-width / 2) - $filter-field-seperator-width;
-
-$date-range-filter-input-right-padding: 27px;
-$date-range-filter-input-horizontal-padding: $date-range-filter-input-right-padding + $text-input-horizontal-padding;
-$date-range-filter-input-width: ($sidebar-inner-content-width / 2) - $filter-field-seperator-width - $date-range-filter-input-horizontal-padding;
-
-form.filter_form {
- .filter_form_field {
- margin-bottom: 10px;
- clear: both;
-
- &.select_and_search {
- input[type=text] {
- margin-left: $filter-field-seperator-width + 4;
- width: $side-by-side-filter-input-width;
- }
- select {
- width: $side-by-side-filter-select-width;
- }
- }
-
- &.filter_check_boxes {
- label { margin-bottom: 3px; }
- .check_boxes_wrapper label {
- font-weight: normal;
- margin-bottom: 3px;
- text-transform: none;
- font-size: 1.0em;
- input { vertical-align: baseline; }
- }
- }
-
- &.filter_date_range {
- .seperator {
- display: inline-block;
- text-align: center;
- width: $filter-field-seperator-width;
- }
-
- input[type=text] {
- background: #fff url(active_admin_image_path('datepicker/datepicker-input-icon.png')) no-repeat 100% 7px;
- padding-right: $date-range-filter-input-right-padding;
- width: $date-range-filter-input-width;
- }
- }
- }
- a.clear_filters_btn { @include light-button; }
-}
-
diff --git a/app/assets/stylesheets/active_admin/_header.css.scss b/app/assets/stylesheets/active_admin/_header.css.scss
deleted file mode 100644
index b0fca081b4e..00000000000
--- a/app/assets/stylesheets/active_admin/_header.css.scss
+++ /dev/null
@@ -1,152 +0,0 @@
-// ----------------------------------- Header
-#header {
- @include primary-gradient;
- @include shadow;
- @include text-shadow(#000);
- display: table;
- height: 20px;
- width: 100%;
- overflow: visible;
- position: inherit;
- padding: 5px 0;
- z-index: 900;
-
- h1 {
- display: table-cell;
- vertical-align: middle;
- white-space: nowrap;
- color: #cdcdcd;
- margin-right: 20px;
- margin-bottom: 0px;
- padding: 3px $horizontal-page-margin 0 $horizontal-page-margin;
- font-size: 1.3em;
- font-weight: normal;
- line-height: 1.2;
-
- a {
- text-decoration: none;
-
- &:hover {
- color: #fff;
- }
- }
-
- img {
- position: relative;
- top: -2px;
- }
- }
-
- a, a:link { color: #cdcdcd; }
-
- .header-item {
- top: 2px;
- position: relative;
- height: 20px
- }
-
- ul.tabs {
- display: table-cell;
- vertical-align: middle;
- height: 100%;
- margin: 0;
- padding: 0;
-
- & > li {
- display: inline-block;
- margin-right: 4px;
- margin-top: 5px;
- margin-bottom: 5px;
- font-size: 1.0em;
- position: relative;
-
- a {
- text-decoration: none;
- padding: 6px 10px 4px 10px;
- position: relative;
- @include rounded(10px);
- }
-
- &.current > a {
- background: $current-menu-item-background;
- color: #fff;
- }
-
- &.has_nested > a {
- background: url(active_admin_image_path('nested_menu_arrow.gif')) no-repeat 89% 50%;
- padding-right: 20px;
- z-index: 1050;
- }
-
- &.has_nested.current > a {
- background: $current-menu-item-background url(active_admin_image_path('nested_menu_arrow_dark.gif')) no-repeat 89% 50%;
- padding-right: 20px;
- }
-
- &:hover > a {
- background: $hover-menu-item-background;
- color: #fff;
- }
-
- &.has_nested:hover > a {
- @include rounded-top(10px);
- border-bottom: 5px solid $hover-menu-item-background;
- background: $hover-menu-item-background url(active_admin_image_path('nested_menu_arrow_dark.gif')) no-repeat 89% 50%;
- }
-
-
- /* Hover on li, display the ul */
- &:hover ul { display: block;}
- /* Drop down menus */
- ul {
- background: $hover-menu-item-background;
- @include rounded-all(0,10px,10px,10px);
- @include shadow(0, 1px, 3px, #444);
- position: absolute;
- width: 175px;
- margin-top: 5px;
- float: left;
- display: none;
- padding: 3px 0px 5px 0;
- list-style: none;
- z-index: 1010;
-
- li {
- margin: 0px;
- a {
- background: none;
- display: block;
- &:hover { color: #fff; background: none; }
- }
-
- &.current {
- a { @include rounded(0) }
- }
- }
- }
- }
-
- }
-
- #tabs {
- width: 100%;
- }
-
- #utility_nav {
- color: #aaa;
- display: table-cell;
- white-space: nowrap;
- margin: 0;
- padding: 0;
- padding-right: 26px;
- text-align: right;
-
- a { text-decoration: none; }
- a:hover { color: #fff; }
-
- li {
- display:inline;
- }
- }
-
-}
diff --git a/app/assets/stylesheets/active_admin/_mixins.css.scss b/app/assets/stylesheets/active_admin/_mixins.css.scss
deleted file mode 100644
index a82c92adf52..00000000000
--- a/app/assets/stylesheets/active_admin/_mixins.css.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import "active_admin/mixins/all";
diff --git a/app/assets/stylesheets/active_admin/_typography.css.scss b/app/assets/stylesheets/active_admin/_typography.css.scss
deleted file mode 100644
index ddb369f37fb..00000000000
--- a/app/assets/stylesheets/active_admin/_typography.css.scss
+++ /dev/null
@@ -1,100 +0,0 @@
-// Adapted from Blueprint CSS Framework
-//
-// Copyright (c) 2007 - 2010 blueprintcss.org
-//
-// Permission is hereby granted, free of charge, to any person
-// obtaining a copy of this software and associated documentation
-// files (the "Software"), to deal in the Software without
-// restriction, including without limitation the rights to use,
-// copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following
-// conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-// OTHER DEALINGS IN THE SOFTWARE.
-
-// Default font settings. The font-size percentage is of 16px. (0.75 * 16px = 12px) */
-html { font-size:100.01%; }
-body { font-size: 75%; font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; }
-
-// Headings
-h1,h2,h3,h4,h5,h6 {
- font-weight: normal;
- color: $primary-color;
- img { margin: 0; }
-}
-
-h1 { font-size: 3em; line-height: 1; margin-bottom: 0.5em; }
-h2 { font-size: 2em; margin-bottom: 0.75em; }
-h3 { font-size: 1.5em; line-height: 1; margin-bottom: 1em; }
-h4 { font-size: 1.2em; line-height: 1.25; margin-bottom: 1.25em; }
-h5 { font-size: 1em; font-weight: bold; margin-bottom: 1.5em; }
-h6 { font-size: 1em; font-weight: bold; }
-
-
-p {
- margin: 0 0 1.5em;
-
- .left { margin: 1.5em 1.5em 1.5em 0; padding: 0; }
- .right { margin: 1.5em 0 1.5em 1.5em; padding: 0; }
-}
-
-.left { float: left !important; }
-.right { float: right !important; }
-
-blockquote { margin: 1.5em; color: #666; font-style: italic; }
-strong,dfn { font-weight: bold; }
-em,dfn { font-style: italic; }
-sup, sub { line-height: 0; }
-
-abbr,
-acronym { border-bottom: 1px dotted #666; }
-address { margin: 0 0 1.5em; font-style: italic; }
-del { color:#666; }
-
-pre { margin: 1.5em 0; white-space: pre; }
-pre,code,tt { font: 1em 'andale mono', 'lucida console', monospace; line-height: 1.5; }
-
-// Lists
-li ul,
-li ol { margin: 0; }
-ul, ol { margin: 0 1.5em 1.5em 0; padding-left: 1.5em; }
-
-ul { list-style-type: disc; }
-ol { list-style-type: decimal; }
-
-dl { margin: 0 0 1.5em 0; }
-dl dt { font-weight: bold; }
-dd { margin-left: 1.5em;}
-
-// Tables
-table { margin-bottom: 1.4em; width:100%; }
-th { font-weight: bold; }
-thead th { background: #c3d9ff; }
-th,td,caption { padding: 4px 10px 4px 5px; }
-
-// Helper Classes
-.small { font-size: .8em; margin-bottom: 1.875em; line-height: 1.875em; }
-.large { font-size: 1.2em; line-height: 2.5em; margin-bottom: 1.25em; }
-.hide { display: none; }
-
-.quiet { color: #666; }
-.loud { color: #000; }
-.highlight { background:#ff0; }
-.added { background:#060; color: #fff; }
-.removed { background:#900; color: #fff; }
-
-.first { margin-left:0; padding-left:0; }
-.last { margin-right:0; padding-right:0; }
-.top { margin-top:0; padding-top:0; }
-.bottom { margin-bottom:0; padding-bottom:0; }
diff --git a/app/assets/stylesheets/active_admin/components/_batch_actions.css.scss b/app/assets/stylesheets/active_admin/components/_batch_actions.css.scss
deleted file mode 100644
index 21daea22546..00000000000
--- a/app/assets/stylesheets/active_admin/components/_batch_actions.css.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-#collection_selection_toggle_panel {
- @include clearfix;
- >.resource_selection_toggle_cell {
- float:left;
- }
- #collection_selection_toggle_explaination {
- float:left;
- margin-left:5px;
- font-style:italic;
- }
-}
diff --git a/app/assets/stylesheets/active_admin/components/_blank_slates.scss b/app/assets/stylesheets/active_admin/components/_blank_slates.scss
deleted file mode 100644
index 80052e33dc1..00000000000
--- a/app/assets/stylesheets/active_admin/components/_blank_slates.scss
+++ /dev/null
@@ -1,31 +0,0 @@
-.blank_slate_container {
- clear: both;
- text-align: center;
-
- .blank_slate {
- @include rounded;
- -webkit-font-smoothing: antialiased;
- border: 1px dashed #DADADA;
- color: #AAA;
- display: inline-block;
- font-size: 1.2em;
- font-weight: bold;
- padding: 14px 25px;
- text-align: center;
-
- small {
- display: block;
- font-size: 0.9em;
- font-weight: normal;
- }
- }
-}
-
-.admin_dashboard .blank_slate_container .blank_slate {
- margin-top: 40px;
- margin-bottom: 40px;
-}
-
-.with_sidebar .blank_slate_container .blank_slate {
- margin-top: 80px;
-}
diff --git a/app/assets/stylesheets/active_admin/components/_breadcrumbs.scss b/app/assets/stylesheets/active_admin/components/_breadcrumbs.scss
deleted file mode 100644
index 164053fff82..00000000000
--- a/app/assets/stylesheets/active_admin/components/_breadcrumbs.scss
+++ /dev/null
@@ -1,20 +0,0 @@
-.breadcrumb {
- display: block;
- font-size: 0.9em;
- font-weight: normal;
- line-height: 1.0em;
- margin-bottom: 12px;
- text-transform: uppercase;
-
- a, a:link, a:visited, a:active {
- color: #8a949e ;
- text-decoration: none;
- }
-
- a:hover { text-decoration: underline; }
-
- .breadcrumb_sep {
- margin: 0 2px;
- color: #aab2ba;
- }
-}
diff --git a/app/assets/stylesheets/active_admin/components/_buttons.scss b/app/assets/stylesheets/active_admin/components/_buttons.scss
deleted file mode 100644
index f7e416465fe..00000000000
--- a/app/assets/stylesheets/active_admin/components/_buttons.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-td, p {
- @include icon(#B3BCC1, 0.8em);
- span.icon { margin: 0 3px; }
-}
-
-a.member_link {
- margin-right: 7px;
- white-space: nowrap;
-}
-
-a.button, a:link.button, a:visited.button, input[type=submit] { @include dark-button; }
diff --git a/app/assets/stylesheets/active_admin/components/_columns.scss b/app/assets/stylesheets/active_admin/components/_columns.scss
deleted file mode 100644
index 3587763ddf0..00000000000
--- a/app/assets/stylesheets/active_admin/components/_columns.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.columns {
- margin-bottom: 10px;
-}
diff --git a/app/assets/stylesheets/active_admin/components/_comments.css.scss b/app/assets/stylesheets/active_admin/components/_comments.css.scss
deleted file mode 100644
index f8862670e30..00000000000
--- a/app/assets/stylesheets/active_admin/components/_comments.css.scss
+++ /dev/null
@@ -1,41 +0,0 @@
-// -------------------------------------- Admin Notes
-.comments {
-
- .active_admin_comment {
- clear: both;
- margin-top: 10px;
- margin-bottom: 40px;
- max-width: 700px;
-
- .active_admin_comment_meta {
- width: 130px;
- float: left;
- overflow: hidden;
- font-size: 0.9em;
- color: lighten($primary-color, 10%);
- .active_admin_comment_author {
- font-size: 1.2em;
- font-weight: bold;
- margin: 0;
- color: $primary-color;
- }
- }
- .active_admin_comment_body {
- margin-left: 150px;
- }
- }
- form.active_admin_comment {
- margin: 0;
- padding: 0;
- margin-left: 150px;
-
- fieldset.inputs {
- margin: 0;
- padding: 0;
- background: none;
- @include no-shadow;
- }
- li { padding: 0; }
- fieldset.buttons { padding: 0; margin-top: 5px;}
- }
-}
diff --git a/app/assets/stylesheets/active_admin/components/_date_picker.css.scss b/app/assets/stylesheets/active_admin/components/_date_picker.css.scss
deleted file mode 100644
index 7d47ee8cd2d..00000000000
--- a/app/assets/stylesheets/active_admin/components/_date_picker.css.scss
+++ /dev/null
@@ -1,123 +0,0 @@
-// -------------------------------------- Date Picker
-.ui-datepicker {
- background: #fff;
- -webkit-background-clip: padding-box;
- -moz-background-clip: padding-box;
- background-clip: padding-box;
- color: #fff;
- display: none;
- margin-top: 2px;
- padding: 0;
- text-align: center;
- width: 160px;
-
- background: url(active_admin_image_path('datepicker/datepicker-nipple.png')) no-repeat 0 -40px;
-
- a {
- text-decoration: none;
- &:hover {
- cursor: pointer;
- }
- }
-
- .ui-datepicker-header {
- background: url(active_admin_image_path('datepicker/datepicker-header-bg.png')) no-repeat 0px 0px;
- height: 12px;
- padding: 16px 7px 8px;
- position: relative;
- z-index: 2000;
-
- .ui-datepicker-title {
- @include text-shadow(#000);
- color: #fff;
- display: block;
- font-size: 1.1em;
- font-weight: bold;
- line-height: 0.8em;
- text-align: center;
- }
-
- a {
- color: #fff;
- display: block;
- height: 19px;
- margin-top: -4px;
- width: 20px;
-
- &.ui-datepicker-prev {
- float: left;
- background: url(active_admin_image_path('datepicker/datepicker-prev-link-icon.png')) no-repeat 2px 5px;
- }
- &.ui-datepicker-next {
- float: right;
- background: url(active_admin_image_path('datepicker/datepicker-next-link-icon.png')) no-repeat 12px 5px;
- }
- &:active {
- margin-top: -3px;
- height: 18px;
- }
-
- span {
- display: none;
- }
- }
- }
-
- table.ui-datepicker-calendar {
- @include rounded-bottom;
- @include shadow(0,1px,6px,rgba(0,0,0,0.26));
- background-color: #f4f4f4;
- border: solid 1px #63686e;
- left: 2px;
- margin-bottom: 0px;
- position: relative;
- top: -2px;
- width: 156px;
-
- td, th {
- padding: 0px;
- text-align: center;
- }
-
- thead th {
- background-color: #dbdddf;
- color: #333333;
- font-weight: normal;
- font-size: 0.8em;
- padding-top: 1px;
- }
-
- tbody {
- color: #666666;
-
- td {
- border: none;
- height: 24px;
- width: 22px;
-
- a {
- @include rounded;
- color: #666666;
- font-weight: bold;
- font-size: 0.85em;
- padding: 4px;
-
- &.ui-state-active {
- background-color: #5a5f64;
- color: #fff;
- &.ui-state-hover {
- background-color: #5a5f64;
- color: #fff;
- }
- }
- &.ui-state-hover {
- background-color: #eceef0;
- }
- &.ui-state-highlight {
- background-color: #dbdddf;
- }
- }
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/active_admin/components/_dropdown_menu.scss b/app/assets/stylesheets/active_admin/components/_dropdown_menu.scss
deleted file mode 100644
index 5cbb15c2a91..00000000000
--- a/app/assets/stylesheets/active_admin/components/_dropdown_menu.scss
+++ /dev/null
@@ -1,151 +0,0 @@
-.dropdown_menu {
- display: inline;
-
- .dropdown_menu_button {
- @include light-button;
- position: relative;
- padding-right: 22px !important;
- cursor: pointer;
-
- &:before {
- content: ' ';
- position: absolute;
- width: 0;
- height: 0;
- border-width: 3px 3px 0;
- border-style: solid;
- border-color: #FFF transparent;
- right: 12px;
- top: 45%;
- }
-
- &:after {
- content: ' ';
- position: absolute;
- width: 0;
- height: 0;
- border-width: 3px 3px 0;
- border-style: solid;
- border-color: #777 transparent;
- right: 12px;
- top: 45%;
- }
- }
-
- .dropdown_menu_nipple {
-
- // The nipple's border
- content: "";
- position: absolute;
- top: -6px;
- display: block;
- width: 0;
- height: 0;
- border-width: 0 6px 6px;
- border-style: solid;
- border-color: darken($primary-color, 4%) transparent;
- z-index: 100;
-
- // The nipple's inner shadow
-
- &:before {
- content: ' ';
- position: absolute;
- width: 0;
- height: 0;
- border-width: 0 5px 5px;
- border-style: solid;
- border-color: lighten($primary-color, 15%) transparent;
- left: -5px;
- top: 1px;
- }
-
- // The nipple's background color
-
- &:after {
- content: ' ';
- position: absolute;
- width: 0;
- height: 0;
- border-width: 0 5px 5px;
- border-style: solid;
- border-color: lighten($primary-color, 4%) transparent;
- left: -5px;
- top: 2px;
- }
- }
-
- .dropdown_menu_list_wrapper {
- display: inline-block;
- position: absolute;
- background-color: white;
- padding: 2px;
- box-shadow: rgba(0,0,0,0.4) 0 1px 3px, lighten($primary-color, 15%) 0px 1px 0px 0px inset;
- background-color: $primary-color;
- @include gradient(lighten($primary-color, 4%), darken($primary-color, 5%));
- border: solid 1px darken($primary-color, 10%);
- border-top-color: darken($primary-color, 4%);
- border-bottom-color: darken($primary-color, 17%);
-
- border-radius: 4px;
-
- .dropdown_menu_list {
- display: block;
- background-color: #FFF;
- border: solid 1px darken($primary-color, 10%);
- box-shadow: lighten($primary-color, 5%) 0px 1px 0px 0px;
- border-radius: 3px;
- margin: 0;
- overflow: hidden;
- padding: 8px;
-
- list-style-type: none;
- padding: 0;
-
- li {
- display: block;
- border-bottom: solid 1px #ebebeb;
- @include box-sizing(border-box);
-
- a {
- display: block;
- @include box-sizing(padding-box);
- font-size: 0.95em;
- font-weight: bold;
- padding: 7px 16px 5px;
- text-decoration: none;
- text-align: center;
- -webkit-font-smoothing: antialiased;
-
- &:hover {
- @include highlight-gradient;
- @include text-shadow(#5a83aa);
- color: #FFF;
- }
-
- &:active {
- @include reverse-highlight-gradient;
- color: #FFF;
- }
-
- }
-
- &:first-child {
- a {
- border-top-left-radius: 2px;
- border-top-right-radius: 2px;
- }
-
- }
-
- &:last-child {
- a {
- border-bottom-left-radius: 2px;
- border-bottom-right-radius: 2px;
- }
- border: none;
- }
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/active_admin/components/_flash_messages.css.scss b/app/assets/stylesheets/active_admin/components/_flash_messages.css.scss
deleted file mode 100644
index e1d1ea20d7d..00000000000
--- a/app/assets/stylesheets/active_admin/components/_flash_messages.css.scss
+++ /dev/null
@@ -1,39 +0,0 @@
-body.logged_in {
- .flash {
- @include gradient(#f7f1d3, #f5edc5);
- @include text-shadow(#fafafa);
- border-bottom: 1px solid #eee098;
- color: #cb9810;
- font-weight: bold;
- font-size: 1.1em;
- line-height: 1.0em;
- margin-bottom: 10px;
- padding: 13px 30px 11px;
- position: relative;
- top: -15px;
-
- &.flash_notice {
- @include gradient(#dce9dd, #ccdfcd);
- border-bottom: 1px solid #adcbaf;
- color: #416347;
- }
- &.flash_error {
- @include gradient(#f5e4e4, #f1dcdc);
- border-bottom: 1px solid #e0c2c0;
- color: #b33c33;
- }
- }
-}
-
-body.logged_out {
- .flash {
- @include no-shadow;
- @include text-shadow(#fff);
- background: none;
- color: #666;
- font-weight: bold;
- line-height: 1.0em;
- margin-bottom: 10px;
- padding: 0;
- }
-}
diff --git a/app/assets/stylesheets/active_admin/components/_grid.scss b/app/assets/stylesheets/active_admin/components/_grid.scss
deleted file mode 100644
index 7a8efbc551b..00000000000
--- a/app/assets/stylesheets/active_admin/components/_grid.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-// -------------------------------------- Index as Grid
-table.index_grid td { border: none; background: none; padding: 0 20px 20px 0; margin: 0;}
-
-// -------------------------------------- Columns
-.columns {
- clear: both;
- padding: 0;
- .column { float: left; }
-}
diff --git a/app/assets/stylesheets/active_admin/components/_index_list.scss b/app/assets/stylesheets/active_admin/components/_index_list.scss
deleted file mode 100644
index c81cc6998a5..00000000000
--- a/app/assets/stylesheets/active_admin/components/_index_list.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-.indexes {
- float: right;
-
- li {
- .count {
- color: #8e979e;
- font-weight: normal;
- font-size: 0.9em;
- line-height: 10px;
- }
- }
-}
diff --git a/app/assets/stylesheets/active_admin/components/_links.scss b/app/assets/stylesheets/active_admin/components/_links.scss
deleted file mode 100644
index 0e6a62c2d24..00000000000
--- a/app/assets/stylesheets/active_admin/components/_links.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-a, a:link, a:visited {
- color: $link-color;
- text-decoration: underline;
-}
-a:hover { text-decoration: none; }
diff --git a/app/assets/stylesheets/active_admin/components/_pagination.scss b/app/assets/stylesheets/active_admin/components/_pagination.scss
deleted file mode 100644
index f3fede99ef8..00000000000
--- a/app/assets/stylesheets/active_admin/components/_pagination.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-.paginated_collection_contents {
- clear: both;
-}
-
-.pagination {
- float: right;
- font-size: 0.9em;
- margin-left: 10px;
-
- a {
- @include light-button;
- }
-
- span.page.current {
- @include default-button;
- }
-
- a, span.page.current {
- @include rounded(0px);
- margin-right: 4px;
- padding: 2px 5px;
- }
-}
-
-.pagination_information {
- float: right;
- margin-bottom: 5px;
- color: #b3bcc1;
- b { color: #5c6469; }
-}
-
-.download_links {
- float: left;
-}
diff --git a/app/assets/stylesheets/active_admin/components/_panels.scss b/app/assets/stylesheets/active_admin/components/_panels.scss
deleted file mode 100644
index dc28c04a829..00000000000
--- a/app/assets/stylesheets/active_admin/components/_panels.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-// ----------------------------------- Helper class to apply to elements to make them sections
-.section, .panel{ @include section; }
-
-// ----------------------------------- Sidebar Sections
-
-.sidebar_section { @include section; }
diff --git a/app/assets/stylesheets/active_admin/components/_popovers.css.scss b/app/assets/stylesheets/active_admin/components/_popovers.css.scss
deleted file mode 100644
index 290a4b5a26a..00000000000
--- a/app/assets/stylesheets/active_admin/components/_popovers.css.scss
+++ /dev/null
@@ -1,123 +0,0 @@
-.popover {
- display: inline-block;
- position: absolute;
- background-color: white;
- padding: 2px;
- box-shadow: rgba(0,0,0,0.4) 0 1px 3px, lighten($primary-color, 15%) 0px 1px 0px 0px inset;
- background-color: $primary-color;
- @include gradient(lighten($primary-color, 4%), darken($primary-color, 5%));
- border: solid 1px darken($primary-color, 10%);
- border-top-color: darken($primary-color, 4%);
- border-bottom-color: darken($primary-color, 17%);
-
- border-radius: 4px;
-
- .popover_nipple{
-
- // The nipple's border
- content: "";
- position: absolute;
- top: -6px;
- display: block;
- width: 0;
- height: 0;
- border-width: 0 6px 6px;
- border-style: solid;
- border-color: darken($primary-color, 4%) transparent;
- z-index: 100;
-
- // The nipple's inner shadow
-
- &:before {
- content: ' ';
- position: absolute;
- width: 0;
- height: 0;
- border-width: 0 5px 5px;
- border-style: solid;
- border-color: lighten($primary-color, 15%) transparent;
- left: -5px;
- top: 1px;
- }
-
- // The nipple's background color
-
- &:after {
- content: ' ';
- position: absolute;
- width: 0;
- height: 0;
- border-width: 0 5px 5px;
- border-style: solid;
- border-color: lighten($primary-color, 4%) transparent;
- left: -5px;
- top: 2px;
- }
- }
-
- .popover_contents {
- display: block;
- background-color: #FFF;
- border: solid 1px darken($primary-color, 10%);
- box-shadow: lighten($primary-color, 5%) 0px 1px 0px 0px;
- border-radius: 3px;
- margin: 0;
- overflow: hidden;
- padding: 8px;
- }
-
-}
-
-//-------------------------------------- Popover with action list
-
-.popover {
- ul.popover_contents {
- list-style-type: none;
- padding: 0;
-
- li {
- display: block;
- border-bottom: solid 1px #ebebeb;
- @include box-sizing(border-box);
-
- a {
- display: block;
- @include box-sizing(padding-box);
- font-size: 0.95em;
- font-weight: bold;
- padding: 7px 16px 5px;
- text-decoration: none;
- text-align: center;
- -webkit-font-smoothing: antialiased;
-
- &:hover {
- @include highlight-gradient;
- @include text-shadow(#5a83aa);
- color: #FFF;
- }
-
- &:active {
- @include reverse-highlight-gradient;
- color: #FFF;
- }
-
- }
-
- &:first-child {
- a {
- border-top-left-radius: 2px;
- border-top-right-radius: 2px;
- }
-
- }
-
- &:last-child {
- a {
- border-bottom-left-radius: 2px;
- border-bottom-right-radius: 2px;
- }
- border: none;
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/active_admin/components/_scopes.scss b/app/assets/stylesheets/active_admin/components/_scopes.scss
deleted file mode 100644
index 1164fc2f612..00000000000
--- a/app/assets/stylesheets/active_admin/components/_scopes.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-.scopes {
- li {
- .count {
- color: #8e979e;
- font-weight: normal;
- font-size: 0.9em;
- line-height: 10px;
- }
- }
-}
diff --git a/app/assets/stylesheets/active_admin/components/_status_tags.scss b/app/assets/stylesheets/active_admin/components/_status_tags.scss
deleted file mode 100644
index bd82c3a20c1..00000000000
--- a/app/assets/stylesheets/active_admin/components/_status_tags.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-.status_tag {
- background: darken($secondary-color, 15%);
- color: #fff;
- text-transform: uppercase;
- letter-spacing: 0.15em;
- padding: 3px 5px 2px 5px;
- font-size: 0.8em;
-
- &.ok, &.published, &.complete, &.completed, &.green { background: #8daa92; }
- &.warn, &.warning, &.orange { background: #e29b20; }
- &.error, &.errored, &.red { background: #d45f53; }
-}
diff --git a/app/assets/stylesheets/active_admin/components/_table_tools.css.scss b/app/assets/stylesheets/active_admin/components/_table_tools.css.scss
deleted file mode 100644
index 0e6e017a505..00000000000
--- a/app/assets/stylesheets/active_admin/components/_table_tools.css.scss
+++ /dev/null
@@ -1,95 +0,0 @@
-.table_tools {
- display: block;
- margin-bottom: 16px;
- width: 100%;
- height: 20px;
-}
-
-.table_tools {
- .table_tools_button + .table_tools_button {
- margin-left: 6px;
- }
-
- .table_tools_button + .table_tools_segmented_control {
- margin-left: 6px;
- }
-
- .table_tools_segmented_control + .table_tools_button {
- margin-left: 6px;
- }
-
-}
-
-
-a.table_tools_button, .table_tools .dropdown_menu_button {
- @include light-button;
- @include gradient(#FFFFFF, #F0F0F0);
- @include border-colors(#d9d9d9, #d0d0d0, #c5c5c5);
- font-size: 0.9em;
- text-decoration: none;
- padding: 4px 14px 4px;
- position: relative;
- -webkit-transition: all 0.08s ease-in;
-
- &:not(.disabled) {
- &:hover {
- @include gradient(#FFFFFF, #F6F6F6);
- }
-
- &:active {
- @include border-colors(#d7d7d7, #c8c8c8, #c3c3c3);
- box-shadow: 0 1px 1px 0 rgba(0,0,0,0.17) inset;
- @include gradient(#FFFFFF, #E8E8E8);
- }
- }
-}
-
-.table_tools_segmented_control {
- display: inline-block;
- list-style-type: none;
- margin: 0;
- padding: 0;
-
- li {
- display: inline-block;
- list-style-image: none;
- list-style-type: none;
- margin-left: -7px;
- white-space: nowrap;
-
- a {
- border-radius: 0px;
- }
-
- &:first-child {
- margin-left: 0;
-
- a {
- border-top-left-radius: 12px;
- border-bottom-left-radius: 12px;
- }
- }
-
- &:last-child {
- a {
- border-top-right-radius: 12px;
- border-bottom-right-radius: 12px;
- }
- }
-
- &.selected {
-
- a {
- @include gradient(#F0F0F0, #FDFDFD);
- box-shadow: 0 1px 1px 0 rgba(0,0,0,0.1) inset;
- cursor: default;
-
- &:hover {
- @include gradient(#F0F0F0, #FDFDFD);
- }
- }
- }
-
- }
-
-}
diff --git a/app/assets/stylesheets/active_admin/components/_tables.css.scss b/app/assets/stylesheets/active_admin/components/_tables.css.scss
deleted file mode 100644
index eed959b37da..00000000000
--- a/app/assets/stylesheets/active_admin/components/_tables.css.scss
+++ /dev/null
@@ -1,108 +0,0 @@
-// ----------------------------------- Tables
-
-table tr {
- td {
- vertical-align: top;
- }
-}
-
-// --------- Index Tables
-
-table.index_table {
- width: 100%;
- margin-bottom: 10px;
- border: 0;
- border-spacing: 0;
-
- th {
- @include section-header;
- border-right: none;
- text-align: left;
- padding-left: $cell-horizontal-padding;
- padding-right: $cell-horizontal-padding;
-
- a, a:link, a:visited {
- color: $section-header-text-color;
- text-decoration: none;
- display: block;
- white-space: nowrap;
- -webkit-font-smoothing: antialiased;
- }
-
- &.sortable a {
- background: url(active_admin_image_path('orderable.png')) no-repeat 0 4px; padding-left: 13px;
- }
-
- &.sorted-asc a { background-position: 0 -27px; }
- &.sorted-desc a { background-position: 0 -56px;}
-
- &.sorted-asc, &.sorted-desc {
- @include gradient(darken($secondary-gradient-start, 5%), darken($secondary-gradient-stop, 5%));
- }
-
- &:last-child {
- border-right: solid 1px #d4d4d4;
- }
-
- }
-
- tr.even td { background: $table-stripe-color; }
-
- tr.selected td {
- background: $table-selected-color;
- }
-
- td {
- padding: 10px $cell-horizontal-padding 8px $cell-horizontal-padding;
- border-bottom: 1px solid #e8e8e8;
- vertical-align: top;
- }
-}
-
-// --------- Tables inside Panels
-
-.panel_contents table {
- margin-top: 5px;
- th {
- padding-top: 10px;
- background: none;
- color: $primary-color;
- @include no-shadow;
- @include text-shadow;
- text-transform: uppercase;
- border-bottom: 1px solid #ccc;
- }
- tr.odd td { background: darken($table-stripe-color, 3%); }
- tr.even td { background: $table-stripe-color; }
-}
-
-// -------------------------------------- Resource Attributes Table
-.attributes_table { overflow: hidden; }
-
-.attributes_table table {
- th, td {
- padding: 8px $cell-horizontal-padding 6px $cell-horizontal-padding;
- vertical-align: top;
- border-bottom: 1px solid #e8e8e8;
- }
- th {
- @include no-shadow;
- @include no-gradient;
- width: 150px;
- font-size: 0.9em;
- padding-left: 0;
- text-transform: uppercase;
- color: $primary-color;
- @include text-shadow;
- }
- td {
- .empty {
- color: #bbb;
- font-size: 0.8em;
- text-transform: uppercase;
- letter-spacing: 0.2em;
- }
- }
-}
-
-.sidebar_section .attributes_table th { width: 50px; }
diff --git a/app/assets/stylesheets/active_admin/mixins/_all.css.scss b/app/assets/stylesheets/active_admin/mixins/_all.css.scss
deleted file mode 100644
index 20b64e78314..00000000000
--- a/app/assets/stylesheets/active_admin/mixins/_all.css.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-@import "active_admin/mixins/variables";
-@import "active_admin/mixins/reset";
-@import "active_admin/mixins/gradients";
-@import "active_admin/mixins/shadows";
-@import "active_admin/mixins/icons";
-@import "active_admin/mixins/rounded";
-@import "active_admin/mixins/buttons";
-@import "active_admin/mixins/sections";
-@import "active_admin/mixins/utilities";
-@import "active_admin/mixins/typography";
-@import "bourbon";
diff --git a/app/assets/stylesheets/active_admin/mixins/_buttons.css.scss b/app/assets/stylesheets/active_admin/mixins/_buttons.css.scss
deleted file mode 100644
index dc5625781ab..00000000000
--- a/app/assets/stylesheets/active_admin/mixins/_buttons.css.scss
+++ /dev/null
@@ -1,66 +0,0 @@
-@mixin basic-button {
- @include rounded(200px);
- display: inline-block;
- font-weight: bold;
- font-size: 1.0em;
- @include sans-family;
- line-height: 12px;
- margin-right: 3px;
- padding: 7px 16px 6px;
- text-decoration: none;
- -webkit-font-smoothing: antialiased;
-
- &.disabled {
- opacity: 0.5;
- cursor: default;
- }
-
-}
-
-@mixin default-button {
- @include basic-button;
- @include gradient(lighten($primary-color, 15%), darken($primary-color, 12%));
- @include text-shadow(#000);
- box-shadow: 0 1px 1px rgba(0,0,0,0.10), 0 1px 0 0px rgba(255,255,255, 0.2) inset;
- border: solid 1px #484e53;
- @include border-colors(#616a71, #484e53, #363b3f);
- color: #efefef;
-
- &:not(.disabled) {
- &:hover{
- @include gradient(lighten($primary-color, 18%), darken($primary-color, 9%));
- }
-
- &:active {
- box-shadow: 0 1px 3px rgba(0,0,0,0.40) inset, 0 1px 0 0px #FFF;
- @include gradient(lighten($primary-color, 8%), darken($primary-color, 17%));
- }
- }
-}
-
-@mixin light-button {
- @include basic-button;
- @include gradient(#FFFFFF, #E7E7E7);
- box-shadow: 0 1px 1px rgba(0,0,0,0.10), 0 1px 0 0 rgba(255,255,255, 0.8) inset;
- border: solid 1px #c7c7c7;
- @include border-colors(#d3d3d3, #c7c7c7, #c2c2c2);
- @include text-shadow;
- color: $primary-color;
-
- &:not(.disabled) {
- &:hover {
- @include gradient(#FFFFFF, #F1F1F1);
- }
-
- &:active {
- box-shadow: 0 1px 2px rgba(0,0,0,0.22) inset, 0 1px 0 0px #EEE;
- @include border-colors(#c2c2c2, #b9b9b9, #b7b7b7);
- @include gradient(#F3F3F3, #D8D8D8);
- }
- }
-
-}
-
-@mixin dark-button {
- @include default-button;
-}
diff --git a/app/assets/stylesheets/active_admin/mixins/_gradients.css.scss b/app/assets/stylesheets/active_admin/mixins/_gradients.css.scss
deleted file mode 100644
index 5a10d959d9a..00000000000
--- a/app/assets/stylesheets/active_admin/mixins/_gradients.css.scss
+++ /dev/null
@@ -1,38 +0,0 @@
-$secondary-gradient-start: #efefef !default;
-$secondary-gradient-stop: #dfe1e2 !default;
-
-@mixin gradient($start, $end){
- background: $start;
- background: -webkit-linear-gradient(-90deg, $start, $end);
- background: -moz-linear-gradient(-90deg, $start, $end);
- background: linear-gradient(-90deg, $start, $end);
- // IE 6 & 7
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start)}', endColorstr='#{ie-hex-str($end)}');
- // IE 8
- -ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start)}', endColorstr='#{ie-hex-str($end)}');
-}
-
-@mixin primary-gradient {
- @include gradient(lighten($primary-color, 5%), darken($primary-color, 7%));
- border-bottom: 1px solid darken($primary-color, 11%);
-}
-
-@mixin secondary-gradient {
- @include gradient($secondary-gradient-start, $secondary-gradient-stop);
-}
-
-@mixin highlight-gradient {
- @include gradient(#75a1c2, #608cb4);
-}
-
-@mixin reverse-highlight-gradient {
- @include gradient(#608cb4, #75a1c2);
-}
-
-@mixin no-gradient {
- background: none;
- // IE 6 & 7
- filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
- // IE 8
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient(enabled=false)";
-}
diff --git a/app/assets/stylesheets/active_admin/mixins/_icons.css.scss b/app/assets/stylesheets/active_admin/mixins/_icons.css.scss
deleted file mode 100644
index 105486295c0..00000000000
--- a/app/assets/stylesheets/active_admin/mixins/_icons.css.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-span.icon { vertical-align: middle; display: inline-block; }
-span.icon svg { vertical-align: baseline; }
-
-
-@mixin icon-color ($color) {
- span.icon svg {
- path, polygon, rect, circle { fill: $color !important; }
- }
-}
-
-@mixin icon-size ($size) {
- span.icon { width: $size; height: $size; }
- span.icon svg { width: $size; height: $size; }
-}
-
-@mixin icon($color, $size) {
- @include icon-color($color);
- @include icon-size($size);
-}
-
-@include icon-size(0.8em);
diff --git a/app/assets/stylesheets/active_admin/mixins/_reset.css.scss b/app/assets/stylesheets/active_admin/mixins/_reset.css.scss
deleted file mode 100644
index 7b89b89b278..00000000000
--- a/app/assets/stylesheets/active_admin/mixins/_reset.css.scss
+++ /dev/null
@@ -1,165 +0,0 @@
-// FROM The Compass Framework (compass-style.org)
-//
-// Copyright (c) 2009 Christopher M. Eppstein
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of
-// this software and associated documentation files (the "Software"), to deal in
-// the Software without restriction, including without limitation the rights to
-// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-// the Software, and to permit persons to whom the Software is furnished to do so,
-// subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software. No attribution is required by
-// products that make use of this software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-// Except as contained in this notice, the name(s) of the above copyright holders
-// shall not be used in advertising or otherwise to promote the sale, use or other
-// dealings in this Software without prior written authorization.
-//
-// Contributors to this project agree to grant all rights to the copyright holder
-// of the primary product. Attribution is maintained in the source control history
-// of the product.
-//
-// Based on [Eric Meyer's reset](http://meyerweb.com/eric/thoughts/2007/05/01/reset-reloaded/)
-// Global reset rules.
-// For more specific resets, use the reset mixins provided below
-//
-// *Please Note*: tables still need `cellspacing="0"` in the markup.
-@mixin global-reset {
- html, body, div, span, applet, object, iframe,
- h1, h2, h3, h4, h5, h6, p, blockquote, pre,
- a, abbr, acronym, address, big, cite, code,
- del, dfn, em, font, img, ins, kbd, q, s, samp,
- small, strike, strong, sub, sup, tt, var,
- dl, dt, dd, ol, ul, li,
- fieldset, form, label, legend,
- table, caption, tbody, tfoot, thead, tr, th, td {
- @include reset-box-model;
- @include reset-font; }
- body {
- @include reset-body; }
- ol, ul {
- @include reset-list-style; }
- table {
- @include reset-table; }
- caption, th, td {
- @include reset-table-cell; }
- q, blockquote {
- @include reset-quotation; }
- a img {
- @include reset-image-anchor-border; } }
-
-// Reset all elements within some selector scope. To reset the selector itself,
-// mixin the appropriate reset mixin for that element type as well. This could be
-// useful if you want to style a part of your page in a dramatically different way.
-//
-// *Please Note*: tables still need `cellspacing="0"` in the markup.
-@mixin nested-reset {
- div, span, object, iframe, h1, h2, h3, h4, h5, h6, p,
- pre, a, abbr, acronym, address, code, del, dfn, em, img,
- dl, dt, dd, ol, ul, li, fieldset, form, label, legend, caption, tbody, tfoot, thead, tr {
- @include reset-box-model;
- @include reset-font; }
- table {
- @include reset-table; }
- caption, th, td {
- @include reset-table-cell; }
- q, blockquote {
- @include reset-quotation; }
- a img {
- @include reset-image-anchor-border; } }
-
-// Reset the box model measurements.
-@mixin reset-box-model {
- margin: 0;
- padding: 0;
- border: 0;
- outline: 0; }
-
-// Reset the font and vertical alignment.
-@mixin reset-font {
- font: {
- weight: inherit;
- style: inherit;
- size: 100%;
- family: inherit; };
- vertical-align: baseline; }
-
-// Resets the outline when focus.
-// For accessibility you need to apply some styling in its place.
-@mixin reset-focus {
- outline: 0; }
-
-// Reset a body element.
-@mixin reset-body {
- line-height: 1;
- color: black;
- background: white; }
-
-// Reset the list style of an element.
-@mixin reset-list-style {
- list-style: none; }
-
-// Reset a table
-@mixin reset-table {
- border-collapse: separate;
- border-spacing: 0;
- vertical-align: middle; }
-
-// Reset a table cell (`th`, `td`)
-@mixin reset-table-cell {
- text-align: left;
- font-weight: normal;
- vertical-align: middle; }
-
-// Reset a quotation (`q`, `blockquote`)
-@mixin reset-quotation {
- quotes: "" "";
- &:before, &:after {
- content: ""; } }
-
-// Resets the border.
-@mixin reset-image-anchor-border {
- border: none; }
-
-// Unrecognized elements are displayed inline.
-// This reset provides a basic reset for html5 elements
-// so they are rendered correctly in browsers that don't recognize them
-// and reset in browsers that have default styles for them.
-@mixin reset-html5 {
- article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary {
- @include reset-box-model;
- display: block; } }
-
-// Resets the display of inline and block elements to their default display
-// according to their tag type. Elements that have a default display that varies across
-// versions of html or browser are not handled here, but this covers the 90% use case.
-// Usage Example:
-//
-// // Turn off the display for both of these classes
-// .unregistered-only, .registered-only
-// display: none
-// // Now turn only one of them back on depending on some other context.
-// body.registered
-// +reset-display(".registered-only")
-// body.unregistered
-// +reset-display(".unregistered-only")
-@mixin reset-display($selector: "", $important: false) {
- #{append-selector(elements-of-type("inline"), $selector)} {
- @if $important {
- display: inline !important; }
- @else {
- display: inline; } }
- #{append-selector(elements-of-type("block"), $selector)} {
- @if $important {
- display: block !important; }
- @else {
- display: block; } } }
diff --git a/app/assets/stylesheets/active_admin/mixins/_rounded.css.scss b/app/assets/stylesheets/active_admin/mixins/_rounded.css.scss
deleted file mode 100644
index 32f90689b2d..00000000000
--- a/app/assets/stylesheets/active_admin/mixins/_rounded.css.scss
+++ /dev/null
@@ -1,43 +0,0 @@
-@mixin rounded($radius: 3px) {
- -webkit-border-radius: $radius;
- -moz-border-radius: $radius;
- border-radius: $radius;
-}
-
-@mixin rounded-all($top-left:3px, $top-right:3px, $bottom-right:3px, $bottom-left:3px) {
- border-top-right-radius: $top-right;
- -moz-border-radius-topright: $top-right;
- -webkit-border-top-right-radius: $top-right;
-
- border-top-left-radius: $top-left;
- -moz-border-radius-topleft: $top-left;
- -webkit-border-top-left-radius: $top-left;
-
- border-bottom-right-radius: $bottom-right;
- -moz-border-radius-bottomright: $bottom-right;
- -webkit-border-bottom-right-radius: $bottom-right;
-
- border-bottom-left-radius: $bottom-left;
- -moz-border-radius-bottomleft: $bottom-left;
- -webkit-border-bottom-left-radius: $bottom-left;
-}
-
-@mixin rounded-top($radius: 3px) {
- @include rounded(0);
- border-top-right-radius: $radius;
- border-top-left-radius: $radius;
- -moz-border-radius-topright: $radius;
- -moz-border-radius-topleft: $radius;
- -webkit-border-top-right-radius: $radius;
- -webkit-border-top-left-radius: $radius;
-}
-
-@mixin rounded-bottom($radius: 3px) {
- @include rounded(0);
- border-bottom-right-radius: $radius;
- border-bottom-left-radius: $radius;
- -moz-border-radius-bottomright: $radius;
- -moz-border-radius-bottomleft: $radius;
- -webkit-border-bottom-right-radius: $radius;
- -webkit-border-bottom-left-radius: $radius;
-}
diff --git a/app/assets/stylesheets/active_admin/mixins/_sections.css.scss b/app/assets/stylesheets/active_admin/mixins/_sections.css.scss
deleted file mode 100644
index 435d0fbe8a6..00000000000
--- a/app/assets/stylesheets/active_admin/mixins/_sections.css.scss
+++ /dev/null
@@ -1,36 +0,0 @@
-@mixin section-header {
- @include secondary-gradient;
- @include text-shadow;
- border: solid 1px #cdcdcd;
- @include border-colors(#e6e6e6, #d4d4d4, #cdcdcd);
- box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 0 1px #FFF inset;
-
- font-size: 1.0em;
- font-weight: bold;
- line-height: 18px;
- margin-bottom: 0.5em;
- color: $section-header-text-color;
- @include icon($section-header-text-color, 1.0em);
-
- padding: 5px 10px 3px 10px;
-}
-
-@mixin section-background {
- background: #f4f4f4;
- @include rounded(4px);
- @include inset-shadow(0,1px,4px, #ddd);
-}
-
-@mixin section {
- @include section-background;
- margin-bottom: 20px;
-
- > h3 { @include section-header; }
-
- > div { padding: 3px $section-padding $section-padding $section-padding; }
-
- hr {
- border: none;
- border-bottom: 1px solid #E8E8E8;
- }
-}
diff --git a/app/assets/stylesheets/active_admin/mixins/_shadows.css.scss b/app/assets/stylesheets/active_admin/mixins/_shadows.css.scss
deleted file mode 100644
index 3b5699eaae7..00000000000
--- a/app/assets/stylesheets/active_admin/mixins/_shadows.css.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-@mixin shadow($x: 0, $y: 1px, $blur: 2px, $color: rgba(0,0,0,0.37)) {
- box-shadow: $x $y $blur $color;
- -moz-box-shadow: $x $y $blur $color;
- -webkit-box-shadow: $x $y $blur $color;
-}
-
-@mixin no-shadow {
- box-shadow: none;
- -moz-box-shadow: none;
- -webkit-box-shadow: none;
-}
-
-@mixin inset-shadow($x: 0, $y: 1px, $blur: 2px, $color: #aaa) {
- box-shadow: inset $x $y $blur $color;
- -moz-box-shadow: inset $x $y $blur $color;
- -webkit-box-shadow: inset $x $y $blur $color;
-}
-
-@mixin text-shadow($color: #fff, $x: 0, $y: 1px, $blur: 0) {
- text-shadow: $color $x $y $blur;
-}
-
diff --git a/app/assets/stylesheets/active_admin/mixins/_typography.scss b/app/assets/stylesheets/active_admin/mixins/_typography.scss
deleted file mode 100644
index ac3bbff0b67..00000000000
--- a/app/assets/stylesheets/active_admin/mixins/_typography.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-@mixin sans-family {
- font-family: Helvetica, Arial, sans-serif;
-}
diff --git a/app/assets/stylesheets/active_admin/mixins/_utilities.scss b/app/assets/stylesheets/active_admin/mixins/_utilities.scss
deleted file mode 100644
index 0d0742feca8..00000000000
--- a/app/assets/stylesheets/active_admin/mixins/_utilities.scss
+++ /dev/null
@@ -1,26 +0,0 @@
-@mixin clearfix {
- &:after {
- visibility: hidden;
- display: block;
- content: "";
- clear: both;
- height: 0;
- }
-}
-
-@mixin box-sizing($value: border-box) {
- -moz-box-sizing: $value;
- -webkit-box-sizing: $value;
- -o-box-sizing: $value;
- -ms-box-sizing: $value;
- box-sizing: $value;
-}
-
-
-@mixin border-colors($top, $sides, $bottom) {
- border-color: $sides;
- border-top-color: $top;
- border-right-color: $sides;
- border-bottom-color: $bottom;
- border-left-color: $sides;
-}
diff --git a/app/assets/stylesheets/active_admin/mixins/_variables.css.scss b/app/assets/stylesheets/active_admin/mixins/_variables.css.scss
deleted file mode 100644
index b35f66c7a8e..00000000000
--- a/app/assets/stylesheets/active_admin/mixins/_variables.css.scss
+++ /dev/null
@@ -1,24 +0,0 @@
-// Variables used throughout Active Admin.
-// They can be overridden by prepending your own
-// to 'app/assets/stylesheets/active_admin.css.scss'.
-
-// Colors
-$primary-color: #5E6469 !default;
-$secondary-color: #f0f0f0 !default;
-$text-color: #323537 !default;
-$link-color: #38678b !default;
-$section-header-text-color: $primary-color !default;
-$current-menu-item-background: lighten($primary-color, 12%) !default;
-$hover-menu-item-background: lighten($primary-color, 12%) !default;
-$table-stripe-color: lighten($primary-color, 57%) !default;
-$table-selected-color: #d9e4ec !default;
-$error-color: #932419 !default;
-
-// Sizes
-$horizontal-page-margin: 30px !default;
-$sidebar-width: 270px !default;
-$cell-padding: 5px 10px 3px 10px !default;
-$cell-horizontal-padding: 12px !default;
-$section-padding: 15px !default;
-$text-input-horizontal-padding: 10px !default;
-
diff --git a/app/assets/stylesheets/active_admin/pages/_logged_out.scss b/app/assets/stylesheets/active_admin/pages/_logged_out.scss
deleted file mode 100644
index cb8c3b58673..00000000000
--- a/app/assets/stylesheets/active_admin/pages/_logged_out.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-body.logged_out {
- background: #e8e9ea;
-
- #content_wrapper{
- width: 500px;
- margin: 70px auto;
- #active_admin_content {
- @include shadow;
- background: #fff;
- padding: 13px 30px;
- }
- }
-
- h2 {
- @include section-header;
- @include primary-gradient;
- @include text-shadow(#000);
- box-shadow: 0 1px 3px rgba(0,0,0,0.3);
- border: none;
- color: #fff;
- margin: -13px -30px 20px -30px;
- }
-
- #login {
- /* Login Form */
- form {
- fieldset {
- @include no-shadow;
- background: none;
- padding: 0;
- li { padding: 10px 0; }
-
- input[type=text], input[type=email], input[type=password] {
- width: 70%;
- }
- &.buttons { margin-left: 20%; }
- margin-bottom: 0;
- }
- }
-
- a { float: right; margin-top: -32px; }
- }
-
-}
diff --git a/app/assets/stylesheets/active_admin/print.css.scss b/app/assets/stylesheets/active_admin/print.css.scss
deleted file mode 100644
index 3908089c69e..00000000000
--- a/app/assets/stylesheets/active_admin/print.css.scss
+++ /dev/null
@@ -1,284 +0,0 @@
-/* Active Admin Print Stylesheet */
-
-// Set colors used elsewhere
-$primary-color: black;
-$text-color: black;
-
-// Reset
-@import "active_admin/mixins/reset";
-@include global-reset;
-
-// Partials
-@import "active_admin/typography";
-
-body {
- font-family: Helvetica, Arial, sans-serif;
- line-height: 150%;
- font-size: 72%;
- background: #fff;
- width: 99%;
- margin: 0;
- padding: .5%;
- color: $text-color;
-}
-
-a {
- color: $text-color;
- text-decoration: none;
-}
-
-h3 {
- font-weight: bold;
- margin-bottom: .5em;
-}
-
-// Header
-#header {
- float: left;
-
- #tabs, .tabs, #utility_nav {
- display: none;
- }
-
- h1{
- font-weight: bold;
- }
-}
-
-.flashes {
- display: none;
-}
-
-#title_bar {
- float: right;
-
- h2 {
- line-height: 2em;
- margin: 0;
- }
-
- .breadcrumb, #titlebar_right {
- display: none;
- }
-}
-
-// Content
-#active_admin_content {
- border-top: thick solid black;
- clear: both;
- margin-top: 2em;
- padding-top: 3em;
-}
-
-// Footer
-#footer {
- display: none;
-}
-
-// Tables
-.table_tools {
- ul {
- padding: 0;
- margin: 0;
- list-style-type: none;
-
- li {
- display: none;
- padding: 0;
- margin-bottom: 1em;
-
- &.scope.selected, &.index.selected {
- display: block;
-
- &:before {
- content: "Showing ";
- }
-
- a {
- font-weight: bold;
- }
-
- span {
- display: inline-block;
- font-weight: normal;
- font-size: .9em;
- }
- }
- }
-
- }
-}
-
-table {
- margin-bottom: 1.5em;
- text-align: left;
- width: 100%;
-
- thead {
- display: table-header-group;
-
- th {
- background: none;
- border-bottom: medium solid black;
- font-weight: bold;
-
- a{
- text-decoration: none;
- }
- }
- }
-
- th, td {
- padding: .5em 1em;
-
- .member_link {
- display: none;
- }
- }
-
- td {
- border-bottom: thin solid black;
- }
-
- tr{
- page-break-inside: avoid;
- }
-}
-
-// Index
-#index_footer, .pagination_information {
- display: none;
-}
-
-.index_grid {
- td {
- border: none;
- text-align: center;
- vertical-align: middle;
-
- img {
- max-width: 1in;
- }
- }
-}
-
-// Show
-.panel {
- border-bottom: thick solid #ccc;
- margin-bottom: 3em;
- padding-bottom: 2em;
- page-break-inside: avoid;
-
- &:last-child {
- border-bottom: none;
- }
-}
-
-.comments {
- form {
- display: none;
- }
-
- .active_admin_comment {
- border-top: thin solid black;
- padding-top: 1em;
-
- .active_admin_comment_meta {
- h4 {
- font-size: 1em;
- font-weight: bold;
- float: left;
- margin-right: .5em;
- margin-bottom: 0;
- }
-
- span {
- font-size: .9em;
- font-style: italic;
- vertical-align: top;
- }
- }
-
- .active_admin_comment_body {
- clear: both;
- margin-bottom: 1em;
- }
- }
-}
-
-
-// Attribute Tables
-.attributes_table {
- border-top: medium solid black;
-
- th {
- border-bottom: thin solid black;
- vertical-align: top;
-
- &:after {
- content: ':';
- }
- }
-
- td {
- img {
- max-height: 4in;
- max-width: 6in;
- }
- }
-}
-
-// Sidebars
-#filters_sidebar_section {
- display: none;
-}
-
-// Forms
-form {
- fieldset {
- border-top: thick solid #ccc;
- padding-top: 2em;
- margin-bottom: 2em;
-
- &:last-child {
- border-bottom: none;
- }
- }
-
- .buttons, abbr {
- display: none;
- }
- ol {
- list-style-type: none;
- padding: 0;
- margin: 0;
-
- li{
- border-top: thin solid black;
- margin: 0;
- padding: 1em 0;
- overflow: hidden;
-
- &.password, &.hidden {
- display: none;
- }
-
- label {
- font-weight: bold;
- float: left;
- width: 20%;
- }
-
- input, textarea, select {
- background: none;
- border: 0;
- font: Arial, Helvetica, sans-serif;
- }
-
- input[type=file] {
- display: none;
- }
-
- }
- }
-}
diff --git a/app/assets/stylesheets/active_admin/structure/_footer.scss b/app/assets/stylesheets/active_admin/structure/_footer.scss
deleted file mode 100644
index f387d207e1d..00000000000
--- a/app/assets/stylesheets/active_admin/structure/_footer.scss
+++ /dev/null
@@ -1,14 +0,0 @@
-#footer {
- padding: 30px 30px;
- font-size: 0.8em;
- clear: both;
-
- p {
- padding-top: 10px
- }
-}
-
-// -------------------------------------- Index Footer (Under Table)
-#index_footer { padding-top: 5px; text-align: right; font-size: 0.85em; }
-
-.index_content { clear: both; }
diff --git a/app/assets/stylesheets/active_admin/structure/_main_structure.scss b/app/assets/stylesheets/active_admin/structure/_main_structure.scss
deleted file mode 100644
index d54c8ce0893..00000000000
--- a/app/assets/stylesheets/active_admin/structure/_main_structure.scss
+++ /dev/null
@@ -1,26 +0,0 @@
-#wrapper {
- display: table;
- width: 100%;
-}
-
-#active_admin_content {
- margin: 0;
- padding: 25px $horizontal-page-margin;
-
- #main_content_wrapper {
- float: left;
- width: 100%;
-
- #main_content{
- margin-right: 300px;
- }
- }
-
- &.without_sidebar #main_content_wrapper #main_content{ margin-right: 0; }
-
- #sidebar {
- float: left;
- width: $sidebar-width;
- margin-left: -$sidebar-width;
- }
-}
diff --git a/app/assets/stylesheets/active_admin/structure/_title_bar.scss b/app/assets/stylesheets/active_admin/structure/_title_bar.scss
deleted file mode 100644
index 2c4d1d945e1..00000000000
--- a/app/assets/stylesheets/active_admin/structure/_title_bar.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-#title_bar {
- @include section-header;
- @include clearfix;
- @include box-sizing(border-box);
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.37);
- display: table;
- border-bottom-color: #EEE;
- width: 100%;
- position: relative;
- margin: 0;
- margin-bottom: 15px;
- padding: 10px $horizontal-page-margin;
- z-index: 800;
-
- #titlebar_left, #titlebar_right {
- height: 50px;
- vertical-align: middle;
- display: table-cell;
- }
-
- #titlebar_right {
- text-align: right;
- }
-
- h2 {
- margin: 0;
- padding: 0;
- font-size: 2.6em;
- font-weight: bold;
- }
-
- .action_items {
- a {
- @include light-button;
- padding: 12px 17px 10px;
- span.icon { vertical-align: bottom; margin-right: 4px;}
- margin: 0px;
-
- &:hover{
- @include icon-color(#000);
- }
- }
- }
-}
diff --git a/lib/active_admin/base_controller.rb b/app/controllers/active_admin/base_controller.rb
similarity index 50%
rename from lib/active_admin/base_controller.rb
rename to app/controllers/active_admin/base_controller.rb
index 8eb56cc087b..c2dd73ffaa0 100644
--- a/lib/active_admin/base_controller.rb
+++ b/app/controllers/active_admin/base_controller.rb
@@ -1,18 +1,20 @@
-require 'inherited_resources'
-
-require 'active_admin/base_controller/authorization'
-require 'active_admin/base_controller/menu'
-
+# frozen_string_literal: true
module ActiveAdmin
# BaseController for ActiveAdmin.
# It implements ActiveAdmin controllers core features.
class BaseController < ::InheritedResources::Base
- helper ::ActiveAdmin::ViewHelpers
+ helper MethodOrProcHelper
+ helper LayoutHelper
+ helper FormHelper
+ helper BreadcrumbHelper
+ helper AutoLinkHelper
+ helper DisplayHelper
+ helper IndexHelper
- layout :determine_active_admin_layout
+ layout "active_admin"
- before_filter :only_render_implemented_actions
- before_filter :authenticate_active_admin_user
+ before_action :only_render_implemented_actions
+ before_action :authenticate_active_admin_user
class << self
# Ensure that this method is available for the DSL
@@ -23,18 +25,18 @@ class << self
attr_accessor :active_admin_config
end
- # By default Rails will render un-implemented actions when the view exists. Becuase Active
+ include BaseController::Authorization
+ include BaseController::Menu
+
+ private
+
+ # By default Rails will render un-implemented actions when the view exists. Because Active
# Admin allows you to not render any of the actions by using the #actions method, we need
# to check if they are implemented.
def only_render_implemented_actions
raise AbstractController::ActionNotFound unless action_methods.include?(params[:action])
end
- include Menu
- include Authorization
-
- private
-
# Calls the authentication method as defined in ActiveAdmin.authentication_method
def authenticate_active_admin_user
send(active_admin_namespace.authentication_method) if active_admin_namespace.authentication_method
@@ -46,7 +48,7 @@ def current_active_admin_user
helper_method :current_active_admin_user
def current_active_admin_user?
- !current_active_admin_user.nil?
+ !!current_active_admin_user
end
helper_method :current_active_admin_user?
@@ -60,19 +62,49 @@ def active_admin_namespace
end
helper_method :active_admin_namespace
-
ACTIVE_ADMIN_ACTIONS = [:index, :show, :new, :create, :edit, :update, :destroy]
- # Determine which layout to use.
- #
- # 1. If we're rendering a standard Active Admin action, we want layout(false)
- # because these actions are subclasses of the Base page (which implements
- # all the required layout code)
- # 2. If we're rendering a custom action, we'll use the active_admin layout so
- # that users can render any template inside Active Admin.
- def determine_active_admin_layout
- ACTIVE_ADMIN_ACTIONS.include?(params[:action].to_sym) ? false : 'active_admin'
+ def active_admin_root
+ controller, action = active_admin_namespace.root_to.split "#"
+ { controller: controller, action: action }
+ end
+
+ def page_presenter
+ active_admin_config.get_page_presenter(params[:action].to_sym) || default_page_presenter
+ end
+ helper_method :page_presenter
+
+ def default_page_presenter
+ PagePresenter.new
+ end
+
+ def page_title
+ if page_presenter[:title]
+ helpers.render_or_call_method_or_proc_on(self, page_presenter[:title])
+ else
+ default_page_title
+ end
+ end
+ helper_method :page_title
+
+ def default_page_title
+ active_admin_config.name
+ end
+
+ DEFAULT_DOWNLOAD_FORMATS = [:csv, :xml, :json]
+
+ def build_download_formats(download_links)
+ download_links = instance_exec(&download_links) if download_links.is_a?(Proc)
+ if download_links.is_a?(Array) && !download_links.empty?
+ download_links
+ elsif download_links == false
+ []
+ else
+ DEFAULT_DOWNLOAD_FORMATS
+ end
end
+ helper_method :build_download_formats
+ ActiveSupport.run_load_hooks(:active_admin_controller, self)
end
end
diff --git a/app/controllers/active_admin/base_controller/authorization.rb b/app/controllers/active_admin/base_controller/authorization.rb
new file mode 100644
index 00000000000..f4c036f8f39
--- /dev/null
+++ b/app/controllers/active_admin/base_controller/authorization.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ class BaseController < ::InheritedResources::Base
+ module Authorization
+ extend ActiveSupport::Concern
+
+ ACTIONS_DICTIONARY = {
+ index: ActiveAdmin::Authorization::READ,
+ show: ActiveAdmin::Authorization::READ,
+ new: ActiveAdmin::Authorization::NEW,
+ create: ActiveAdmin::Authorization::CREATE,
+ edit: ActiveAdmin::Authorization::EDIT,
+ update: ActiveAdmin::Authorization::UPDATE,
+ destroy: ActiveAdmin::Authorization::DESTROY
+ }
+
+ included do
+ rescue_from ActiveAdmin::AccessDenied, with: :dispatch_active_admin_access_denied
+
+ helper_method :authorized?
+ helper_method :authorize!
+ helper_method :active_admin_authorization
+ end
+
+ protected
+
+ # Authorize the action and subject. Available in the controller
+ # as well as all the views.
+ #
+ # @param [Symbol] action The action to check if the user has permission
+ # to perform on the subject.
+ #
+ # @param [any] subject The subject that the user is trying to perform
+ # the action on.
+ #
+ # @return [Boolean]
+ #
+ def authorized?(action, subject = nil)
+ active_admin_authorization.authorized?(action, subject)
+ end
+
+ # Authorize the action and subject. Available in the controller
+ # as well as all the views. If the action is not allowed, it raises
+ # an ActiveAdmin::AccessDenied exception.
+ #
+ # @param [Symbol] action The action to check if the user has permission
+ # to perform on the subject.
+ #
+ # @param [any] subject The subject that the user is trying to perform
+ # the action on.
+ #
+ # @return [Boolean] True if authorized, otherwise raises
+ # an ActiveAdmin::AccessDenied.
+ def authorize!(action, subject = nil)
+ unless authorized? action, subject
+ raise ActiveAdmin::AccessDenied.new(
+ current_active_admin_user,
+ action,
+ subject)
+ end
+ end
+
+ # Performs authorization on the resource using the current controller
+ # action as the permission action.
+ #
+ def authorize_resource!(resource)
+ permission = action_to_permission(params[:action])
+ authorize! permission, resource
+ end
+
+ # Retrieve or instantiate the authorization instance for this resource
+ #
+ # @return [ActiveAdmin::AuthorizationAdapter]
+ def active_admin_authorization
+ @active_admin_authorization ||=
+ active_admin_authorization_adapter.new active_admin_config, current_active_admin_user
+ end
+
+ # Returns the class to be used as the authorization adapter
+ #
+ # @return [Class]
+ def active_admin_authorization_adapter
+ adapter = active_admin_namespace.authorization_adapter
+ if adapter.is_a? String
+ adapter.constantize
+ else
+ adapter
+ end
+ end
+
+ # Converts a controller action into one of the correct Active Admin
+ # authorization names. Uses the ACTIONS_DICTIONARY to convert the
+ # action name to permission.
+ #
+ # @param [String, Symbol] action The controller action name.
+ #
+ # @return [Symbol] The permission name to use.
+ def action_to_permission(action)
+ if action && action = action.to_sym
+ Authorization::ACTIONS_DICTIONARY[action] || action
+ end
+ end
+
+ def dispatch_active_admin_access_denied(exception)
+ instance_exec(self, exception, &active_admin_namespace.on_unauthorized_access.to_proc)
+ end
+
+ def rescue_active_admin_access_denied(exception)
+ error = exception.message
+
+ respond_to do |format|
+ format.html do
+ flash[:error] = error
+ redirect_backwards_or_to_root
+ end
+
+ format.csv { render body: error, status: :unauthorized }
+ format.json { render json: { error: error }, status: :unauthorized }
+ format.xml { render xml: "#{error}", status: :unauthorized }
+ end
+ end
+
+ def redirect_backwards_or_to_root
+ redirect_back fallback_location: active_admin_root
+ end
+
+ end
+ end
+end
diff --git a/app/controllers/active_admin/base_controller/menu.rb b/app/controllers/active_admin/base_controller/menu.rb
new file mode 100644
index 00000000000..afb9e8306b2
--- /dev/null
+++ b/app/controllers/active_admin/base_controller/menu.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ class BaseController < ::InheritedResources::Base
+ module Menu
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :set_current_menu_item
+
+ helper_method :current_menu
+ helper_method :current_menu_item?
+ end
+
+ protected
+
+ def current_menu
+ active_admin_config.navigation_menu
+ end
+
+ def current_menu_item?(item, children: true)
+ item.current?(@current_menu_item, children: children)
+ end
+
+ def set_current_menu_item
+ @current_menu_item = if current_menu && active_admin_config.belongs_to? && parent?
+ parent_item = active_admin_config.belongs_to_config.target.menu_item
+ if current_menu.include? parent_item
+ parent_item
+ else
+ active_admin_config.menu_item
+ end
+ else
+ active_admin_config.menu_item
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/active_admin/page_controller.rb b/app/controllers/active_admin/page_controller.rb
similarity index 74%
rename from lib/active_admin/page_controller.rb
rename to app/controllers/active_admin/page_controller.rb
index 4048d313838..e9979e53b43 100644
--- a/lib/active_admin/page_controller.rb
+++ b/app/controllers/active_admin/page_controller.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
# All Pages controllers inherit from this controller.
@@ -8,16 +9,12 @@ class PageController < BaseController
actions :index
- before_filter :authorize_access!
+ before_action :authorize_access!
- def index(options={}, &block)
+ def index(options = {}, &block)
render "active_admin/page/index"
end
- def clear_page_actions!
- active_admin_config.clear_page_actions!
- end
-
private
def authorize_access!
diff --git a/app/controllers/active_admin/resource_controller.rb b/app/controllers/active_admin/resource_controller.rb
new file mode 100644
index 00000000000..bedd3483f1c
--- /dev/null
+++ b/app/controllers/active_admin/resource_controller.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+require "active_admin/collection_decorator"
+
+module ActiveAdmin
+ # All Resources Controller inherits from this controller.
+ # It implements actions and helpers for resources.
+ class ResourceController < BaseController
+ respond_to :html, :xml, :json
+ respond_to :csv, only: :index
+
+ before_action :restrict_download_format_access!, only: [:index, :show]
+
+ include ResourceController::ActionBuilder
+ include ResourceController::Decorators
+ include ResourceController::DataAccess
+ include ResourceController::PolymorphicRoutes
+ include ResourceController::Scoping
+ include ResourceController::Streaming
+ extend ResourceClassMethods
+
+ def self.active_admin_config=(config)
+ if @active_admin_config = config
+ defaults resource_class: config.resource_class,
+ route_prefix: config.route_prefix,
+ instance_name: config.resource_name.singular
+ end
+ end
+
+ # Inherited Resources uses the `self.inherited(base)` hook to add
+ # in `self.resource_class`. To override it, we need to install
+ # our resource_class method each time we're inherited from.
+ def self.inherited(base)
+ super(base)
+ base.override_resource_class_methods!
+ end
+
+ private
+
+ def page_presenter
+ case params[:action].to_sym
+ when :index
+ active_admin_config.get_page_presenter(params[:action], params[:as])
+ when :new, :edit, :create, :update
+ active_admin_config.get_page_presenter(:form)
+ end || super
+ end
+
+ def default_page_presenter
+ case params[:action].to_sym
+ when :index
+ PagePresenter.new(as: :table)
+ when :new, :edit
+ PagePresenter.new
+ end || super
+ end
+
+ def page_title
+ if page_presenter[:title]
+ case params[:action].to_sym
+ when :index
+ case page_presenter[:title]
+ when Symbol, Proc
+ instance_exec(&page_presenter[:title])
+ else
+ page_presenter[:title]
+ end
+ else
+ helpers.render_or_call_method_or_proc_on(resource, page_presenter[:title])
+ end
+ else
+ default_page_title
+ end
+ end
+
+ def default_page_title
+ case params[:action].to_sym
+ when :index
+ active_admin_config.plural_resource_label
+ when :show
+ helpers.display_name(resource)
+ when :new, :edit, :create, :update
+ normalized_action = params[:action]
+ normalized_action = 'new' if normalized_action == 'create'
+ normalized_action = 'edit' if normalized_action == 'update'
+
+ ActiveAdmin::Localizers.resource(active_admin_config).t("#{normalized_action}_model")
+ else
+ I18n.t("active_admin.#{params[:action]}", default: params[:action].to_s.titleize)
+ end
+ end
+
+ def restrict_download_format_access!
+ unless request.format.html?
+ presenter = active_admin_config.get_page_presenter(:index)
+ download_formats = (presenter || {}).fetch(:download_links, active_admin_config.namespace.download_links)
+ unless build_download_formats(download_formats).include?(request.format.symbol)
+ raise ActiveAdmin::AccessDenied.new(current_active_admin_user, :index)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/resource_controller/action_builder.rb b/app/controllers/active_admin/resource_controller/action_builder.rb
similarity index 53%
rename from lib/active_admin/resource_controller/action_builder.rb
rename to app/controllers/active_admin/resource_controller/action_builder.rb
index fa0954dad4c..bc85be26849 100644
--- a/lib/active_admin/resource_controller/action_builder.rb
+++ b/app/controllers/active_admin/resource_controller/action_builder.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
class ResourceController < BaseController
@@ -7,12 +8,22 @@ module ActionBuilder
module ClassMethods
def clear_member_actions!
+ remove_action_methods(:member)
active_admin_config.clear_member_actions!
end
def clear_collection_actions!
+ remove_action_methods(:collection)
active_admin_config.clear_collection_actions!
end
+
+ private
+
+ def remove_action_methods(actions_type)
+ active_admin_config.public_send(:"#{actions_type}_actions").each do |action|
+ remove_method action.name
+ end
+ end
end
end
diff --git a/app/controllers/active_admin/resource_controller/data_access.rb b/app/controllers/active_admin/resource_controller/data_access.rb
new file mode 100644
index 00000000000..cd543223345
--- /dev/null
+++ b/app/controllers/active_admin/resource_controller/data_access.rb
@@ -0,0 +1,374 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ class ResourceController < BaseController
+
+ # This module overrides most of the data access methods in Inherited
+ # Resources to provide Active Admin with it's data.
+ #
+ # The module also deals with authorization and resource callbacks.
+ #
+ module DataAccess
+
+ def self.included(base)
+ base.class_exec do
+ include Callbacks
+ include ScopeChain
+
+ define_active_admin_callbacks :build, :create, :update, :save, :destroy
+
+ helper_method :current_scope
+ end
+ end
+
+ protected
+
+ COLLECTION_APPLIES = [
+ :authorization_scope,
+ :filtering,
+ :scoping,
+ :sorting,
+ :includes,
+ :pagination,
+ :collection_decorator
+ ].freeze
+
+ # Retrieve, memoize and authorize the current collection from the db. This
+ # method delegates the finding of the collection to #find_collection.
+ #
+ # Once #collection has been called, the collection is available using
+ # either the @collection instance variable or an instance variable named
+ # after the resource that the collection is for. eg: Post => @post.
+ #
+ # @return [ActiveRecord::Relation] The collection for the index
+ def collection
+ get_collection_ivar || begin
+ collection = find_collection
+ authorize! Authorization::READ, active_admin_config.resource_class
+ set_collection_ivar collection
+ end
+ end
+
+ # Does the actual work of retrieving the current collection from the db.
+ # This is a great method to override if you would like to perform
+ # some additional db # work before your controller returns and
+ # authorizes the collection.
+ #
+ # @return [ActiveRecord::Relation] The collection for the index
+ def find_collection(options = {})
+ collection = scoped_collection
+ collection_applies(options).each do |applyer|
+ collection = send(:"apply_#{applyer}", collection)
+ end
+ collection
+ end
+
+ # Override this method in your controllers to modify the start point
+ # of our searches and index.
+ #
+ # This method should return an ActiveRecord::Relation object so that
+ # the searching and filtering can be applied on top
+ #
+ # Note, unless you are doing something special, you should use the
+ # scope_to method from the Scoping module instead of overriding this
+ # method.
+ def scoped_collection
+ end_of_association_chain
+ end
+
+ # `super` is `resource_collection_name`, which honours `defaults collection_name:`. An
+ # `as:` alias makes it the alias's plural, which usually names no association on the
+ # parent, so the model's plural is derived instead. The chain parent is asked rather
+ # than the registration, because only it knows which of the two it actually has.
+ def method_for_association_chain
+ return super unless active_admin_config&.belongs_to?
+ return super if chain_parent_association?(resource_collection_name)
+
+ active_admin_config.resource_class.model_name.element.pluralize.to_sym
+ end
+
+ # Ask the actual chain parent (`scope_to` can make it something other than the
+ # `belongs_to` target) and require the association to target the resource's model,
+ # because an alias's plural can coincide with an unrelated association's name.
+ def chain_parent_association?(name)
+ reflection = association_chain.last.class.try(:reflect_on_association, name)
+ reflection.present? && reflection.klass <= active_admin_config.resource_class
+ rescue NameError
+ false
+ end
+
+ # Retrieve, memoize and authorize a resource based on params[:id]. The
+ # actual work of finding the resource is done in #find_resource.
+ #
+ # This method is used on all the member actions:
+ #
+ # * show
+ # * edit
+ # * update
+ # * destroy
+ #
+ # @return [ActiveRecord::Base] An active record object
+ def resource
+ get_resource_ivar || begin
+ resource = find_resource
+ resource = apply_decorations(resource)
+ authorize_resource! resource
+
+ set_resource_ivar resource
+ end
+ end
+
+ # Does the actual work of finding a resource in the database. This
+ # method uses the finder method as defined in InheritedResources.
+ #
+ # @return [ActiveRecord::Base] An active record object.
+ def find_resource
+ scoped_collection.send method_for_find, params[:id]
+ end
+
+ # Builds, memoize and authorize a new instance of the resource. The
+ # actual work of building the new instance is delegated to the
+ # #build_new_resource method.
+ #
+ # This method is used to instantiate and authorize new resources in the
+ # new and create controller actions.
+ #
+ # @return [ActiveRecord::Base] An un-saved active record base object
+ def build_resource
+ get_resource_ivar || begin
+ resource = build_new_resource
+ resource = apply_decorations(resource)
+ resource = assign_attributes(resource, resource_params)
+ run_build_callbacks resource
+ authorize_resource! resource
+
+ set_resource_ivar resource
+ end
+ end
+
+ # Builds a new resource. This method uses the method_for_build provided
+ # by Inherited Resources.
+ #
+ # @return [ActiveRecord::Base] An un-saved active record base object
+ def build_new_resource
+ apply_authorization_scope(scoped_collection).send(
+ method_for_build,
+ *resource_params.map { |params| params.slice(active_admin_config.resource_class.inheritance_column) }
+ )
+ end
+
+ # Calls all the appropriate callbacks and then creates the new resource.
+ #
+ # @param [ActiveRecord::Base] object The new resource to create
+ #
+ # @return [void]
+ def create_resource(object)
+ run_create_callbacks object do
+ save_resource(object)
+ end
+ end
+
+ # Calls all the appropriate callbacks and then saves the new resource.
+ #
+ # @param [ActiveRecord::Base] object The new resource to save
+ #
+ # @return [void]
+ def save_resource(object)
+ run_save_callbacks object do
+ object.save
+ end
+ end
+
+ # Update an object with the given attributes. Also calls the appropriate
+ # callbacks for update action.
+ #
+ # @param [ActiveRecord::Base] object The instance to update
+ #
+ # @param [Array] attributes An array with the attributes in the first position
+ # and the Active Record "role" in the second. The role
+ # may be set to nil.
+ #
+ # @return [void]
+ def update_resource(object, attributes)
+ status = nil
+ ActiveRecord::Base.transaction do
+ object = assign_attributes(object, attributes)
+
+ run_update_callbacks object do
+ status = save_resource(object)
+ raise ActiveRecord::Rollback unless status
+ end
+ end
+ status
+ end
+
+ # Destroys an object from the database and calls appropriate callbacks.
+ #
+ # @return [void]
+ def destroy_resource(object)
+ run_destroy_callbacks object do
+ object.destroy
+ end
+ end
+
+ #
+ # Collection Helper Methods
+ #
+
+ # Gives the authorization library a change to pre-scope the collection.
+ #
+ # In the case of the CanCan adapter, it calls `#accessible_by` on
+ # the collection.
+ #
+ # @param [ActiveRecord::Relation] collection The collection to scope
+ #
+ # @return [ActiveRecord::Relation] a scoped collection of query
+ def apply_authorization_scope(collection)
+ action_name = action_to_permission(params[:action])
+ active_admin_authorization.scope_collection(collection, action_name)
+ end
+
+ def apply_sorting(chain)
+ params[:order] ||= active_admin_config.sort_order
+ order_clause = active_admin_config.order_clause.new(active_admin_config, params[:order])
+
+ if order_clause.valid?
+ order_clause.apply(chain)
+ else
+ chain # just return the chain
+ end
+ end
+
+ # Applies any Ransack search methods to the currently scoped collection.
+ # Both `search` and `ransack` are provided, but we use `ransack` to prevent conflicts.
+ def apply_filtering(chain)
+ @search = chain.ransack(params[:q] || {}, auth_object: active_admin_authorization)
+ @search.result
+ end
+
+ def apply_scoping(chain)
+ @collection_before_scope = chain
+
+ if current_scope
+ scope_chain(current_scope, chain)
+ else
+ chain
+ end
+ end
+
+ def apply_includes(chain)
+ if active_admin_config.includes.any?
+ chain.includes(*active_admin_config.includes)
+ else
+ chain
+ end
+ end
+
+ def collection_before_scope
+ @collection_before_scope
+ end
+
+ def current_scope
+ @current_scope ||= if params[:scope]
+ active_admin_config.get_scope_by_id(params[:scope])
+ else
+ active_admin_config.default_scope(self)
+ end
+ end
+
+ def apply_pagination(chain)
+ # skip pagination if CSV format was requested
+ return chain if params["format"] == "csv"
+ # skip pagination if already was paginated by scope
+ return chain if chain.respond_to?(:total_pages)
+ page = params[Kaminari.config.param_name]
+
+ paginate(chain, page, per_page)
+ end
+
+ def collection_applies(options = {})
+ only = Array(options.fetch(:only, COLLECTION_APPLIES))
+ except = Array(options.fetch(:except, []))
+
+ COLLECTION_APPLIES & only - except
+ end
+
+ def in_paginated_batches(&block)
+ ActiveRecord::Base.uncached do
+ (1..paginated_collection.total_pages).each do |page|
+ paginated_collection(page).each do |resource|
+ yield apply_decorator(resource)
+ end
+ end
+ end
+ end
+
+ def per_page
+ if active_admin_config.paginate
+ dynamic_per_page || configured_per_page
+ else
+ active_admin_config.max_per_page
+ end
+ end
+
+ def dynamic_per_page
+ params[:per_page] || @per_page
+ end
+
+ def configured_per_page
+ Array(active_admin_config.per_page).first
+ end
+
+ # @param resource [ActiveRecord::Base]
+ # @param attributes [Array {
+ klass = self.class
+ name = if klass.respond_to?(:model_name)
+ if klass.respond_to?(:primary_key)
+ "#{klass.model_name.human} ##{send(klass.primary_key)}"
+ else
+ klass.model_name.human
+ end
+ elsif klass.respond_to?(:primary_key)
+ " ##{send(klass.primary_key)}"
+ end
+ name.present? ? name : to_s
+ }
+
+ def DISPLAY_NAME_FALLBACK.inspect
+ "DISPLAY_NAME_FALLBACK"
+ end
+
+ # Attempts to call any known display name methods on the resource.
+ # See the setting in `application.rb` for the list of methods and their priority.
+ def display_name(resource)
+ unless resource.nil?
+ result = render_in_context(resource, display_name_method_for(resource))
+ if result.to_s.strip.present?
+ ERB::Util.html_escape(result)
+ else
+ ERB::Util.html_escape(render_in_context(resource, DISPLAY_NAME_FALLBACK))
+ end
+ end
+ end
+
+ def format_attribute(resource, attr)
+ value = find_value resource, attr
+
+ if value.is_a?(Arbre::Element)
+ value
+ elsif boolean_attr?(resource, attr, value)
+ Arbre::Context.new { status_tag value }
+ else
+ pretty_format value
+ end
+ end
+
+ # Attempts to create a human-readable string for any object
+ def pretty_format(object)
+ case object
+ when String, Numeric, Symbol, Arbre::Element
+ object.to_s
+ when Date, Time
+ I18n.localize object, format: active_admin_application.localize_format
+ when Array
+ format_collection(object)
+ else
+ if defined?(::ActiveRecord) && object.is_a?(ActiveRecord::Base) ||
+ defined?(::Mongoid) && object.class.include?(Mongoid::Document)
+ auto_link object
+ elsif defined?(::ActiveRecord) && object.is_a?(ActiveRecord::Relation)
+ format_collection(object)
+ else
+ display_name object
+ end
+ end
+ end
+
+ private
+
+ # Looks up and caches the first available display name method.
+ # To prevent conflicts, we exclude any methods that happen to be associations.
+ # If no methods are available and we're about to use the Kernel's `to_s`, provide our own.
+ def display_name_method_for(resource)
+ @@display_name_methods_cache ||= {}
+ @@display_name_methods_cache[resource.class] ||= begin
+ methods = active_admin_application.display_name_methods - association_methods_for(resource)
+ method = methods.detect { |method| resource.respond_to? method }
+
+ if method != :to_s || resource.method(method).source_location
+ method
+ else
+ DISPLAY_NAME_FALLBACK
+ end
+ end
+ end
+
+ def association_methods_for(resource)
+ return [] unless resource.class.respond_to? :reflect_on_all_associations
+ resource.class.reflect_on_all_associations.map(&:name)
+ end
+
+ def find_value(resource, attr)
+ if attr.is_a? Proc
+ attr.call resource
+ elsif resource.respond_to? attr
+ resource.public_send attr
+ elsif resource.respond_to? :[]
+ resource[attr]
+ end
+ end
+
+ def format_collection(collection)
+ safe_join(collection.map { |item| pretty_format(item) }, ", ")
+ end
+
+ def boolean_attr?(resource, attr, value)
+ case value
+ when TrueClass, FalseClass
+ true
+ else
+ if resource.class.respond_to? :attribute_types
+ resource.class.attribute_types[attr.to_s].is_a?(ActiveModel::Type::Boolean)
+ end
+ end
+ end
+ end
+end
diff --git a/app/helpers/active_admin/form_helper.rb b/app/helpers/active_admin/form_helper.rb
new file mode 100644
index 00000000000..e9a0b391cc0
--- /dev/null
+++ b/app/helpers/active_admin/form_helper.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module FormHelper
+ RESERVED_PARAMS = %w(controller action commit utf8).freeze
+
+ def active_admin_form_for(resource, options = {}, &block)
+ Arbre::Context.new({}, self) do
+ active_admin_form_for resource, options, &block
+ end.content
+ end
+
+ def hidden_field_tags_for(params, options = {})
+ fields_for_params(params.to_unsafe_hash, options).map do |kv|
+ k, v = kv.first
+ hidden_field_tag k, v, id: sanitize_to_id("hidden_active_admin_#{k}")
+ end.join("\n").html_safe
+ end
+
+ # Flatten a params Hash to an array of fields.
+ #
+ # @param params [Hash]
+ # @param options [Hash] :namespace and :except
+ #
+ # @return [Array] of [Hash] with one element.
+ #
+ # @example
+ # fields_for_params(scope: "all", users: ["greg"])
+ # => [ {"scope" => "all"} , {"users[]" => "greg"} ]
+ #
+ def fields_for_params(params, options = {})
+ namespace = options[:namespace]
+ except = Array.wrap(options[:except]).map(&:to_s)
+
+ params.flat_map do |k, v|
+ next if namespace.nil? && RESERVED_PARAMS.include?(k.to_s)
+ next if except.include?(k.to_s)
+
+ if namespace
+ k = "#{namespace}[#{k}]"
+ end
+
+ case v
+ when String, TrueClass, FalseClass
+ { k => v }
+ when Symbol
+ { k => v.to_s }
+ when Hash
+ fields_for_params(v, namespace: k)
+ when Array
+ v.map do |v|
+ { "#{k}[]" => v }
+ end
+ when nil
+ { k => "" }
+ else
+ raise TypeError, "Cannot convert #{v.class} value: #{v.inspect}"
+ end
+ end.compact
+ end
+
+ # Helper method to render a filter form
+ def active_admin_filters_form_for(search, filters, options = {})
+ defaults = { builder: ActiveAdmin::Filters::FormBuilder, url: collection_path, html: { class: "filters-form" } }
+ required = { html: { method: :get }, as: :q }
+ options = defaults.deep_merge(options).deep_merge(required)
+
+ form_for search, options do |f|
+ f.template.concat hidden_field_tags_for(params, except: except_hidden_fields)
+
+ filters.each do |attribute, opts|
+ next if opts.key?(:if) && !call_method_or_proc_on(self, opts[:if])
+ next if opts.key?(:unless) && call_method_or_proc_on(self, opts[:unless])
+
+ filter_opts = opts.except(:if, :unless)
+ filter_opts[:input_html] = instance_exec(&filter_opts[:input_html]) if filter_opts[:input_html].is_a?(Proc)
+
+ f.filter attribute, filter_opts
+ end
+
+ buttons = content_tag :div, class: "filters-form-buttons" do
+ f.submit(I18n.t("active_admin.filters.buttons.filter"), class: "filters-form-submit") +
+ link_to(I18n.t("active_admin.filters.buttons.clear"), collection_path, class: "filters-form-clear")
+ end
+
+ f.template.concat buttons
+ end
+ end
+
+ private
+
+ def except_hidden_fields
+ [:q, :page]
+ end
+ end
+end
diff --git a/app/helpers/active_admin/index_helper.rb b/app/helpers/active_admin/index_helper.rb
new file mode 100644
index 00000000000..013201d37a0
--- /dev/null
+++ b/app/helpers/active_admin/index_helper.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module IndexHelper
+ def scope_name(scope)
+ case scope.name
+ when Proc then
+ self.instance_exec(&scope.name).to_s
+ else
+ scope.name.to_s
+ end
+ end
+
+ def batch_actions_to_display
+ @batch_actions_to_display ||= begin
+ if active_admin_config && active_admin_config.batch_actions.any?
+ active_admin_config.batch_actions.select do |batch_action|
+ call_method_or_proc_on(self, batch_action.display_if_block)
+ end
+ else
+ []
+ end
+ end
+ end
+
+ # 1. removes `select` and `order` to prevent invalid SQL
+ # 2. correctly handles the Hash returned when `group by` is used
+ def collection_size(c = collection)
+ return c.count if c.is_a?(Array)
+ return c.length if c.limit_value
+
+ c = c.except :select, :order
+
+ c.group_values.present? ? c.count.count : c.count
+ end
+
+ def collection_empty?(c = collection)
+ collection_size(c) == 0
+ end
+ end
+end
diff --git a/app/helpers/active_admin/layout_helper.rb b/app/helpers/active_admin/layout_helper.rb
new file mode 100644
index 00000000000..0a82a466194
--- /dev/null
+++ b/app/helpers/active_admin/layout_helper.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module LayoutHelper
+ # Returns the current Active Admin application instance
+ def active_admin_application
+ ActiveAdmin.application
+ end
+
+ def set_page_title(title)
+ @page_title = title
+ end
+
+ def site_title
+ # Prioritize namespace and account for Devise views where namespace is not available
+ namespace = active_admin_namespace if respond_to?(:active_admin_namespace)
+ (namespace || active_admin_application).site_title(self)
+ end
+
+ def html_head_site_title(separator: "-")
+ safe_join([@page_title || page_title, site_title], " #{separator} ")
+ end
+
+ def action_items_for_action
+ @action_items_for_action ||= begin
+ if active_admin_config&.action_items?
+ active_admin_config.action_items_for(params[:action], self)
+ else
+ []
+ end
+ end
+ end
+
+ def sidebar_sections_for_action
+ @sidebar_sections_for_action ||= begin
+ if active_admin_config&.sidebar_sections?
+ active_admin_config.sidebar_sections_for(params[:action], self)
+ else
+ []
+ end
+ end
+ end
+
+ def skip_sidebar!
+ @skip_sidebar = true
+ end
+
+ def skip_sidebar?
+ @skip_sidebar == true
+ end
+
+ def flash_messages
+ @flash_messages ||= flash.to_hash.except(*active_admin_application.flash_keys_to_except)
+ end
+
+ def url_for_comments(*args)
+ parts = []
+ parts << active_admin_namespace.name unless active_admin_namespace.root?
+ parts << active_admin_namespace.comments_registration_name.underscore
+ parts << "path"
+ send parts.join("_"), *args
+ end
+ end
+end
diff --git a/app/javascript/active_admin.js b/app/javascript/active_admin.js
new file mode 100644
index 00000000000..fcaaf86ec2b
--- /dev/null
+++ b/app/javascript/active_admin.js
@@ -0,0 +1,10 @@
+import "flowbite"
+import Rails from "@rails/ujs"
+import "active_admin/features/batch_actions"
+import "active_admin/features/dark_mode_toggle"
+import "active_admin/features/has_many"
+import "active_admin/features/filters"
+import "active_admin/features/main_menu"
+import "active_admin/features/per_page"
+
+Rails.start()
diff --git a/app/javascript/active_admin/features/batch_actions.js b/app/javascript/active_admin/features/batch_actions.js
new file mode 100644
index 00000000000..a9bf3ecd408
--- /dev/null
+++ b/app/javascript/active_admin/features/batch_actions.js
@@ -0,0 +1,95 @@
+import Rails from '@rails/ujs';
+
+const submitForm = function() {
+ let form = document.getElementById("collection_selection")
+ if (form) {
+ form.submit()
+ }
+}
+
+const batchActionClick = function(event) {
+ event.preventDefault()
+ let batchAction = document.getElementById("batch_action")
+ if (batchAction) {
+ batchAction.value = this.dataset.action
+ }
+
+ if (!event.target.dataset.confirm && !event.target.dataset.modalTarget) { submitForm() }
+}
+
+const batchActionConfirmComplete = function(event) {
+ event.preventDefault()
+ if (event.detail[0] === true) {
+ let batchAction = document.getElementById("batch_action")
+ if (batchAction) {
+ batchAction.value = this.dataset.action
+ }
+ submitForm()
+ }
+}
+
+const batchActionFormSubmit = function(event) {
+ event.preventDefault();
+ let json = JSON.stringify(Object.fromEntries(new FormData(this).entries()));
+ let inputsField = document.getElementById('batch_action_inputs')
+ let form = document.getElementById("collection_selection")
+ if (json && inputsField && form) {
+ inputsField.value = json
+ form.submit()
+ }
+}
+
+Rails.delegate(document, "[data-batch-action-item]", "confirm:complete", batchActionConfirmComplete)
+Rails.delegate(document, "[data-batch-action-item]", "click", batchActionClick)
+Rails.delegate(document, "form[data-batch-action-form]", "submit", batchActionFormSubmit)
+
+const disableDropdown = function(condition) {
+ const button = document.querySelector(".batch-actions-dropdown-toggle")
+ if (button) {
+ button.disabled = condition
+ }
+}
+
+const toggleAllChange = function(event) {
+ const checkboxes = document.querySelectorAll(".batch-actions-resource-selection")
+ for (const checkbox of checkboxes) {
+ checkbox.checked = this.checked
+ }
+
+ const rows = document.querySelectorAll(".paginated-collection tbody tr")
+ for (const row of rows) {
+ row.classList.toggle("selected", this.checked);
+ }
+
+ disableDropdown(!this.checked)
+}
+
+Rails.delegate(document, ".batch-actions-toggle-all", "change", toggleAllChange)
+
+const toggleCheckboxChange = function(event) {
+ const numChecked = document.querySelectorAll(".batch-actions-resource-selection:checked").length;
+ const allChecked = numChecked === document.querySelectorAll(".batch-actions-resource-selection").length;
+ const someChecked = (numChecked > 0) && (numChecked < document.querySelectorAll(".batch-actions-resource-selection").length);
+
+ const toggleAll = document.querySelector(".batch-actions-toggle-all")
+ if (toggleAll) {
+ toggleAll.checked = allChecked
+ toggleAll.indeterminate = someChecked
+ }
+
+ disableDropdown(numChecked === 0)
+}
+
+Rails.delegate(document, ".batch-actions-resource-selection", "change", toggleCheckboxChange)
+
+const tableRowClick = function(event) {
+ const type = event.target.type;
+ if (typeof type === "undefined" || (type !== "checkbox" && type !== "button" && type !== "")) {
+ const checkbox = event.target.closest("tr").querySelector("input[type=checkbox]")
+ if (checkbox) {
+ checkbox.click()
+ }
+ }
+}
+
+Rails.delegate(document, ".paginated-collection tbody td", "click", tableRowClick)
diff --git a/app/javascript/active_admin/features/dark_mode_toggle.js b/app/javascript/active_admin/features/dark_mode_toggle.js
new file mode 100644
index 00000000000..417f36177b3
--- /dev/null
+++ b/app/javascript/active_admin/features/dark_mode_toggle.js
@@ -0,0 +1,37 @@
+import Rails from '@rails/ujs';
+
+const THEME_KEY = "theme";
+const darkModeMedia = window.matchMedia('(prefers-color-scheme: dark)');
+
+const setTheme = () => {
+ // On page load or when changing themes, best to add inline in `head` to avoid FOUC
+ if (localStorage.getItem(THEME_KEY) === 'dark' || (!(THEME_KEY in localStorage) && darkModeMedia.matches)) {
+ document.documentElement.classList.add('dark');
+ } else {
+ document.documentElement.classList.remove('dark');
+ }
+}
+
+// Detect when user changes their system level preference to set theme.
+darkModeMedia.addEventListener("change", setTheme);
+
+// When the page loads, set theme. By default, uses the system preference.
+document.addEventListener("DOMContentLoaded", setTheme);
+
+// If user deletes the Local Storage key, then re-apply theme.
+window.addEventListener("storage", (event) => {
+ if (event.key === THEME_KEY) {
+ setTheme()
+ }
+});
+
+const toggleTheme = () => {
+ if (localStorage.getItem(THEME_KEY) === 'dark' || (!(THEME_KEY in localStorage) && darkModeMedia.matches)) {
+ localStorage.setItem(THEME_KEY, 'light');
+ } else {
+ localStorage.setItem(THEME_KEY, 'dark');
+ }
+ setTheme();
+};
+
+Rails.delegate(document, ".dark-mode-toggle", "click", toggleTheme);
diff --git a/app/javascript/active_admin/features/filters.js b/app/javascript/active_admin/features/filters.js
new file mode 100644
index 00000000000..6e8c5e25263
--- /dev/null
+++ b/app/javascript/active_admin/features/filters.js
@@ -0,0 +1,34 @@
+import Rails from '@rails/ujs';
+import { nextSibling } from 'active_admin/utils/dom'
+
+const disableEmptyFields = function(event) {
+ Array.from(this.querySelectorAll("input, select, textarea"))
+ .filter((el) => el.value === "")
+ .forEach((el) => el.disabled = true)
+};
+
+Rails.delegate(document, ".filters-form", "submit", disableEmptyFields)
+
+const setSearchType = function(event) {
+ const input = nextSibling(this, "input")
+ if (input) {
+ input.name = `q[${this.value}]`
+ }
+};
+
+Rails.delegate(document, ".filters-form-field [data-search-methods]", "change", setSearchType)
+
+const clearFiltersForm = function(event) {
+ event.preventDefault()
+
+ const regex = /^(q\[|page|utf8|commit)/
+ const params = new URLSearchParams(window.location.search)
+
+ Array.from(params.keys())
+ .filter(k => k.match(regex))
+ .forEach(k => params.delete(k))
+
+ window.location.search = params.toString()
+}
+
+Rails.delegate(document, ".filters-form-clear", "click", clearFiltersForm)
diff --git a/app/javascript/active_admin/features/has_many.js b/app/javascript/active_admin/features/has_many.js
new file mode 100644
index 00000000000..524e093c18d
--- /dev/null
+++ b/app/javascript/active_admin/features/has_many.js
@@ -0,0 +1,28 @@
+import Rails from '@rails/ujs';
+
+const hasManyRemoveClick = function(event) {
+ event.preventDefault()
+ const oldGroup = this.closest("fieldset")
+ if (oldGroup) {
+ oldGroup.remove()
+ }
+}
+
+Rails.delegate(document, "form .has-many-remove", "click", hasManyRemoveClick)
+
+const hasManyAddClick = function(event) {
+ event.preventDefault()
+ const parent = this.closest(".has-many-container")
+
+ let index = parseInt(parent.dataset.has_many_index || (parent.querySelectorAll('fieldset').length - 1), 10)
+ parent.dataset.has_many_index = ++index
+
+ const regex = new RegExp(this.dataset.placeholder, 'g')
+ const html = this.dataset.html.replace(regex, index)
+
+ const tempEl = document.createElement("div");
+ tempEl.innerHTML = html
+ this.before(tempEl.firstChild)
+}
+
+Rails.delegate(document, "form .has-many-add", "click", hasManyAddClick)
diff --git a/app/javascript/active_admin/features/main_menu.js b/app/javascript/active_admin/features/main_menu.js
new file mode 100644
index 00000000000..03e7332f505
--- /dev/null
+++ b/app/javascript/active_admin/features/main_menu.js
@@ -0,0 +1,13 @@
+import Rails from '@rails/ujs';
+
+const toggleMenu = function() {
+ const parent = this.closest([`[data-item-id="${this.dataset.parentId}"]`]) || this.parentNode
+
+ if (!("open" in parent.dataset)) {
+ parent.dataset.open = ""
+ } else {
+ delete parent.dataset.open
+ }
+}
+
+Rails.delegate(document, "#main-menu [data-menu-button]", "click", toggleMenu)
diff --git a/app/javascript/active_admin/features/per_page.js b/app/javascript/active_admin/features/per_page.js
new file mode 100644
index 00000000000..499c6b45fd1
--- /dev/null
+++ b/app/javascript/active_admin/features/per_page.js
@@ -0,0 +1,9 @@
+import Rails from '@rails/ujs';
+
+const setPerPage = function(event) {
+ const params = new URLSearchParams(window.location.search)
+ params.set("per_page", this.value)
+ window.location.search = params
+}
+
+Rails.delegate(document, ".pagination-per-page", "change", setPerPage)
diff --git a/app/javascript/active_admin/utils/dom.js b/app/javascript/active_admin/utils/dom.js
new file mode 100644
index 00000000000..20f2d051edb
--- /dev/null
+++ b/app/javascript/active_admin/utils/dom.js
@@ -0,0 +1,17 @@
+const nextSibling = function next(element, selector) {
+ let sibling = element.nextElementSibling;
+
+ if (!selector) {
+ return sibling;
+ }
+
+ while (sibling) {
+ if (sibling && sibling.matches(selector)) {
+ return sibling;
+ }
+
+ sibling = sibling.nextElementSibling;
+ }
+}
+
+export { nextSibling }
diff --git a/app/views/active_admin/_flash_messages.html.erb b/app/views/active_admin/_flash_messages.html.erb
new file mode 100644
index 00000000000..2e67f504610
--- /dev/null
+++ b/app/views/active_admin/_flash_messages.html.erb
@@ -0,0 +1,22 @@
+<% if flash_messages.present? %>
+
+ <% flash_messages.each do |type, message| %>
+ <% if type == "error" %>
+
+
+ <%= message %>
+
+ <% elsif type == "alert" %>
+
+
+ <%= message %>
+
+ <% elsif type == "notice" %>
+
+
+ <%= message %>
+
+ <% end %>
+ <% end %>
+
+<% end %>
diff --git a/app/views/active_admin/_html_head.html.erb b/app/views/active_admin/_html_head.html.erb
new file mode 100644
index 00000000000..afbfd32ae28
--- /dev/null
+++ b/app/views/active_admin/_html_head.html.erb
@@ -0,0 +1,13 @@
+<%= stylesheet_link_tag "active_admin" %>
+
+<%= csrf_meta_tags %>
+<%= csp_meta_tag %>
+<% # On page load or when changing themes, best to add inline in `head` to avoid FOUC %>
+<%= javascript_tag nonce: true do %>
+ if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
+ document.documentElement.classList.add('dark')
+ } else {
+ document.documentElement.classList.remove('dark')
+ }
+<% end %>
+<%= javascript_importmap_tags "active_admin", importmap: ActiveAdmin.importmap %>
diff --git a/app/views/active_admin/_main_navigation.html.erb b/app/views/active_admin/_main_navigation.html.erb
new file mode 100644
index 00000000000..9f11ccfb919
--- /dev/null
+++ b/app/views/active_admin/_main_navigation.html.erb
@@ -0,0 +1,45 @@
+
diff --git a/app/views/active_admin/kaminari/_gap.html.erb b/app/views/active_admin/kaminari/_gap.html.erb
new file mode 100644
index 00000000000..a310332dc25
--- /dev/null
+++ b/app/views/active_admin/kaminari/_gap.html.erb
@@ -0,0 +1,10 @@
+<%# Non-link tag that stands for skipped pages...
+ - available local variables
+ current_page: a page object for the currently displayed page
+ total_pages: total number of pages
+ per_page: number of items to fetch per page
+ remote: data-remote
+-%>
+
+ <%= t('active_admin.pagination.truncate').html_safe %>
+
diff --git a/app/views/active_admin/kaminari/_next_page.html.erb b/app/views/active_admin/kaminari/_next_page.html.erb
new file mode 100644
index 00000000000..97566c1890d
--- /dev/null
+++ b/app/views/active_admin/kaminari/_next_page.html.erb
@@ -0,0 +1,16 @@
+<%# Link to the "Next" page
+ - available local variables
+ url: url to the next page
+ current_page: a page object for the currently displayed page
+ total_pages: total number of pages
+ per_page: number of items to fetch per page
+ remote: data-remote
+-%>
+<% unless current_page.last? %>
+ <%= link_to url, rel: 'next', remote: remote, class: "flex items-center justify-center px-2.5 py-3 h-8 leading-tight text-gray-500 dark:text-gray-400 hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-800 dark:hover:text-white rounded no-underline" do %>
+ <%= t('active_admin.pagination.next') %>
+
+ <% end %>
+<% end %>
diff --git a/app/views/active_admin/kaminari/_page.html.erb b/app/views/active_admin/kaminari/_page.html.erb
new file mode 100644
index 00000000000..3b48ec63cab
--- /dev/null
+++ b/app/views/active_admin/kaminari/_page.html.erb
@@ -0,0 +1,14 @@
+<%# Link showing page number
+ - available local variables
+ page: a page object for "this" page
+ url: url to this page
+ current_page: a page object for the currently displayed page
+ total_pages: total number of pages
+ per_page: number of items to fetch per page
+ remote: data-remote
+-%>
+<% if page.current? %>
+ <%= link_to page, url, { remote: remote, rel: page.rel, class: "flex items-center justify-center px-2.5 py-3 h-8 leading-tight text-white bg-blue-500 dark:text-white dark:bg-blue-600 hover:bg-blue-500 hover:text-white dark:hover:bg-blue-600 dark:hover:text-white rounded no-underline" } %>
+<% else %>
+ <%= link_to page, url, { remote: remote, rel: page.rel, class: "flex items-center justify-center px-2.5 py-3 h-8 leading-tight text-gray-500 dark:text-gray-400 hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-800 dark:hover:text-white rounded no-underline" } %>
+<% end %>
diff --git a/app/views/active_admin/kaminari/_paginator.html.erb b/app/views/active_admin/kaminari/_paginator.html.erb
new file mode 100644
index 00000000000..ade78b2f619
--- /dev/null
+++ b/app/views/active_admin/kaminari/_paginator.html.erb
@@ -0,0 +1,23 @@
+<%# The container tag
+ - available local variables
+ current_page: a page object for the currently displayed page
+ total_pages: total number of pages
+ per_page: number of items to fetch per page
+ remote: data-remote
+ paginator: the paginator that renders the pagination tags inside
+-%>
+<%= paginator.render do -%>
+
+<% end -%>
diff --git a/app/views/active_admin/kaminari/_prev_page.html.erb b/app/views/active_admin/kaminari/_prev_page.html.erb
new file mode 100644
index 00000000000..fad4ddf169b
--- /dev/null
+++ b/app/views/active_admin/kaminari/_prev_page.html.erb
@@ -0,0 +1,16 @@
+<%# Link to the "Previous" page
+ - available local variables
+ url: url to the previous page
+ current_page: a page object for the currently displayed page
+ total_pages: total number of pages
+ per_page: number of items to fetch per page
+ remote: data-remote
+-%>
+<% unless current_page.first? %>
+ <%= link_to url, rel: 'prev', remote: remote, class: "flex items-center justify-center px-2.5 py-3 h-8 leading-tight text-gray-500 dark:text-gray-400 hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-800 dark:hover:text-white rounded no-underline" do %>
+ <%= t('active_admin.pagination.previous') %>
+
+ <% end %>
+<% end %>
diff --git a/app/views/active_admin/page/index.html.arb b/app/views/active_admin/page/index.html.arb
index a1c14ae8a34..19e0b9e838a 100644
--- a/app/views/active_admin/page/index.html.arb
+++ b/app/views/active_admin/page/index.html.arb
@@ -1 +1,5 @@
-insert_tag active_admin_application.view_factory["page"]
+div class: "main-content-container" do
+ if page_presenter.block
+ instance_exec(&page_presenter.block)
+ end
+end
diff --git a/app/views/active_admin/resource/_active_filters.html.erb b/app/views/active_admin/resource/_active_filters.html.erb
new file mode 100644
index 00000000000..16cc8625bf5
--- /dev/null
+++ b/app/views/active_admin/resource/_active_filters.html.erb
@@ -0,0 +1,32 @@
+
"""
- Given a configuration of:
+ And a configuration of:
"""
ActiveAdmin.register Post do
sidebar :help
@@ -182,17 +177,29 @@ Feature: Sidebar Sections
When I am on the index page for posts
Then I should see "Hello World from a partial" within the "Help" sidebar
+ @changes-filesystem
Scenario: Rendering a partial as the sidebar content
Given "app/views/admin/posts/_custom_help_partial.html.erb" contains:
"""
Hello World from a custom partial
"""
- Given a configuration of:
+ And a configuration of:
"""
ActiveAdmin.register Post do
- sidebar :help, :partial => "custom_help_partial"
+ sidebar :help, partial: "custom_help_partial"
end
"""
When I am on the index page for posts
Then I should see "Hello World from a custom partial" within the "Help" sidebar
+ Scenario: Position sidebar at the top using priority option
+ Given a configuration of:
+ """
+ ActiveAdmin.register Post do
+ sidebar :help, priority: 0 do
+ "Need help? Email us at help@example.com"
+ end
+ end
+ """
+ When I am on the index page for posts
+ Then I should see content "Need help? Email us at help@example.com" above other content "Filters"
diff --git a/features/site_title.feature b/features/site_title.feature
index 03cd236defb..2dc92d2d027 100644
--- a/features/site_title.feature
+++ b/features/site_title.feature
@@ -1,3 +1,4 @@
+@site_title
Feature: Site title
As a developer
@@ -7,41 +8,30 @@ Feature: Site title
Background:
Given I am logged in
- Scenario: Set the site title and site title link
+ Scenario: Set the site title
Given a configuration of:
"""
ActiveAdmin.application.site_title = "My Great Site"
- ActiveAdmin.application.site_title_link = "/"
"""
When I am on the dashboard
And I should see the site title "My Great Site"
- When I follow "My Great Site"
- Then I should see "Ruby on Rails: Welcome aboard"
- Scenario: Set the site title image
- Given a configuration of:
- """
- ActiveAdmin.application.site_title_image = "http://railscasts.com/assets/episodes/stills/284-active-admin.png?1316476106"
- """
- When I am on the dashboard
- And I should not see the site title "My Great Site"
- And I should see the site title image "http://railscasts.com/assets/episodes/stills/284-active-admin.png?1316476106"
-
- Scenario: Set the site title image with link
+ Scenario: Set the site title to a proc
Given a configuration of:
"""
- ActiveAdmin.application.site_title_link = "http://www.google.com"
- ActiveAdmin.application.site_title_image = "http://railscasts.com/assets/episodes/stills/284-active-admin.png?1316476106"
+ ActiveAdmin.application.site_title = proc { "Hello #{controller.current_admin_user.try(:email) || 'you!'}" }
"""
When I am on the dashboard
- And I should see the site title image "http://railscasts.com/assets/episodes/stills/284-active-admin.png?1316476106"
- And I should see the site title image linked to "http://www.google.com"
+ And I should see the site title "Hello admin@example.com"
- Scenario: Set the site title to a proc
+ Scenario: Set the site title by namespace
Given a configuration of:
"""
- ActiveAdmin.application.site_title_image = nil # Configuration is not reset between scenarios
- ActiveAdmin.application.site_title = proc { "Hello #{controller.current_admin_user.try(:email) || 'you!'}" }
+ ActiveAdmin.application.site_title = "My Great Site"
+ ActiveAdmin.application.namespace(:superadmin).site_title = "Namespace Site Title"
+ ActiveAdmin.register AdminUser, namespace: :superadmin
"""
+ When I am on the index page for admin_users in the superadmin namespace
+ Then I should see the site title "Namespace Site Title"
When I am on the dashboard
- And I should see the site title "Hello admin@example.com"
+ Then I should see the site title "My Great Site"
diff --git a/features/specifying_actions.feature b/features/specifying_actions.feature
index 8a49949a617..44390f4a50e 100644
--- a/features/specifying_actions.feature
+++ b/features/specifying_actions.feature
@@ -19,18 +19,19 @@ Feature: Specifying Actions
When I am on the index page for posts
Then an "ActionController::RoutingError" exception should be raised when I follow "View"
+ @changes-filesystem
Scenario: Specify a custom collection action with template
Given a configuration of:
"""
ActiveAdmin.register Post do
- action_item(:only => :index) do
+ action_item(:import, only: :index) do
link_to('Import Posts', import_admin_posts_path)
end
collection_action :import
end
"""
- Given "app/views/admin/posts/import.html.arb" contains:
+ And "app/views/admin/posts/import.html.arb" contains:
"""
para "We are currently working on this feature..."
"""
@@ -39,11 +40,12 @@ Feature: Specifying Actions
And I follow "Import"
Then I should see "We are currently working on this feature"
+ @changes-filesystem
Scenario: Specify a custom member action with template
Given a configuration of:
"""
ActiveAdmin.register Post do
- action_item(:only => :show) do
+ action_item(:review, only: :show) do
link_to('Review', review_admin_post_path)
end
@@ -52,7 +54,7 @@ Feature: Specifying Actions
end
end
"""
- Given "app/views/admin/posts/review.html.erb" contains:
+ And "app/views/admin/posts/review.html.erb" contains:
"""
Review: <%= @post.title %>
"""
@@ -63,13 +65,13 @@ Feature: Specifying Actions
And I follow "Review"
Then I should see "Review: Hello World"
And I should see the page title "Review"
- And I should see the Active Admin layout
+ @changes-filesystem
Scenario: Specify a custom member action with template using arb
Given a configuration of:
"""
ActiveAdmin.register Post do
- action_item(:only => :show) do
+ action_item(:review, only: :show) do
link_to('Review', review_admin_post_path)
end
@@ -78,9 +80,9 @@ Feature: Specifying Actions
end
end
"""
- Given "app/views/admin/posts/review.html.arb" contains:
+ And "app/views/admin/posts/review.html.arb" contains:
"""
- h1 "Review: #{@post.title}"
+ h1 "Review: #{post.title}"
"""
And I am logged in
And a post with the title "Hello World" exists
@@ -89,5 +91,29 @@ Feature: Specifying Actions
And I follow "Review"
Then I should see "Review: Hello World"
And I should see the page title "Review"
- And I should see the Active Admin layout
+ Scenario: Specify a custom member action with multiple http methods
+ Given a configuration of:
+ """
+ ActiveAdmin.register Post do
+ action_item(:get_check, only: :show) do
+ link_to('Get Check', check_admin_post_path)
+ end
+
+ action_item(:post_check, only: :show) do
+ link_to('Post Check', check_admin_post_path, method: :post)
+ end
+
+ member_action :check, method: [:get, :post] do
+ redirect_to admin_post_path(resource), notice: "Checked via #{request.method}"
+ end
+ end
+ """
+ And I am logged in
+ And a post with the title "Hello World" exists
+ When I am on the index page for posts
+ And I follow "View"
+ And I follow "Get Check"
+ Then I should see "Checked via GET"
+ When I follow "Post Check"
+ Then I should see "Checked via POST"
diff --git a/features/step_definitions/action_item_steps.rb b/features/step_definitions/action_item_steps.rb
index 8030dc2d08e..be27c221bb0 100644
--- a/features/step_definitions/action_item_steps.rb
+++ b/features/step_definitions/action_item_steps.rb
@@ -1,7 +1,8 @@
-Then /^I should see an action item link to "([^"]*)"$/ do |link|
- page.should have_css('.action_item a', :text => link)
+# frozen_string_literal: true
+Then(/^I should see an action item link to "([^"]*)"$/) do |link|
+ expect(page).to have_css("[data-test-action-items] > a", text: link)
end
-Then /^I should not see an action item link to "([^"]*)"$/ do |link|
- page.should_not have_css('.action_item a', :text => link)
+Then(/^I should not see an action item link to "([^"]*)"$/) do |link|
+ expect(page).to have_no_css("[data-test-action-items] > a", text: link)
end
diff --git a/features/step_definitions/action_link_steps.rb b/features/step_definitions/action_link_steps.rb
index 46273f2db29..9531ac3f540 100644
--- a/features/step_definitions/action_link_steps.rb
+++ b/features/step_definitions/action_link_steps.rb
@@ -1,7 +1,12 @@
-Then /^I should see a member link to "([^"]*)"$/ do |name|
- page.should have_css("a.member_link", :text => name)
+# frozen_string_literal: true
+Then(/^I should see a member link to "([^"]*)"$/) do |name|
+ expect(page).to have_css(".data-table-resource-actions > a", text: name)
end
-Then /^I should not see a member link to "([^"]*)"$/ do |name|
- %{Then I should not see "#{name}" within "a.member_link"}
+Then(/^I should not see a member link to "([^"]*)"$/) do |name|
+ %{Then I should not see "#{name}" within ".data-table-resource-actions > a"}
+end
+
+Then(/^I should see the actions column with the class "([^"]*)" and the title "([^"]*)"$/) do |klass, title|
+ expect(page).to have_css "th#{'.' + klass}", text: title
end
diff --git a/features/step_definitions/additional_web_steps.rb b/features/step_definitions/additional_web_steps.rb
index f8cd625eb7a..e1217c3c279 100644
--- a/features/step_definitions/additional_web_steps.rb
+++ b/features/step_definitions/additional_web_steps.rb
@@ -1,82 +1,79 @@
-Then /^I should see a table header with "([^"]*)"$/ do |content|
- page.should have_xpath('//th', :text => content)
+# frozen_string_literal: true
+Then(/^I should see a table header with "([^"]*)"$/) do |content|
+ expect(page).to have_xpath "//th", text: content
end
-Then /^I should not see a table header with "([^"]*)"$/ do |content|
- page.should_not have_xpath('//th', :text => content)
+Then(/^I should not see a table header with "([^"]*)"$/) do |content|
+ expect(page).to have_no_xpath "//th", text: content
end
-Then /^I should see a sortable table header with "([^"]*)"$/ do |content|
- page.should have_css('th.sortable', :text => content)
+Then(/^I should see a sortable table header with "([^"]*)"$/) do |content|
+ expect(page).to have_css "th[data-sortable]", text: content
end
-Then /^I should not see a sortable table header with "([^"]*)"$/ do |content|
- page.should_not have_css('th.sortable', :text => content)
+Then(/^I should not see a sortable table header with "([^"]*)"$/) do |content|
+ expect(page).to have_no_css "th[data-sortable]", text: content
end
-Then /^I should not see a sortable table header$/ do
- step %{I should not see "th.sortable"}
+Then(/^I should not see a sortable table header$/) do
+ step %{I should not see "th[data-sortable]"}
end
-Then /^the table "([^"]*)" should have (\d+) rows/ do |selector, count|
- table = page.find(selector)
- table.all(:css, 'tr').size.should == count.to_i
+Then(/^the table "([^"]*)" should have (\d+) rows/) do |selector, count|
+ trs = page.find(selector).all :css, "tr"
+ expect(trs.size).to eq count.to_i
end
-Then /^the table "([^"]*)" should have (\d+) columns/ do |selector, count|
- table = page.find(selector)
- row = table.find('tr:first')
- row.all(:css, "td").size.should == count.to_i
+Then(/^the table "([^"]*)" should have (\d+) columns/) do |selector, count|
+ tds = page.find(selector).find("tr:first").all :css, "td"
+ expect(tds.size).to eq count.to_i
end
-Then /^there should be (\d+) "([^"]*)" tags$/ do |count, tag|
- page.all(:css, tag).size.should == count.to_i
+Then(/^there should be (\d+) "([^"]*)" tags?$/) do |count, tag|
+ expect(page.all(:css, tag).size).to eq count.to_i
end
-Then /^I should see a link to "([^"]*)"$/ do |link|
- page.should have_xpath('//a', :text => link)
+Then(/^I should see a link to "([^"]*)"$/) do |link|
+ if Capybara.current_driver == Capybara.javascript_driver
+ expect(page).to have_xpath "//a", text: link, wait: 30
+ else
+ expect(page).to have_xpath "//a", text: link
+ end
end
-Then /^an "([^"]*)" exception should be raised when I follow "([^"]*)"$/ do |error, link|
- lambda {
+Then(/^an "([^"]*)" exception should be raised when I follow "([^"]*)"$/) do |error, link|
+ expect do
step "I follow \"#{link}\""
- }.should raise_error(error.constantize)
-end
-
-Then /^I should be in the resource section for (.+)$/ do |resource_name|
- current_url.should include resource_name.gsub(' ', '').underscore.pluralize
+ end.to raise_error(error.constantize)
end
-Then /^I should wait and see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector|
- sleep 1
- step 'show me the page'
- selector ||= "*"
- locate(:xpath, "//#{selector}[text()='#{text}']")
+Then(/^I should be in the resource section for (.+)$/) do |resource_name|
+ expect(current_url).to include resource_name.tr(" ", "").underscore.pluralize
end
-Then /^I should see the page title "([^"]*)"$/ do |title|
- within('h2#page_title') do
- page.should have_content(title)
+Then(/^I should see the page title "([^"]*)"$/) do |title|
+ within("[data-test-page-header]") do
+ expect(page).to have_text title
end
end
-Then /^I should see a fieldset titled "([^"]*)"$/ do |title|
- page.should have_css('fieldset legend', :text => title)
+Then(/^I should see a fieldset titled "([^"]*)"$/) do |title|
+ expect(page).to have_css "fieldset legend", text: title
end
-Then /^the "([^"]*)" field should contain the option "([^"]*)"$/ do |field, option|
+Then(/^the "([^"]*)" field should contain the option "([^"]*)"$/) do |field, option|
field = find_field(field)
- field.should have_css("option", :text => option)
+ expect(field).to have_css "option", text: option
end
-Then /^I should see the content "([^"]*)"$/ do |content|
- page.should have_css("#active_admin_content", :text => content)
+Then(/^I should see the content "([^"]*)"$/) do |content|
+ expect(page).to have_css "[data-test-page-content]", text: content
end
-Then /^I should see a validation error "([^"]*)"$/ do |error_message|
- page.should have_css(".inline-errors", :text => error_message)
+Then(/^I should see a validation error "([^"]*)"$/) do |error_message|
+ expect(page).to have_css ".inline-errors", text: error_message
end
-Then /^I should see a table with id "([^"]*)"$/ do |dom_id|
- page.should have_css('table', :id => dom_id)
+Then(/^I should see a table with id "([^"]*)"$/) do |dom_id|
+ page.find("table##{dom_id}")
end
diff --git a/features/step_definitions/asset_steps.rb b/features/step_definitions/asset_steps.rb
deleted file mode 100644
index 537fd13f5c8..00000000000
--- a/features/step_definitions/asset_steps.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-Then /^I should see the css file "([^"]*)"$/ do |path|
- step %{I should see the css file "#{path}" of media "screen"}
-end
-
-Then /^I should see the css file "([^"]*)" of media "([^"]*)"$/ do |path, media|
- page.should have_xpath("//link[contains(@href, /stylesheets/#{path}) and contains(@media, #{media})]")
-end
-
-Then /^I should see the js file "([^"]*)"$/ do |path|
- page.should have_xpath("//script[contains(@src, /javascripts/#{path})]")
-end
diff --git a/features/step_definitions/attribute_steps.rb b/features/step_definitions/attribute_steps.rb
index 6f1b624d6d0..20f652ba434 100644
--- a/features/step_definitions/attribute_steps.rb
+++ b/features/step_definitions/attribute_steps.rb
@@ -1,13 +1,19 @@
-Then /^I should see the attribute "([^"]*)" with "([^"]*)"$/ do |title, value|
- elems = all ".attributes_table th:contains('#{title}') ~ td:contains('#{value}')"
- elems.first.should_not be_nil, 'attribute missing'
+# frozen_string_literal: true
+Then(/^I should( not)? see the attribute "([^"]*)" with "([^"]*)"$/) do |negate, title, value|
+ elems = all ".attributes-table th:contains('#{title}') ~ td:contains('#{value}')"
+
+ if negate
+ expect(elems.first).to eq(nil), "attribute missing"
+ else
+ expect(elems.first).to_not eq(nil), "attribute missing"
+ end
end
-Then /^I should see the attribute "([^"]*)" with a nicely formatted datetime$/ do |title|
- text = all(".attributes_table th:contains('#{title}') ~ td").first.text
- text.should match /\w+ \d{1,2}, \d{4} \d{2}:\d{2}/
+Then(/^I should see the attribute "([^"]*)" with a nicely formatted datetime$/) do |title|
+ text = first(".attributes-table th:contains('#{title}') ~ td").text
+ expect(text).to match(/\w+ \d{1,2}, \d{4} \d{2}:\d{2}/)
end
-Then /^I should not see the attribute "([^"]*)"$/ do |title|
- page.should_not have_css('.attributes_table th', :text => title)
+Then(/^I should not see the attribute "([^"]*)"$/) do |title|
+ expect(page).to have_no_css ".attributes-table th", text: title
end
diff --git a/features/step_definitions/batch_action_steps.rb b/features/step_definitions/batch_action_steps.rb
index 9f124143c82..2a0a126d4b3 100644
--- a/features/step_definitions/batch_action_steps.rb
+++ b/features/step_definitions/batch_action_steps.rb
@@ -1,55 +1,58 @@
-Then /^I (should|should not) be asked to confirm "([^"]*)" for "([^"]*)"$/ do |maybe, confirmation, title|
- selector = "#batch_actions_popover a.batch_action:contains('#{title}')"
- selector << "[data-confirm='#{confirmation}']" if maybe == 'should'
- page.send maybe.sub(' ', '_'), have_css(selector)
-end
+# frozen_string_literal: true
+Then(/^I (should|should not) see the batch action :([^\s]*) "([^"]*)"$/) do |maybe, sym, title|
+ selector = "[data-batch-action-item]"
+ selector += "[href='#'][data-action='#{sym}']" if maybe == "should"
-Then /^I (should|should not) see the batch action :([^\s]*) "([^"]*)"$/ do |maybe, sym, title|
- selector = "#batch_actions_selector a.batch_action:contains('#{title}')"
- selector << "[href='#'][data-action='#{sym}']" if maybe == 'should'
- page.send maybe.sub(' ', '_'), have_css(selector)
+ verb = maybe == "should" ? :to : :to_not
+ expect(page).send verb, have_css(selector, text: title)
end
-Then /^the (\d+)(?:st|nd|rd|th) batch action should be "([^"]*)"$/ do |index, title|
- page.all("#batch_actions_selector a.batch_action")[index.to_i - 1].text.should match title
+Then(/^the (\d+)(?:st|nd|rd|th) batch action should be "([^"]*)"$/) do |index, title|
+ batch_action = page.all("[data-batch-action-item]")[index.to_i - 1]
+ expect(batch_action.text).to match title
end
-When /^I check the (\d+)(?:st|nd|rd|th) record$/ do |index|
- page.all("table.index_table input[type=checkbox]")[index.to_i].set true
+When(/^I check the (\d+)(?:st|nd|rd|th) record$/) do |index|
+ page.all(".batch-actions-resource-selection")[index.to_i].set true
end
-When /^I toggle the collection selection$/ do
- page.find("#collection_selection_toggle_all").click
+Then(/^I should see that the batch action button is disabled$/) do
+ expect(page).to have_css ".batch-actions-dropdown button[disabled]"
end
-Then /^I should see that the batch action button is disabled$/ do
- page.should have_css "#batch_actions_selector .dropdown_menu_button.disabled"
-end
+Then(/^I (should|should not) see the batch action (button|selector)$/) do |maybe, type|
+ selector = ".batch-actions-dropdown"
+ selector += " button" if maybe == "should" && type == "button"
-Then /^I (should|should not) see the batch action (button|selector)$/ do |maybe, type|
- selector = "div.table_tools #batch_actions_selector"
- selector << ' .dropdown_menu_button' if maybe == 'should' && type == 'button'
- page.send maybe.sub(' ', '_'), have_css(selector)
+ verb = maybe == "should" ? :to : :to_not
+ expect(page).send verb, have_css(selector)
end
-Then /^I should see the batch action popover exists$/ do
- page.should have_css "#batch_actions_selector"
+Then(/^I should see the batch action popover$/) do
+ expect(page).to have_css ".batch-actions-dropdown"
end
-Given /^I submit the batch action form with "([^"]*)"$/ do |action|
- page.find("#batch_action").set action
- form = page.find "#collection_selection"
- params = page.all("#main_content input").each_with_object({}) do |input, obj|
- key, value = input['name'], input['value']
- if key == 'collection_selection[]'
+Given(/^I submit the batch action form with "([^"]*)"$/) do |action|
+ page.find_by_id('batch_action', visible: false).set action
+ form = page.find_by_id 'collection_selection'
+ params = page.all("#collection_selection input", visible: false).each_with_object({}) do |input, obj|
+ key = input["name"]
+ value = input["value"]
+ if key == "collection_selection[]"
(obj[key] ||= []).push value if input.checked?
else
obj[key] = value
end
end
- page.driver.submit form['method'], form['action'], params
+ page.driver.submit form["method"], form["action"], params
+end
+
+When(/^I click "(.*?)" and accept confirmation$/) do |link|
+ accept_confirm do
+ click_on(link)
+ end
end
-Then /^I should not see checkboxes in the table$/ do
- page.should_not have_css ".paginated_collection table input[type=checkbox]"
+Then(/^I should not see checkboxes in the table$/) do
+ expect(page).to have_no_css ".paginated-collection table input[type=checkbox]"
end
diff --git a/features/step_definitions/breadcrumb_steps.rb b/features/step_definitions/breadcrumb_steps.rb
index 144a01e9baf..e423287f8c6 100644
--- a/features/step_definitions/breadcrumb_steps.rb
+++ b/features/step_definitions/breadcrumb_steps.rb
@@ -1,3 +1,14 @@
-Then /^I should see a link to "([^"]*)" in the breadcrumb$/ do |text|
- page.should have_css('.breadcrumb > a', :text => text)
+# frozen_string_literal: true
+Around "@breadcrumb" do |scenario, block|
+ previous_breadcrumb = ActiveAdmin.application.breadcrumb
+
+ begin
+ block.call
+ ensure
+ ActiveAdmin.application.breadcrumb = previous_breadcrumb
+ end
+end
+
+Then(/^I should see a link to "([^"]*)" in the breadcrumb$/) do |text|
+ expect(page).to have_css "nav[aria-label=breadcrumb] a", text: text
end
diff --git a/features/step_definitions/comment_steps.rb b/features/step_definitions/comment_steps.rb
index 669e9df7890..d59240f5a40 100644
--- a/features/step_definitions/comment_steps.rb
+++ b/features/step_definitions/comment_steps.rb
@@ -1,12 +1,35 @@
-Then /^I should see a comment by "([^"]*)"$/ do |name|
- step %{I should see "#{name}" within ".active_admin_comment_author"}
+# frozen_string_literal: true
+Then(/^I should see a comment by "([^"]*)"$/) do |name|
+ step %{I should see "#{name}" within "[data-test-comment-container]"}
end
-When /^I add a comment "([^"]*)"$/ do |comment|
- step %{I fill in "active_admin_comment_body" with "#{comment}"}
- step %{I press "Add Comment"}
+Then(/^I should( not)? be able to add a comment$/) do |negate|
+ should = negate ? :not_to : :to
+ expect(page).send should, have_button("Add Comment")
end
-Given /^a tag with the name "([^"]*)" exists$/ do |tag_name|
- Tag.create(:name => tag_name)
+When(/^I add a comment "([^"]*)"$/) do |comment|
+ step %{I fill in "comment_body" with "#{comment}"}
+ step %{I press "Add Comment"}
+end
+
+Given(/^(a|\d+) comments added by admin with an email "([^"]+)"?$/) do |number, email|
+ number = number == "a" ? 1 : number.to_i
+ admin_user = ensure_user_created(email)
+
+ comment_text = "Comment %i"
+
+ number.times do |i|
+ ActiveAdmin::Comment.create!(
+ namespace: "admin",
+ body: comment_text % i,
+ resource_type: Post.to_s,
+ resource_id: Post.first.id,
+ author_type: admin_user.class.to_s,
+ author_id: admin_user.id)
+ end
+end
+
+Then(/^I should see (\d+) comments?$/) do |number|
+ expect(page).to have_css("[data-test-comment-container]", count: number.to_i)
end
diff --git a/features/step_definitions/configuration_steps.rb b/features/step_definitions/configuration_steps.rb
index 68d25a9b13e..8cc5c97fa96 100644
--- a/features/step_definitions/configuration_steps.rb
+++ b/features/step_definitions/configuration_steps.rb
@@ -1,93 +1,56 @@
+# frozen_string_literal: true
module ActiveAdminReloading
def load_aa_config(config_content)
- ActiveAdmin::Event.dispatch ActiveAdmin::Application::BeforeLoadEvent, ActiveAdmin.application
+ ActiveSupport::Notifications.instrument ActiveAdmin::Application::BeforeLoadEvent, { active_admin_application: ActiveAdmin.application }
eval(config_content)
- ActiveAdmin::Event.dispatch ActiveAdmin::Application::AfterLoadEvent, ActiveAdmin.application
+ ActiveSupport::Notifications.instrument ActiveAdmin::Application::AfterLoadEvent, { active_admin_application: ActiveAdmin.application }
Rails.application.reload_routes!
- ActiveAdmin.application.namespaces.values.each &:reset_menu!
- end
-end
-
-module ActiveAdminContentsRollback
- def files
- @files ||= {}
- end
-
- # Records the contents of a file the first time we are
- # about to change it
- def record(filename)
- contents = File.read(filename) rescue nil
- files[filename] = contents unless files.has_key? filename
- end
-
- # Rolls the recorded files back to their original states
- def rollback!
- files.each{ |file, contents| rollback_file(file, contents) }
- @files = {}
- end
-
- # If the file originally had content, override the stuff on disk.
- # Else, remove the file and its parent folder structure until Rails.root OR other files exist.
- def rollback_file(file, contents)
- if contents.present?
- File.open(file,'w') { |f| f << contents }
- else
- File.delete(file)
- begin
- dir = File.dirname(file)
- until dir == Rails.root
- Dir.rmdir(dir) # delete current folder
- dir = dir.split('/')[0..-2].join('/') # select parent folder
- end
- rescue Errno::ENOTEMPTY # Directory not empty
- end
- end
+ ActiveAdmin.application.namespaces.each(&:reset_menu!)
end
end
World(ActiveAdminReloading)
-World(ActiveAdminContentsRollback)
-
-After do
- rollback!
-end
-Given /^a(?:n? (index|show))? configuration of:$/ do |action, config_content|
+Given(/^a(?:n? (index|show))? configuration of:$/) do |action, config_content|
load_aa_config(config_content)
case action
- when 'index'
- step 'I am logged in'
- step 'I am on the index page for posts'
- when 'show'
+ when "index"
+ step "I am logged in"
case resource = config_content.match(/ActiveAdmin\.register (\w+)/)[1]
- when 'Post'
- step 'I am logged in'
- step 'I am on the index page for posts'
+ when "Post"
+ step "I am on the index page for posts"
+ when "Category"
+ step "I am on the index page for categories"
+ when "User"
+ step "I am on the index page for users"
+ else
+ # simplecov:disable
+ raise "#{resource} is not supported"
+ # simplecov:enable
+ end
+ when "show"
+ case resource = config_content.match(/ActiveAdmin\.register (\w+)/)[1]
+ when "Post"
+ step "I am logged in"
+ step "I am on the index page for posts"
+ step 'I follow "View"'
+ when "User"
+ step "I am logged in"
+ step "I am on the index page for users"
+ step 'I follow "View"'
+ when "Category"
+ step "I am logged in"
+ step "I am on the index page for categories"
step 'I follow "View"'
- when 'Tag'
- step 'I am logged in'
+ when "Tag"
+ step "I am logged in"
Tag.create!
visit admin_tag_path Tag.last
else
+ # simplecov:disable
raise "#{resource} is not supported"
+ # simplecov:enable
end
end
end
-
-Given /^"([^"]*)" contains:$/ do |filename, contents|
- path = Rails.root + filename
- FileUtils.mkdir_p File.dirname path
- record path
-
- File.open(path,'w+'){ |f| f << contents }
-end
-
-Given /^I add "([^"]*)" to the "([^"]*)" model$/ do |code, model_name|
- path = File.join Rails.root, "app", "models", "#{model_name}.rb"
- record path
-
- str = File.read(path).gsub /^(class .+)$/, "\\1\n #{code}\n"
- File.open(path, 'w+') { |f| f << str }
- ActiveSupport::Dependencies.clear
-end
diff --git a/features/step_definitions/dashboard_steps.rb b/features/step_definitions/dashboard_steps.rb
deleted file mode 100644
index c37d9174be6..00000000000
--- a/features/step_definitions/dashboard_steps.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-Then /^I should see the default welcome message$/ do
- step %{I should see "Welcome to Active Admin" within "#dashboard_default_message"}
-end
-
-Then /^I should not see the default welcome message$/ do
- step %{I should not see "Welcome to Active Admin"}
-end
-
-Then /^I should see a dashboard widget "([^"]*)"$/ do |name|
- page.should have_css('.dashboard .panel h3', :text => name)
-end
-
-Then /^I should not see a dashboard widget "([^"]*)"$/ do |name|
- page.should_not have_css('.dashboard .panel h3', :text => name)
-end
diff --git a/features/step_definitions/factory_steps.rb b/features/step_definitions/factory_steps.rb
index e25c1658b82..a97af4d9ce9 100644
--- a/features/step_definitions/factory_steps.rb
+++ b/features/step_definitions/factory_steps.rb
@@ -1,33 +1,46 @@
-def create_user(name, type = 'User')
- type.camelize.constantize.
- find_or_create_by_first_name_and_last_name *name.split(' '), :username => name.gsub(' ', '').underscore
+# frozen_string_literal: true
+def create_user(name, type = "User")
+ first_name, last_name = name.split(" ")
+ type.camelize.constantize.where(first_name: first_name, last_name: last_name).first_or_create(username: name.tr(" ", "").underscore)
end
-Given /^(a|\d+)( published)? posts?(?: with the title "([^"]*)")?(?: and body "([^"]*)")?(?: written by "([^"]*)")? exists?$/ do |count, published, title, body, user|
- count = count == 'a' ? 1 : count.to_i
- published = Time.now if published
- author = create_user(user) if user
- title ||= "Hello World %i"
+Given(/^(a|\d+)( published)?( unstarred| starred)? posts?(?: with the title "([^"]*)")?(?: and body "([^"]*)")?(?: written by "([^"]*)")?(?: in category "([^"]*)")? exists?$/) do |count, published, starred, title, body, user, category_name|
+ count = count == "a" ? 1 : count.to_i
+ published = Time.now if published
+ starred = starred == " starred" if starred
+ author = create_user(user) if user
+ category = Category.where(name: category_name).first_or_create if category_name
+ title ||= "Hello World %i"
count.times do |i|
- Post.create! :title => title % i, :body => body, :author => author, :published_at => published
+ Post.create! title: title % i, body: body, author: author, published_date: published, custom_category_id: category.try(:id), starred: starred
end
end
-Given /^a category named "([^"]*)" exists$/ do |name|
- Category.create! :name => name
+Given(/^a category named "([^"]*)" exists$/) do |name|
+ Category.create! name: name
end
-Given /^a (user|publisher) named "([^"]*)" exists$/ do |type, name|
+Given(/^a (user|publisher) named "([^"]*)" exists$/) do |type, name|
create_user name, type
end
-Given /^a store named "([^"]*)" exists$/ do |name|
- Store.create! :name => name
+Given(/^a store named "([^"]*)" exists$/) do |name|
+ Store.create! name: name
end
-Given /^I create a new post with the title "([^"]*)"$/ do |title|
- click_link "Posts"
- click_link "New Post"
- fill_in :title, :with => title
- click_button "Create Post"
+Given(/^a tag named "([^"]*)" exists$/) do |name|
+ Tag.create! name: name
+end
+
+Given(/^a company named "([^"]*)"(?: with a store named "([^"]*)")? exists$/) do |name, store_name|
+ store = Store.create! name: store_name if store_name
+
+ Company.create! name: name, stores: [store].compact
+end
+
+Given(/^I create a new post with the title "([^"]*)"$/) do |title|
+ first(:link, "Posts").click
+ click_on "New Post"
+ fill_in "post_title", with: title
+ click_on "Create Post"
end
diff --git a/features/step_definitions/filesystem_steps.rb b/features/step_definitions/filesystem_steps.rb
new file mode 100644
index 00000000000..c7a3190e4e5
--- /dev/null
+++ b/features/step_definitions/filesystem_steps.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+module ActiveAdminContentsRollback
+ def files
+ @files ||= {}
+ end
+
+ # Records the contents of a file the first time we are
+ # about to change it
+ def record(filename)
+ contents = File.read(filename) rescue nil
+ files[filename] = contents unless files.has_key? filename
+ end
+
+ # Rolls the recorded files back to their original states
+ def rollback!
+ files.each { |file, contents| rollback_file(file, contents) }
+ @files = {}
+ end
+
+ # If the file originally had content, override the stuff on disk.
+ # Else, remove the file and its parent folder structure until Rails.root OR other files exist.
+ def rollback_file(file, contents)
+ if contents.present?
+ File.open(file, "w") { |f| f << contents }
+ else
+ File.delete(file)
+ begin
+ dir = File.dirname(file)
+ until dir == Rails.root
+ Dir.rmdir(dir) # delete current folder
+ dir = dir.split("/")[0..-2].join("/") # select parent folder
+ end
+ rescue Errno::ENOTEMPTY # Directory not empty
+ end
+ end
+ end
+end
+
+World(ActiveAdminContentsRollback)
+
+After "@changes-filesystem or @requires-reloading" do
+ rollback!
+end
+
+Given(/^"([^"]*)" contains:$/) do |filename, contents|
+ path = Rails.root + filename
+ FileUtils.mkdir_p File.dirname path
+ record path
+
+ File.open(path, "w+") { |f| f << contents }
+end
+
+Given(/^I add "([^"]*)" to the "([^"]*)" model$/) do |code, model_name|
+ path = Rails.root.join "app", "models", "#{model_name}.rb"
+ record path
+
+ str = File.read(path).gsub(/^(class .+)$/, "\\1\n #{code}\n")
+ File.open(path, "w+") { |f| f << str }
+end
diff --git a/features/step_definitions/filter_steps.rb b/features/step_definitions/filter_steps.rb
index b9e6e2107dc..4ec225bf05d 100644
--- a/features/step_definitions/filter_steps.rb
+++ b/features/step_definitions/filter_steps.rb
@@ -1,17 +1,57 @@
-Then /^I should see a select filter for "([^"]*)"$/ do |label|
- page.should have_css(".filter_select label", :text => label)
+# frozen_string_literal: true
+Around "@filters" do |scenario, block|
+ previous_current_filters = ActiveAdmin.application.current_filters
+
+ begin
+ block.call
+ ensure
+ ActiveAdmin.application.current_filters = previous_current_filters
+ end
+end
+
+Then(/^I should see a select filter for "([^"]*)"$/) do |label|
+ expect(page).to have_css ".filters-form-field.select label", text: label
end
-Then /^I should see a string filter for "([^"]*)"$/ do |label|
- page.should have_css(".filter_string label", :text => label)
+Then(/^I should see a string filter for "([^"]*)"$/) do |label|
+ expect(page).to have_css ".filters-form-field.string label", text: label
end
-Then /^I should see a date range filter for "([^"]*)"$/ do |label|
- page.should have_css(".filter_date_range label", :text => label)
+Then(/^I should see a date range filter for "([^"]*)"$/) do |label|
+ expect(page).to have_css ".filters-form-field.date_range label", text: label
end
-Then /^I should see the following filters:$/ do |table|
+Then(/^I should see a number filter for "([^"]*)"$/) do |label|
+ expect(page).to have_css ".filters-form-field.numeric label", text: label
+end
+
+Then(/^I should see the following filters:$/) do |table|
table.rows_hash.each do |label, type|
step %{I should see a #{type} filter for "#{label}"}
end
end
+
+Given(/^I add parameter "([^"]*)" with value "([^"]*)" to the URL$/) do |key, value|
+ url = page.current_url
+ separator = url.include?("?") ? "&" : "?"
+ visit url + separator + key.to_s + "=" + value.to_s
+end
+
+Then(/^I should have parameter "([^"]*)" with value "([^"]*)"$/) do |key, value|
+ query = URI(page.current_url).query
+ params = Rack::Utils.parse_query query
+ expect(params[key]).to eq value
+end
+
+Then(/^I should see current filter "([^"]*)" equal to "([^"]*)" with label "([^"]*)"$/) do |name, value, label|
+ expect(page).to have_css ".active-filters [data-filter='#{name}'] span", text: label
+ expect(page).to have_css ".active-filters [data-filter='#{name}'] strong", text: value
+end
+
+Then(/^I should see current filter "([^"]*)" equal to "([^"]*)"$/) do |name, value|
+ expect(page).to have_css ".active-filters [data-filter='#{name}'] strong", text: value
+end
+
+Then(/^I should see link "([^"]*)" in current filters/) do |label|
+ expect(page).to have_css ".active-filters [data-filter] strong a", text: label
+end
diff --git a/features/step_definitions/flash_steps.rb b/features/step_definitions/flash_steps.rb
deleted file mode 100644
index cdb9c30e428..00000000000
--- a/features/step_definitions/flash_steps.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-Then /^I should see a flash with "([^"]*)"$/ do |text|
- page.should have_content(text)
-end
-
-Then /^I should see a successful create flash$/ do
- page.should have_css('div.flash_notice', :text => /was successfully created/)
-end
-
-Then /^I should not see a successful create flash$/ do
- page.should_not have_css('div.flash_notice', :text => /was successfully created/)
-end
diff --git a/features/step_definitions/format_steps.rb b/features/step_definitions/format_steps.rb
index 3412a0b9f9e..ad7f3660499 100644
--- a/features/step_definitions/format_steps.rb
+++ b/features/step_definitions/format_steps.rb
@@ -1,52 +1,67 @@
-CSVLib = if RUBY_VERSION =~ /^1.8/
- require 'fastercsv'
- FasterCSV
- else
- require 'csv'
- CSV
- end
+# frozen_string_literal: true
+require "csv"
+
+Around "@csv" do |scenario, block|
+ default_csv_options = ActiveAdmin.application.csv_options
+ default_disable_streaming_in = ActiveAdmin.application.disable_streaming_in
+
+ begin
+ block.call
+ ensure
+ ActiveAdmin.application.disable_streaming_in = default_disable_streaming_in
+ ActiveAdmin.application.csv_options = default_csv_options
+ end
+end
Then "I should see nicely formatted datetimes" do
- page.body.should =~ /\w+ \d{1,2}, \d{4} \d{2}:\d{2}/
+ expect(page.body).to match(/\w+ \d{1,2}, \d{4} \d{2}:\d{2}/)
end
-Then /^I should( not)? see a link to download "([^"]*)"$/ do |negate, format|
- method = negate ? :should_not : :should
- page.send method, have_css("#index_footer a", :text => format)
+Then(/^I should( not)? see a link to download "([^"]*)"$/) do |negate, format|
+ method = negate ? :to_not : :to
+ expect(page).send method, have_css("a", text: format)
end
# Check first rows of the displayed CSV.
-Then /^I should download a CSV file with "([^"]*)" separator for "([^"]*)" containing:$/ do |sep, resource_name, table|
- body = page.driver.response.body
- headers = page.response_headers
- headers['Content-Type'].should eq 'text/csv; charset=utf-8'
- headers['Content-Disposition'].should eq %{attachment; filename="#{resource_name}-#{Time.now.strftime("%Y-%m-%d")}.csv"}
+Then(/^I should download a CSV file with "([^"]*)" separator for "([^"]*)" containing:$/) do |sep, resource_name, table|
+ body = page.driver.response.body
+ content_type_header, content_disposition_header, last_modified_header = %w[Content-Type Content-Disposition Last-Modified].map do |header_name|
+ page.response_headers[header_name]
+ end
+ expect(content_type_header).to eq "text/csv; charset=utf-8"
+ expect(content_disposition_header).to match(/\Aattachment; filename=".+?\.csv"\z/)
+ expect(last_modified_header).to_not be_nil
+ expect(Date.strptime(last_modified_header, "%a, %d %b %Y %H:%M:%S GMT")).to be_a(Date)
- begin
- csv = CSVLib.parse(body, :col_sep => sep)
- table.raw.each_with_index do |expected_row, row_index|
- expected_row.each_with_index do |expected_cell, col_index|
- cell = csv.try(:[], row_index).try(:[], col_index)
- if expected_cell.blank?
- cell.should be_nil
- else
- (cell || '').should match /#{expected_cell}/
- end
+ csv = CSV.parse(body, col_sep: sep)
+ table.raw.each_with_index do |expected_row, row_index|
+ expected_row.each_with_index do |expected_cell, col_index|
+ cell = csv.try(:[], row_index).try(:[], col_index)
+ if expected_cell.blank?
+ expect(cell).to eq nil
+ else
+ expect(cell || "").to match(/#{expected_cell}/)
end
end
- rescue
- puts "Expecting:"
- p table.raw
- puts "to match:"
- p csv
- raise $!
end
end
-Then /^I should download a CSV file for "([^"]*)" containing:$/ do |resource_name, table|
+Then(/^I should download a CSV file for "([^"]*)" containing:$/) do |resource_name, table|
step %{I should download a CSV file with "," separator for "#{resource_name}" containing:}, table
end
-Then /^the CSV file should contain "([^"]*)" in quotes$/ do |text|
- page.driver.response.body.should match /"#{text}"/
+Then(/^the CSV file should contain "([^"]*)" in quotes$/) do |text|
+ expect(page.driver.response.body).to match(/"#{text}"/)
+end
+
+Then(/^the encoding of the CSV file should be "([^"]*)"$/) do |text|
+ expect(page.driver.response.body.encoding).to be Encoding.find(Encoding.aliases[text] || text)
+end
+
+Then(/^the CSV file should start with BOM$/) do
+ expect(page.driver.response.body.bytes).to start_with(239, 187, 191)
+end
+
+Then(/^access denied$/) do
+ expect(page).to have_text(I18n.t("active_admin.access_denied.message"))
end
diff --git a/features/step_definitions/i18n_steps.rb b/features/step_definitions/i18n_steps.rb
index bc93daf0ea1..de9dd3f19cf 100644
--- a/features/step_definitions/i18n_steps.rb
+++ b/features/step_definitions/i18n_steps.rb
@@ -1,3 +1,4 @@
-When /^I set my locale to "([^"]*)"$/ do |lang|
+# frozen_string_literal: true
+When(/^I set my locale to "([^"]*)"$/) do |lang|
I18n.locale = lang
end
diff --git a/features/step_definitions/index_scope_steps.rb b/features/step_definitions/index_scope_steps.rb
index 78c0b487fd4..e785a0e2383 100644
--- a/features/step_definitions/index_scope_steps.rb
+++ b/features/step_definitions/index_scope_steps.rb
@@ -1,21 +1,39 @@
-Then /^I should( not)? see the scope "([^"]*)"( selected)?$/ do |negate, name, selected|
+# frozen_string_literal: true
+Then(/^I should( not)? see the scope "([^"]*)"( selected)?$/) do |negate, name, selected|
should = "I should#{' not' if negate}"
- scope = ".scopes#{' .selected' if selected}"
+ scope = ".scopes#{' .index-button-selected' if selected}"
step %{#{should} see "#{name}" within "#{scope}"}
end
-Then /^I should see the scope "([^"]*)" not selected$/ do |name|
+Then(/^I should see the scope "([^"]*)" not selected$/) do |name|
step %{I should see the scope "#{name}"}
- page.should_not have_css('.scopes .selected', :text => name)
+ expect(page).to have_no_css ".scopes .index-button-selected", text: name
end
-Then /^I should see the scope "([^"]*)" with the count (\d+)$/ do |name, count|
- name = name.gsub(' ','').underscore.downcase
- step %{I should see "#{count}" within ".scopes .#{name} .count"}
+Then(/^I should see the scope "([^"]*)" with the count (\d+)$/) do |name, count|
+ expect(page).to have_css ".scopes a", text: name
+ expect(page).to have_css ".scopes-count", text: count
end
-Then /^I should see the scope "([^"]*)" with no count$/ do |name|
- name = name.gsub(" ", "").underscore.downcase
- page.should have_css(".scopes .#{name}")
- page.should_not have_css(".scopes .#{name} .count")
+Then(/^I should see the scope with label "([^"]*)"$/) do |label|
+ expect(page).to have_link(label)
+end
+
+Then(/^I should see the scope "([^"]*)" with no count$/) do |name|
+ expect(page).to have_css ".scopes a", text: name
+ expect(page).to have_no_css ".scopes-count"
+end
+
+Then "I should see a group {string} with the scopes {string} and {string}" do |group, name1, name2|
+ group = group.tr(" ", "").underscore.downcase
+ expect(page).to have_css ".scopes [data-group='#{group}'] a", text: name1
+ expect(page).to have_css ".scopes [data-group='#{group}'] a", text: name2
+end
+
+Then "I should see a default group with a single scope {string}" do |name|
+ expect(page).to have_css ".scopes [data-group=default] a", text: name
+end
+
+Then "I should not see any scopes" do
+ expect(page).to have_no_css ".scopes a"
end
diff --git a/features/step_definitions/index_views_steps.rb b/features/step_definitions/index_views_steps.rb
deleted file mode 100644
index 9827e852b7a..00000000000
--- a/features/step_definitions/index_views_steps.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-When /^I click "(.*?)"$/ do |link|
- click_link(link)
-end
diff --git a/features/step_definitions/layout_steps.rb b/features/step_definitions/layout_steps.rb
deleted file mode 100644
index 28ff1211f0f..00000000000
--- a/features/step_definitions/layout_steps.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-Then /^I should see the Active Admin layout$/ do
- page.should have_css("#active_admin_content #main_content_wrapper")
-end
diff --git a/features/step_definitions/member_link_steps.rb b/features/step_definitions/member_link_steps.rb
deleted file mode 100644
index 8e044dd63a9..00000000000
--- a/features/step_definitions/member_link_steps.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-Then /^I should see an action item button "([^"]*)"$/ do |content|
- page.should have_css(".action_items a", :text => content)
-end
-
-Then /^I should not see an action item button "([^"]*)"$/ do |content|
- page.should_not have_css(".action_items", :text => content)
-end
diff --git a/features/step_definitions/menu_steps.rb b/features/step_definitions/menu_steps.rb
index 6568fa9cdc4..f010f336aff 100644
--- a/features/step_definitions/menu_steps.rb
+++ b/features/step_definitions/menu_steps.rb
@@ -1,11 +1,24 @@
-Then /^I should see a menu item for "([^"]*)"$/ do |name|
- page.should have_css('#tabs li a', :text => name)
+# frozen_string_literal: true
+Then(/^I should see a menu item for "([^"]*)"$/) do |name|
+ expect(page).to have_css "#main-menu li a", text: name
end
-Then /^I should not see a menu item for "([^"]*)"$/ do |name|
- page.should_not have_css('#tabs li a', :text => name)
+Then(/^I should not see a menu item for "([^"]*)"$/) do |name|
+ expect(page).to have_no_css "#main-menu li a", text: name
end
-Then /^I should see a nested menu item for "([^"]*)"$/ do |name|
- page.should have_css('#tabs > li > ul > li > a', :text => name)
+Then(/^the "([^"]*)" menu item should be hidden$/) do |name|
+ expect(page).to have_css "#main-menu .hidden a", text: name
+end
+
+Then(/^I should see a menu parent for "([^"]*)"$/) do |name|
+ expect(page).to have_css "#main-menu li button, #main-menu li a", text: name
+end
+
+Then(/^I should see a nested menu item for "([^"]*)"$/) do |name|
+ expect(page).to have_css "#main-menu li li a", text: name
+end
+
+Then(/^the "([^"]*)" menu item should be selected$/) do |name|
+ expect(page).to have_css "#main-menu li a.selected", text: name
end
diff --git a/features/step_definitions/pagination_steps.rb b/features/step_definitions/pagination_steps.rb
index ed86dcc7988..33a15772da8 100644
--- a/features/step_definitions/pagination_steps.rb
+++ b/features/step_definitions/pagination_steps.rb
@@ -1,8 +1,16 @@
-Then /^I should not see pagination$/ do
- page.should_not have_css(".pagination")
+# frozen_string_literal: true
+Then(/^I should not see pagination$/) do
+ expect(page).to have_no_css "[data-test-pagination]"
end
-Then /^I should see pagination with (\d+) pages$/ do |count|
- step %{I should see "#{count}" within ".pagination a"}
- step %{I should not see "#{count.to_i + 1}" within ".pagination a"}
+Then(/^I should see pagination page (\d+) link$/) do |num|
+ expect(page).to have_css "[data-test-pagination] a", text: num, count: 1
+end
+
+Then(/^I should see the pagination "Next" link/) do
+ expect(page).to have_css "[data-test-pagination] a", text: "Next"
+end
+
+Then(/^I should not see the pagination "Next" link/) do
+ expect(page).to have_no_css "[data-test-pagination] a", text: "Next"
end
diff --git a/features/step_definitions/root_steps.rb b/features/step_definitions/root_steps.rb
new file mode 100644
index 00000000000..dc035569571
--- /dev/null
+++ b/features/step_definitions/root_steps.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+Around "@root" do |scenario, block|
+ previous_root = ActiveAdmin.application.root_to
+
+ begin
+ block.call
+ ensure
+ ActiveAdmin.application.root_to = previous_root
+ end
+end
diff --git a/features/step_definitions/sidebar_steps.rb b/features/step_definitions/sidebar_steps.rb
deleted file mode 100644
index 007dc1f980d..00000000000
--- a/features/step_definitions/sidebar_steps.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-Then /^I should see a sidebar titled "([^"]*)"$/ do |title|
- page.should have_css(".sidebar_section h3", :text => title)
-end
-
-Then /^I should not see a sidebar titled "([^"]*)"$/ do |title|
- title = title.gsub(' ', '').underscore
- page.all(:css, "##{title}_sidebar_section").count.should == 0
-end
diff --git a/features/step_definitions/site_title_steps.rb b/features/step_definitions/site_title_steps.rb
index bff3734c6a2..601281b5808 100644
--- a/features/step_definitions/site_title_steps.rb
+++ b/features/step_definitions/site_title_steps.rb
@@ -1,15 +1,14 @@
-Then /^I should see the site title "([^"]*)"$/ do |title|
- page.should have_css('h1#site_title', :text => title)
-end
-
-Then /^I should not see the site title "([^"]*)"$/ do |title|
- page.should_not have_css('h1#site_title', :text => title)
-end
+# frozen_string_literal: true
+Around "@site_title" do |scenario, block|
+ previous_site_title = ActiveAdmin.application.site_title
-Then /^I should see the site title image "([^"]*)"$/ do |image|
- page.should have_css('h1#site_title img', :src => image)
+ begin
+ block.call
+ ensure
+ ActiveAdmin.application.site_title = previous_site_title
+ end
end
-Then /^I should see the site title image linked to "([^"]*)"$/ do |url|
- page.should have_css('h1#site_title a', :href => url)
+Then(/^I should see the site title "([^"]*)"$/) do |title|
+ expect(page).to have_css "[data-test-site-title]", text: title
end
diff --git a/features/step_definitions/tab_steps.rb b/features/step_definitions/tab_steps.rb
deleted file mode 100644
index fef7a72fb6b..00000000000
--- a/features/step_definitions/tab_steps.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-Then /^the "([^"]*)" tab should be selected$/ do |name|
- step %{I should see "#{name}" within "ul#tabs li.current"}
-end
diff --git a/features/step_definitions/table_steps.rb b/features/step_definitions/table_steps.rb
index 0ec372ce210..5a7b15860fe 100644
--- a/features/step_definitions/table_steps.rb
+++ b/features/step_definitions/table_steps.rb
@@ -1,5 +1,18 @@
-Then /^I should see (\d+) ([\w]*) in the table$/ do |count, resource_type|
- all("table.index_table tr > td:first").count.should eq count.to_i
+# frozen_string_literal: true
+Then(/^I should see (\d+) ([\w]*) in the table$/) do |count, resource_type|
+ expect(page).to have_css(".data-table tr > td:first-child", count: count.to_i)
+end
+
+Then("I should see {string} in the table") do |string|
+ expect(page).to have_css(".data-table tr > td", text: string)
+end
+
+Then("I should not see {string} in the table") do |string|
+ expect(page).to have_no_css(".data-table tr > td", text: string)
+end
+
+Then(/^I should see an id_column link to edit page$/) do
+ expect(page).to have_css(".data-table a[href*='/edit']", text: /^\d+$/)
end
# TODO: simplify this, if possible?
@@ -12,7 +25,7 @@ def initialize(html, table_css_selector = "table")
def to_array
rows = Nokogiri::HTML(@html).css("#{@selector} tr")
rows.map do |row|
- row.css('th, td').map do |td|
+ row.css("th, td").map do |td|
cell_to_string(td)
end
end
@@ -22,44 +35,28 @@ def to_array
def cell_to_string(td)
str = ""
- input = td.css('input').last
+ input = td.css("input").last
if input
- str << input_to_string(input)
+ str += input_to_string(input)
end
- str << td.content.strip.gsub("\n", ' ')
+ str + td.content.strip.tr("\n", " ")
end
def input_to_string(input)
case input.attribute("type").value
when "checkbox"
- if input.attribute("disabled")
- "_"
- else
- if input.attribute("checked")
- "[X]"
- else
- "[ ]"
- end
- end
- when "text"
- if input.attribute("value").present?
- "[#{input.attribute("value")}]"
- else
- "[ ]"
- end
- when "submit"
- input.attribute("value")
+ "[ ]"
else
+ # simplecov:disable
raise "I don't know what to do with #{input}"
+ # simplecov:enable
end
end
end
module TableMatchHelper
-
-
# @param table [Array[Array]]
# @param expected_table [Array[Array[String]]]
# The expected_table values are String. They are converted to
@@ -78,30 +75,30 @@ def assert_tables_match(table, expected_table)
begin
assert_cells_match(cell, expected_cell)
rescue
+ # simplecov:disable
puts "Cell at line #{row_index} and column #{column_index}: #{cell.inspect} does not match #{expected_cell.inspect}"
puts "Expecting:"
table.each { |row| puts row.inspect }
puts "to match:"
expected_table.each { |row| puts row.inspect }
raise $!
+ # simplecov:enable
end
end
end
end
def assert_cells_match(cell, expected_cell)
- if expected_cell =~ /^\/.*\/$/
- cell.should match(Regexp.new(expected_cell[1..-2]))
+ if /^\/.*\/$/.match?(expected_cell)
+ expect(cell).to match(/#{expected_cell[1..-2]}/)
else
- (cell || "").strip.should == expected_cell
+ expect((cell || "").strip).to eq expected_cell
end
end
-
-end # module TableMatchHelper
+end
World(TableMatchHelper)
-
# Usage:
#
# I should see the "invoices" table:
@@ -109,8 +106,8 @@ def assert_cells_match(cell, expected_cell)
# | /\d+/ | 27/01/12 | $30.00 |
# | /\d+/ | 12/02/12 | $25.00 |
#
-Then /^I should see the "([^"]*)" table:$/ do |table_id, expected_table|
- page.should have_css("table##{table_id}")
+Then(/^I should see the "([^"]*)" table:$/) do |table_id, expected_table|
+ expect(page).to have_css "table##{table_id}"
assert_tables_match(
HtmlTableToTextHelper.new(page.body, "table##{table_id}").to_array,
diff --git a/features/step_definitions/user_steps.rb b/features/step_definitions/user_steps.rb
index f3d606f18cb..1c39a6d6025 100644
--- a/features/step_definitions/user_steps.rb
+++ b/features/step_definitions/user_steps.rb
@@ -1,40 +1,47 @@
+# frozen_string_literal: true
def ensure_user_created(email)
- user = AdminUser.find_or_create_by_email :email => email,
- :password => 'password', :password_confirmation => 'password'
-
- unless user.persisted?
- raise "Could not create user #{email}: #{user.errors.full_messages}"
- end
- user
+ AdminUser.create_with(password: "password", password_confirmation: "password").find_or_create_by!(email: email)
end
-Given /^(?:I am logged|log) out$/ do
- click_link 'Logout' if page.all(:css, "a", :text => 'Logout').any?
+Given(/^(?:I am logged|log) out$/) do
+ click_on "Sign out" if page.all(:css, "a", text: "Sign out").any?
end
-Given /^I am logged in$/ do
- step 'log out'
- login_as ensure_user_created 'admin@example.com'
+Given(/^I am logged in$/) do
+ logout(:admin_user)
+ login_as ensure_user_created("admin@example.com"), scope: :admin_user
end
-# only for @requires-reloading scenario
-Given /^I am logged in with capybara$/ do
- ensure_user_created 'admin@example.com'
- step 'log out'
+Given(/^I am logged in with capybara$/) do
+ ensure_user_created "admin@example.com"
+ step "log out"
visit new_admin_user_session_path
- fill_in 'Email', :with => 'admin@example.com'
- fill_in 'Password', :with => 'password'
- click_button 'Login'
+ fill_in "Email", with: "admin@example.com"
+ fill_in "Password", with: "password"
+ click_on "Sign In"
end
-Given /^an admin user "([^"]*)" exists$/ do |email|
+Given(/^an admin user "([^"]*)" exists$/) do |email|
ensure_user_created(email)
end
-Given /^an admin user "([^"]*)" exists with( expired)? reset password token "(.*?)"$/ do |email, expired, token|
- user = ensure_user_created(email)
- user.reset_password_token = token
- user.reset_password_sent_at = 1.minute.ago unless expired
- user.save
+Given(/^"([^"]*)" requests a password reset with token "([^"]*)"( but it expires)?$/) do |email, token, expired|
+ visit new_admin_user_password_path
+ fill_in "Email", with: email
+ allow(Devise).to receive(:friendly_token).and_return(token)
+ click_on "Reset My Password"
+
+ AdminUser.where(email: email).first.update_attribute :reset_password_sent_at, 1.month.ago if expired
+end
+
+Given(/^override locale "([^"]*)" with "([^"]*)"$/) do |path, value|
+ keys_value = path.split(".") + [value]
+ locale_hash = keys_value.reverse.inject { |a, n| { n => a } }
+ I18n.available_locales
+ I18n.backend.store_translations(I18n.locale, locale_hash)
+end
+
+When(/^I fill in the password field with "([^"]*)"$/) do |password|
+ fill_in "admin_user_password", with: password
end
diff --git a/features/step_definitions/web_steps.rb b/features/step_definitions/web_steps.rb
index 4ac1163bd74..01a64f12ae3 100644
--- a/features/step_definitions/web_steps.rb
+++ b/features/step_definitions/web_steps.rb
@@ -1,82 +1,136 @@
-require 'uri'
-require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
-require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "selectors"))
+# frozen_string_literal: true
+require "uri"
+require File.expand_path(File.join(__dir__, "..", "support", "paths"))
module WithinHelpers
def with_scope(locator)
locator ? within(*selector_for(locator)) { yield } : yield
end
+
+ private
+
+ def selector_for(locator)
+ case locator
+
+ # Add more mappings here.
+ # Here is an example that pulls values out of the Regexp:
+ #
+ # when /^the (notice|error|info) flash$/
+ # ".flash.#{$1}"
+
+ # You can also return an array to use a different selector
+ # type, like:
+ #
+ # when /the header/
+ # [:xpath, "//header"]
+
+ when /^the "([^"]*)" sidebar$/
+ [:css, "##{$1.tr(" ", '').underscore}_sidebar_section"]
+
+ # This allows you to provide a quoted selector as the scope
+ # for "within" steps as was previously the default for the
+ # web steps:
+ when /^"(.+)"$/
+ $1
+
+ else
+ # simplecov:disable
+ raise "Can't find mapping from \"#{locator}\" to a selector.\n" +
+ "Now, go and add a mapping in #{__FILE__}"
+ # simplecov:enable
+ end
+ end
end
World(WithinHelpers)
-# Single-line step scoper
-When /^(.*) within (.*[^:])$/ do |step_name, parent|
+When(/^(.*) within (.*)$/) do |step_name, parent|
with_scope(parent) { step step_name }
end
-# Multi-line step scoper
-When /^(.*) within (.*[^:]):$/ do |step_name, parent, table_or_string|
- with_scope(parent) { step "#{step_name}:", table_or_string }
+Given(/^I am on (.+)$/) do |page_name|
+ visit path_to(page_name)
end
-Given /^(?:I )am on (.+)$/ do |page_name|
+When(/^I go to (.+)$/) do |page_name|
visit path_to(page_name)
end
-When /^(?:I )go to (.+)$/ do |page_name|
- visit path_to(page_name)
+When(/^I visit (.+) twice$/) do |page_name|
+ 2.times { visit path_to(page_name) }
end
-When /^(?:I )press "([^"]*)"$/ do |button|
- click_button(button)
+When(/^I press "([^"]*)"$/) do |button|
+ click_on(button)
end
-When /^(?:I )follow "([^"]*)"$/ do |link|
- click_link(link)
+When(/^I follow "([^"]*)"$/) do |link|
+ first(:link, link).click
end
-When /^(?:I )fill in "([^"]*)" with "([^"]*)"$/ do |field, value|
- fill_in(field, :with => value)
+When(/^I click "(.*?)"$/) do |link|
+ click_on(link)
end
-When /^(?:I )select "([^"]*)" from "([^"]*)"$/ do |value, field|
- select(value, :from => field)
+When(/^I fill in "([^"]*)" with "([^"]*)"$/) do |field, value|
+ fill_in(field, with: value)
end
-When /^(?:I )(check|uncheck|choose) "([^"]*)"$/ do |action, field|
+When(/^I select "([^"]*)" from "([^"]*)"$/) do |value, field|
+ select(value, from: field)
+end
+
+When(/^I (check|uncheck) "([^"]*)"$/) do |action, field|
send action, field
end
-When /^(?:I )attach the file "([^"]*)" to "([^"]*)"$/ do |path, field|
- attach_file(field, File.expand_path(path))
+Then(/^I should( not)? see( the element)? "([^"]*)"$/) do |negate, is_css, text|
+ should = negate ? :not_to : :to
+ have = is_css ? have_css(text) : have_text(text)
+ expect(page).send should, have
+end
+
+Then(/^I should see the select "([^"]*)" with options "([^"]+)"?$/) do |label, with_options|
+ expect(page).to have_select(label, with_options: with_options.split(", "))
end
-Then /^(?:I )should( not)? see( the element)? "([^"]*)"$/ do |negate, is_css, text|
- should = negate ? :should_not : :should
- have = is_css ? have_css(text) : have_content(text)
- page.send should, have
+Then(/^I should see the field "([^"]*)" of type "([^"]+)"?$/) do |label, of_type|
+ expect(page).to have_field(label, type: of_type)
end
-Then /^the "([^"]*)" field(?: within (.*))? should( not)? contain "([^"]*)"$/ do |field, parent, negate, value|
+Then(/^the "([^"]*)" field(?: within (.*))? should contain "([^"]*)"$/) do |field, parent, value|
with_scope(parent) do
field = find_field(field)
- field_value = (field.tag_name == 'textarea') ? field.text : field.value
- negate ? field_value.should_not =~ /#{value}/ : field_value.should =~ /#{value}/
+ value = field.tag_name == "textarea" ? field.text : field.value
+
+ expect(value).to match(/#{value}/)
end
end
-Then /^the "([^"]*)" checkbox(?: within (.*))? should( not)? be checked$/ do |label, parent, negate|
+Then(/^the "([^"]*)" select(?: within (.*))? should have "([^"]+)" selected$/) do |label, parent, option|
with_scope(parent) do
- field_checked = find_field(label)['checked']
- field_checked.should negate ? be_false : be_true
+ expect(page).to have_select(label, selected: option)
end
end
-Then /^(?:|I )should be on (.+)$/ do |page_name|
- current_path = URI.parse(current_url).path
- current_path.should == path_to(page_name)
+Then(/^the "([^"]*)" checkbox(?: within (.*))? should( not)? be checked$/) do |label, parent, negate|
+ with_scope(parent) do
+ checkbox = find_field(label)
+ if negate
+ expect(checkbox).not_to be_checked
+ else
+ expect(checkbox).to be_checked
+ end
+ end
+end
+
+Then(/^I should be on (.+)$/) do |page_name|
+ expect(URI.parse(current_url).path).to eq path_to page_name
+end
+
+Then(/^I should see content "(.*?)" above other content "(.*?)"$/) do |top_title, bottom_title|
+ expect(page).to have_css %Q(div:contains('#{top_title}') + div:contains('#{bottom_title}'))
end
-Then /^show me the page$/ do
- save_and_open_page
+Then(/^I should see a flash with "([^"]*)"$/) do |text|
+ expect(page).to have_text text
end
diff --git a/features/sti_resource.feature b/features/sti_resource.feature
index 3c669c864f6..54d0929c442 100644
--- a/features/sti_resource.feature
+++ b/features/sti_resource.feature
@@ -6,8 +6,12 @@ Feature: STI Resource
Given I am logged in
And a configuration of:
"""
- ActiveAdmin.register Publisher
- ActiveAdmin.register User
+ ActiveAdmin.register Publisher do
+ permit_params :first_name, :last_name, :username, :age, :reason_of_sign_in
+ end
+ ActiveAdmin.register User do
+ permit_params :first_name, :last_name, :username, :age, :reason_of_sign_in
+ end
"""
Scenario: Create, update and delete a child STI resource
diff --git a/features/strong_parameters.feature b/features/strong_parameters.feature
new file mode 100644
index 00000000000..51e527cf28b
--- /dev/null
+++ b/features/strong_parameters.feature
@@ -0,0 +1,69 @@
+@silent_unpermitted_params_failure
+Feature: Strong Params
+
+ Background:
+ Given a category named "Music" exists
+ And a user named "John Doe" exists
+ And a post with the title "Hello World" written by "John Doe" exists
+ And I am logged in
+ And a configuration of:
+ """
+ ActiveAdmin.register Post do
+ end
+ """
+ And I am on the index page for posts
+
+ Scenario: Static permitted parameters
+ Given a configuration of:
+ """
+ ActiveAdmin.register Post do
+ permit_params :author, :title, :starred
+ end
+ """
+ When I follow "Edit"
+
+ And I fill in "Title" with "Hello World from update"
+ And I check "Starred"
+ And I press "Update Post"
+ Then I should see "Post was successfully updated."
+ And I should see the attribute "Title" with "Hello World from update"
+ And I should see the attribute "Author" with "John Doe"
+ And I should see the attribute "Starred" with "Yes"
+
+ Scenario: Dynamic permitted parameters
+ Given a configuration of:
+ """
+ ActiveAdmin.register Post do
+ permit_params do
+ allow = [:author, :title]
+ allow << :starred
+ allow
+ end
+ end
+ """
+ When I follow "Edit"
+
+ And I fill in "Title" with "Hello World from update"
+ And I check "Starred"
+ And I press "Update Post"
+ Then I should see "Post was successfully updated."
+ And I should see the attribute "Title" with "Hello World from update"
+ And I should see the attribute "Author" with "John Doe"
+ And I should see the attribute "Starred" with "Yes"
+
+ Scenario: Should not update parameters that are not declared as permitted
+ Given a configuration of:
+ """
+ ActiveAdmin.register Post do
+ permit_params :author, :title
+ end
+ """
+ When I follow "Edit"
+
+ And I fill in "Title" with "Hello World from update"
+ And I check "Starred"
+ And I press "Update Post"
+ Then I should see "Post was successfully updated."
+ And I should see the attribute "Title" with "Hello World from update"
+ And I should see the attribute "Author" with "John Doe"
+ And I should see the attribute "Starred" with "Unknown"
diff --git a/features/support/env.rb b/features/support/env.rb
index d269e7aa0a5..82754c7f448 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -1,118 +1,113 @@
-# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
-# It is recommended to regenerate this file in the future when you upgrade to a
-# newer version of cucumber-rails. Consider adding your own code to a new file
-# instead of editing this one. Cucumber will automatically load all features/**/*.rb
-# files.
+# frozen_string_literal: true
+ENV["RAILS_ENV"] = "test"
-ENV["RAILS_ENV"] ||= "cucumber"
-ENV['BUNDLE_GEMFILE'] = File.expand_path('../../../Gemfile', __FILE__)
-require "rubygems"
-require "bundler"
-Bundler.setup
+require_relative "../../spec/support/simplecov_helper"
-require File.expand_path('../../../spec/support/detect_rails_version', __FILE__)
-ENV["RAILS"] = detect_rails_version
+ActiveAdmin::TestSupport::SimpleCovHelper.start("features")
-ENV['RAILS_ROOT'] = File.expand_path("../../../spec/rails/rails-#{ENV["RAILS"]}", __FILE__)
+Dir["#{File.expand_path('../step_definitions', __dir__)}/*.rb"].each do |f|
+ require f
+end
+
+require_relative "../../tasks/test_application"
+
+require "#{ActiveAdmin::TestApplication.new.full_app_dir}/config/environment.rb"
+
+require_relative "rails"
+require_relative "../../spec/support/active_support_deprecation"
+require "rspec/mocks"
+World(RSpec::Mocks::ExampleMethods)
-# Create the test app if it doesn't exists
-unless File.exists?(ENV['RAILS_ROOT'])
- system 'rake setup'
+Around "@mocks" do |scenario, block|
+ RSpec::Mocks.setup
+
+ block.call
+
+ begin
+ RSpec::Mocks.verify
+ ensure
+ RSpec::Mocks.teardown
+ end
+end
+
+After "@debug" do |scenario|
+ # simplecov:disable
+ save_and_open_page if scenario.failed?
+ # simplecov:enable
end
-# Ensure the Active Admin load path is happy
-require 'rails'
-require 'active_admin'
-ActiveAdmin.application.load_paths = [ENV['RAILS_ROOT'] + "/app/admin"]
+require "capybara/cuprite"
-require ENV['RAILS_ROOT'] + '/config/environment'
+Capybara.register_driver(:cuprite) do |app|
+ Capybara::Cuprite::Driver.new(app, process_timeout: 30, timeout: 30)
+end
+
+Capybara.javascript_driver = :cuprite
-# Setup autoloading of ActiveAdmin and the load path
-$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
-autoload :ActiveAdmin, 'active_admin'
+Capybara.server = :webrick
-require 'cucumber/rails'
+Capybara.asset_host = "http://localhost:3000"
-require 'capybara/rails'
-require 'capybara/cucumber'
-require 'capybara/session'
# Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In
# order to ease the transition to Capybara we set the default here. If you'd
# prefer to use XPath just remove this line and adjust any selectors in your
# steps to use the XPath syntax.
Capybara.default_selector = :css
-# If you set this to false, any error raised from within your app will bubble
-# up to your step definition and out to cucumber unless you catch it somewhere
-# on the way. You can make Rails rescue errors and render error pages on a
-# per-scenario basis by tagging a scenario or feature with the @allow-rescue tag.
-#
-# If you set this to true, Rails will rescue all errors and render error
-# pages, more or less in the same way your application would behave in the
-# default production environment. It's not recommended to do this for all
-# of your scenarios, as this makes it hard to discover errors in your application.
-ActionController::Base.allow_rescue = false
-
-# If you set this to true, each scenario will run in a database transaction.
-# You can still turn off transactions on a per-scenario basis, simply tagging
-# a feature or scenario with the @no-txn tag. If you are using Capybara,
-# tagging with @culerity or @javascript will also turn transactions off.
-#
-# If you set this to false, transactions will be off for all scenarios,
-# regardless of whether you use @no-txn or not.
-#
-# Beware that turning transactions off will leave data in your database
-# after each scenario, which can lead to hard-to-debug failures in
-# subsequent scenarios. If you do this, we recommend you create a Before
-# block that will explicitly put your database in a known state.
-Cucumber::Rails::World.use_transactional_fixtures = false
-# How to clean your database when transactions are turned off. See
-# http://github.com/bmabey/database_cleaner for more info.
-if defined?(ActiveRecord::Base)
- begin
- require 'database_cleaner'
- require 'database_cleaner/cucumber'
- DatabaseCleaner.strategy = :truncation
- rescue LoadError => ignore_if_database_cleaner_not_present
- end
-end
+# Database resetting strategy
+DatabaseCleaner.strategy = :truncation
+Cucumber::Rails::Database.javascript_strategy = :truncation
# Warden helpers to speed up login
-# See https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
+# See https://github.com/heartcombo/devise/wiki/How-To:-Test-with-Capybara
include Warden::Test::Helpers
+Devise.configure_warden!
After do
Warden.test_reset!
+end
+
+Before do
+ # Reload Active Admin
+ ActiveAdmin.unload!
+ ActiveAdmin.load!
+end
+# Force deprecations to raise an exception.
+ActiveAdmin::DeprecationHelper.behavior = :raise
+
+After "@authorization" do |scenario, block|
# Reset back to the default auth adapter
ActiveAdmin.application.namespace(:admin).
authorization_adapter = ActiveAdmin::AuthorizationAdapter
end
-Before do
+Around "@silent_unpermitted_params_failure" do |scenario, block|
+ original = ActionController::Parameters.action_on_unpermitted_parameters
begin
- # We are caching classes, but need to manually clear references to
- # the controllers. If they aren't clear, the router stores references
- ActiveSupport::Dependencies.clear
-
- # Reload Active Admin
- ActiveAdmin.unload!
- ActiveAdmin.load!
- rescue
- p $!
- raise $!
+ ActionController::Parameters.action_on_unpermitted_parameters = false
+ block.call
+ ensure
+ ActionController::Parameters.action_on_unpermitted_parameters = original
end
end
-# improve the performance of the specs suite by not logging anything
-# see http://blog.plataformatec.com.br/2011/12/three-tips-to-improve-the-performance-of-your-test-suite/
-Rails.logger.level = 4
+Around "@locale_manipulation" do |scenario, block|
+ I18n.with_locale(:en, &block)
+end
+
+class CustomIndexView < ActiveAdmin::Component
+ def build(page_presenter, collection)
+ add_class "custom-index-view"
+ resource_selection_toggle_panel if active_admin_config.batch_actions.any?
+ collection.each do |obj|
+ instance_exec(obj, &page_presenter.block)
+ end
+ end
-# Improves performance by forcing the garbage collector to run less often.
-unless ENV['DEFER_GC'] == '0' || ENV['DEFER_GC'] == 'false'
- require File.expand_path('../../../spec/support/deferred_garbage_collection', __FILE__)
- Before { DeferredGarbageCollection.start }
- After { DeferredGarbageCollection.reconsider }
+ def self.index_name
+ "custom"
+ end
end
diff --git a/features/support/paths.rb b/features/support/paths.rb
index c78de34ff33..e240a512616 100644
--- a/features/support/paths.rb
+++ b/features/support/paths.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module NavigationHelpers
# Maps a name to a path. Used by the
#
@@ -8,37 +9,51 @@ module NavigationHelpers
def path_to(page_name)
case page_name
- when /the home\s?page/
- '/'
when /the dashboard/
"/admin"
when /the new post page/
"/admin/posts/new"
- when /the admin password reset form with reset password token "([^"]*)"/
+ when /the login page/
+ "/admin/login"
+ when /the first post custom status page/
+ "/admin/posts/1/status"
+ when /the last posts page/
+ "/admin/last_posts"
+ when /the admin password reset form with token "([^"]*)"/
"/admin/password/edit?reset_password_token=#{$1}"
- # the index page for posts in the root namespace
# the index page for posts in the user_admin namespace
when /^the index page for (.*) in the (.*) namespace$/
- if $2 != 'root'
- send(:"#{$2}_#{$1}_path")
- else
- send(:"#{$1}_path")
- end
+ send :"#{$2}_#{$1}_path"
# same as above, except defaults to admin namespace
when /^the index page for (.*)$/
- send(:"admin_#{$1}_path")
+ send :"admin_#{$1}_path"
+
+ when /^the (.*) index page for (.*)$/
+ send :"admin_#{$2}_path", format: $1
when /^the last author's posts$/
admin_user_posts_path(User.last)
+ when /^the last author's stories$/
+ admin_user_stories_path(User.last)
+
when /^the last author's last post page$/
- admin_user_post_path(User.last, Post.where(:author_id => User.last.id).last)
+ admin_user_post_path(User.last, Post.where(author_id: User.last.id).last)
+
+ when /^the last post's show page$/
+ admin_post_path(Post.last)
+
+ when /^the post's show page$/
+ admin_post_path(Post.last)
when /^the last post's edit page$/
edit_admin_post_path(Post.last)
+ when /^the last author's show page$/
+ admin_user_path(User.last)
+
# Add more mappings here.
# Here is an example that pulls values out of the Regexp:
#
@@ -49,10 +64,12 @@ def path_to(page_name)
begin
page_name =~ /the (.*) page/
path_components = $1.split(/\s+/)
- self.send(path_components.push('path').join('_').to_sym)
- rescue Object => e
+ self.send path_components.push("path").join("_")
+ # simplecov:disable
+ rescue Object
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
"Now, go and add a mapping in #{__FILE__}"
+ # simplecov:enable
end
end
end
diff --git a/features/support/rails.rb b/features/support/rails.rb
new file mode 100644
index 00000000000..2a37e729456
--- /dev/null
+++ b/features/support/rails.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+require "cucumber/rails/application"
+require "cucumber/rails/action_dispatch"
+require "cucumber/rails/world"
+require "cucumber/rails/hooks"
+require "cucumber/rails/capybara"
+require "cucumber/rails/database"
+
+MultiTest.disable_autorun
diff --git a/features/support/selectors.rb b/features/support/selectors.rb
deleted file mode 100644
index 238f4745c99..00000000000
--- a/features/support/selectors.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-module HtmlSelectorsHelpers
- # Maps a name to a selector. Used primarily by the
- #
- # When /^(.+) within (.+)$/ do |step, scope|
- #
- # step definitions in web_steps.rb
- #
- def selector_for(locator)
- case locator
-
- when "the page"
- "html > body"
-
- # Add more mappings here.
- # Here is an example that pulls values out of the Regexp:
- #
- # when /^the (notice|error|info) flash$/
- # ".flash.#{$1}"
-
- # You can also return an array to use a different selector
- # type, like:
- #
- # when /the header/
- # [:xpath, "//header"]
-
- when "index grid"
- [:css, "table.index_grid"]
-
- when /^the "([^"]*)" sidebar$/
- [:css, "##{$1.gsub(" ", '').underscore}_sidebar_section"]
-
- # This allows you to provide a quoted selector as the scope
- # for "within" steps as was previously the default for the
- # web steps:
- when /^"(.+)"$/
- $1
-
- else
- raise "Can't find mapping from \"#{locator}\" to a selector.\n" +
- "Now, go and add a mapping in #{__FILE__}"
- end
- end
-end
-
-World(HtmlSelectorsHelpers)
diff --git a/features/support/simplecov_changes_env.rb b/features/support/simplecov_changes_env.rb
new file mode 100644
index 00000000000..26c98e2427c
--- /dev/null
+++ b/features/support/simplecov_changes_env.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+require_relative "../../spec/support/simplecov_helper"
+
+ActiveAdmin::TestSupport::SimpleCovHelper.start("filesystem changes features")
+
+require_relative "env"
diff --git a/features/support/simplecov_regular_env.rb b/features/support/simplecov_regular_env.rb
new file mode 100644
index 00000000000..0d0f61e2fa4
--- /dev/null
+++ b/features/support/simplecov_regular_env.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+require_relative "../../spec/support/simplecov_helper"
+
+ActiveAdmin::TestSupport::SimpleCovHelper.start(["regular features", ENV["TEST_ENV_NUMBER"]].join(" ").rstrip)
+
+require_relative "env"
diff --git a/features/support/simplecov_reload_env.rb b/features/support/simplecov_reload_env.rb
new file mode 100644
index 00000000000..8b147e24c42
--- /dev/null
+++ b/features/support/simplecov_reload_env.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+require_relative "../../spec/support/simplecov_helper"
+
+ActiveAdmin::TestSupport::SimpleCovHelper.start("reload features")
+
+require_relative "env"
diff --git a/features/users/logging_in.feature b/features/users/logging_in.feature
index 23416b5ef74..ed90c3e2c04 100644
--- a/features/users/logging_in.feature
+++ b/features/users/logging_in.feature
@@ -3,32 +3,30 @@ Feature: User Logging In
Logging in to the system as an admin user
Background:
- Given a configuration of:
- """
- ActiveAdmin.register Post
- """
- And I am logged out
+ Given I am logged out
And an admin user "admin@example.com" exists
When I go to the dashboard
Scenario: Logging in Successfully
When I fill in "Email" with "admin@example.com"
And I fill in "Password" with "password"
- And I press "Login"
+ And I press "Sign In"
Then I should be on the the dashboard
- And I should see "Logout"
- And I should see "admin@example.com"
+ And I should see the element "a[href='/admin/logout' ]:contains('Sign out')"
+ And I should see the element "a[href='/admin/admin_users/1']:contains('admin@example.com')"
Scenario: Attempting to log in with an incorrect email address
+ Given override locale "devise.failure.not_found_in_database" with "Invalid email or password."
When I fill in "Email" with "not-an-admin@example.com"
And I fill in "Password" with "not-my-password"
- And I press "Login"
- Then I should see "Login"
+ And I press "Sign In"
+ Then I should see "Sign In"
And I should see "Invalid email or password."
Scenario: Attempting to log in with an incorrect password
+ Given override locale "devise.failure.invalid" with "Invalid email or password."
When I fill in "Email" with "admin@example.com"
And I fill in "Password" with "not-my-password"
- And I press "Login"
- Then I should see "Login"
+ And I press "Sign In"
+ Then I should see "Sign In"
And I should see "Invalid email or password."
diff --git a/features/users/logging_out.feature b/features/users/logging_out.feature
index bf8ef77e190..ed6acbe7e3c 100644
--- a/features/users/logging_out.feature
+++ b/features/users/logging_out.feature
@@ -3,11 +3,8 @@ Feature: User Logging out
Logging out of the system as an admin user
Scenario: Logging out successfully
- Given a configuration of:
- """
- ActiveAdmin.register Post
- """
- And I am logged in
- When I go to the dashboard
- And I follow "Logout"
- Then I should see "Login"
+ When I am logged in
+ And I go to the dashboard
+ Then I should see the element "a[data-method='delete']:contains('Sign out')"
+ And I follow "Sign out"
+ And I should see "Sign In"
diff --git a/features/users/resetting_password.feature b/features/users/resetting_password.feature
index 9935fc7bf54..dbad1f8e3de 100644
--- a/features/users/resetting_password.feature
+++ b/features/users/resetting_password.feature
@@ -3,32 +3,30 @@ Feature: User Resetting Password
Resetting my password as an admin user
Background:
- Given a configuration of:
- """
- ActiveAdmin.register Post
- """
- And I am logged out
+ Given I am logged out
And an admin user "admin@example.com" exists
Scenario: Resetting password successfully
When I go to the dashboard
And I follow "Forgot your password?"
- When I fill in "Email" with "admin@example.com"
+ And I fill in "Email" with "admin@example.com"
And I press "Reset My Password"
- Then I should see "You will receive an email with instructions about how to reset your password in a few minutes."
+ Then I should see "You will receive an email with instructions on how to reset your password in a few minutes."
+ @mocks
Scenario: Changing password after resetting
- Given an admin user "admin@example.com" exists with reset password token "123reset"
- When I go to the admin password reset form with reset password token "123reset"
- And I fill in "Password" with "password"
+ When "admin@example.com" requests a password reset with token "foobarbaz"
+ And I go to the admin password reset form with token "foobarbaz"
+ And I fill in the password field with "password"
And I fill in "Password confirmation" with "password"
And I press "Change my password"
Then I should see "success"
+ @mocks
Scenario: Changing password after resetting with errors
- Given an admin user "admin@example.com" exists with expired reset password token "123reset"
- When I go to the admin password reset form with reset password token "123reset"
- And I fill in "Password" with "password"
+ When "admin@example.com" requests a password reset with token "foobarbaz" but it expires
+ And I go to the admin password reset form with token "foobarbaz"
+ And I fill in the password field with "password"
And I fill in "Password confirmation" with "wrong"
And I press "Change my password"
Then I should see "expired"
diff --git a/gemfiles/rails_72/Gemfile b/gemfiles/rails_72/Gemfile
new file mode 100644
index 00000000000..1dac1013775
--- /dev/null
+++ b/gemfiles/rails_72/Gemfile
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+source "https://rubygems.org", cooldown: 5
+
+group :development, :test do
+ gem "rake"
+
+ gem "cancancan"
+ gem "pundit"
+
+ gem "draper"
+ gem "devise"
+
+ gem "rails", "~> 7.2.0"
+ # Rails 7.2.4 caps connection_pool below 3, whose keyword-argument API breaks cache stores
+ gem "connection_pool", "< 3" # TODO: remove when dropping Rails 7.2 (see rails/rails#56620)
+
+ gem "sprockets-rails"
+ gem "ransack", ">= 4.2.0"
+ gem "formtastic", ">= 6.0.0"
+
+ gem "cssbundling-rails"
+ gem "importmap-rails"
+end
+
+group :test do
+ gem "cuprite"
+ gem "capybara"
+ gem "webrick"
+
+ gem "simplecov", require: false # Test coverage generator. Go to /coverage/ after running tests
+ gem "simplecov-cobertura", require: false
+ gem "cucumber-rails", require: false
+ gem "database_cleaner-active_record"
+ gem "launchy"
+ gem "parallel_tests"
+ gem "rspec-rails"
+ gem "sqlite3", platform: :mri
+
+ # Translations
+ gem "i18n-tasks"
+ gem "i18n-spec"
+ gem "rails-i18n" # Provides default i18n for many languages
+end
+
+gemspec path: "../.."
diff --git a/gemfiles/rails_72/Gemfile.lock b/gemfiles/rails_72/Gemfile.lock
new file mode 100644
index 00000000000..5d6a8b085af
--- /dev/null
+++ b/gemfiles/rails_72/Gemfile.lock
@@ -0,0 +1,457 @@
+PATH
+ remote: ../..
+ specs:
+ activeadmin (4.0.0.beta23)
+ arbre (~> 2.0)
+ csv
+ formtastic (>= 6.0)
+ formtastic_i18n (>= 0.7)
+ inherited_resources (~> 2.0)
+ kaminari (>= 1.2.1)
+ railties (>= 7.2)
+ ransack (>= 4.0)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ actioncable (7.2.4)
+ actionpack (= 7.2.4)
+ activesupport (= 7.2.4)
+ nio4r (~> 2.0)
+ websocket-driver (>= 0.6.1)
+ zeitwerk (~> 2.6)
+ actionmailbox (7.2.4)
+ actionpack (= 7.2.4)
+ activejob (= 7.2.4)
+ activerecord (= 7.2.4)
+ activestorage (= 7.2.4)
+ activesupport (= 7.2.4)
+ mail (>= 2.8.0)
+ actionmailer (7.2.4)
+ actionpack (= 7.2.4)
+ actionview (= 7.2.4)
+ activejob (= 7.2.4)
+ activesupport (= 7.2.4)
+ mail (>= 2.8.0)
+ rails-dom-testing (~> 2.2)
+ actionpack (7.2.4)
+ actionview (= 7.2.4)
+ activesupport (= 7.2.4)
+ cgi
+ nokogiri (>= 1.8.5)
+ racc
+ rack (>= 2.2.4, < 3.3)
+ rack-session (>= 1.0.1)
+ rack-test (>= 0.6.3)
+ rails-dom-testing (~> 2.2)
+ rails-html-sanitizer (~> 1.6)
+ useragent (~> 0.16)
+ actiontext (7.2.4)
+ actionpack (= 7.2.4)
+ activerecord (= 7.2.4)
+ activestorage (= 7.2.4)
+ activesupport (= 7.2.4)
+ globalid (>= 0.6.0)
+ nokogiri (>= 1.8.5)
+ actionview (7.2.4)
+ activesupport (= 7.2.4)
+ builder (~> 3.1)
+ cgi
+ erubi (~> 1.11)
+ rails-dom-testing (~> 2.2)
+ rails-html-sanitizer (~> 1.6)
+ activejob (7.2.4)
+ activesupport (= 7.2.4)
+ globalid (>= 0.3.6)
+ activemodel (7.2.4)
+ activesupport (= 7.2.4)
+ activemodel-serializers-xml (1.0.3)
+ activemodel (>= 5.0.0.a)
+ activesupport (>= 5.0.0.a)
+ builder (~> 3.1)
+ activerecord (7.2.4)
+ activemodel (= 7.2.4)
+ activesupport (= 7.2.4)
+ timeout (>= 0.4.0)
+ activestorage (7.2.4)
+ actionpack (= 7.2.4)
+ activejob (= 7.2.4)
+ activerecord (= 7.2.4)
+ activesupport (= 7.2.4)
+ marcel (~> 1.0)
+ activesupport (7.2.4)
+ base64
+ benchmark (>= 0.3)
+ bigdecimal
+ concurrent-ruby (~> 1.0, >= 1.3.1)
+ connection_pool (>= 2.2.5, < 3)
+ drb
+ i18n (>= 1.6, < 2)
+ logger (>= 1.4.2)
+ minitest (>= 5.1, < 6)
+ securerandom (>= 0.3)
+ tzinfo (~> 2.0, >= 2.0.5)
+ addressable (2.9.0)
+ public_suffix (>= 2.0.2, < 8.0)
+ arbre (2.2.1)
+ activesupport (>= 7.0)
+ ast (2.4.3)
+ base64 (0.3.0)
+ bcrypt (3.1.22)
+ benchmark (0.5.0)
+ bigdecimal (4.1.3)
+ builder (3.3.0)
+ cancancan (3.6.1)
+ capybara (3.40.0)
+ addressable
+ matrix
+ mini_mime (>= 0.1.3)
+ nokogiri (~> 1.11)
+ rack (>= 1.6.0)
+ rack-test (>= 0.6.3)
+ regexp_parser (>= 1.5, < 3.0)
+ xpath (~> 3.2)
+ cgi (0.5.2)
+ childprocess (5.1.0)
+ logger (~> 1.5)
+ concurrent-ruby (1.3.8)
+ connection_pool (2.5.5)
+ crass (1.0.7)
+ cssbundling-rails (1.4.3)
+ railties (>= 6.0.0)
+ csv (3.3.6)
+ cucumber (11.1.1)
+ base64 (~> 0.2)
+ builder (~> 3.2)
+ cucumber-ci-environment (> 9, < 12)
+ cucumber-core (>= 16.2.0, < 17)
+ cucumber-cucumber-expressions (> 17, < 20)
+ cucumber-html-formatter (> 21, < 24)
+ diff-lcs (~> 1.5)
+ logger (~> 1.6)
+ mini_mime (~> 1.1)
+ multi_test (~> 1.1)
+ sys-uname (~> 1.5)
+ cucumber-ci-environment (11.0.0)
+ cucumber-core (16.2.0)
+ cucumber-gherkin (> 36, < 40)
+ cucumber-messages (> 31, < 33)
+ cucumber-tag-expressions (> 6, < 9)
+ cucumber-cucumber-expressions (19.0.1)
+ bigdecimal
+ cucumber-gherkin (39.1.0)
+ cucumber-messages (>= 31, < 33)
+ cucumber-html-formatter (23.1.0)
+ cucumber-messages (> 23, < 33)
+ cucumber-messages (32.3.1)
+ cucumber-rails (4.1.0)
+ capybara (>= 3.25, < 4)
+ cucumber (>= 7, < 12)
+ railties (>= 6.1, < 9)
+ cucumber-tag-expressions (8.1.0)
+ cuprite (0.18)
+ capybara (~> 3.0)
+ ferrum (~> 0.18.0)
+ database_cleaner-active_record (2.2.2)
+ activerecord (>= 5.a)
+ database_cleaner-core (~> 2.0)
+ database_cleaner-core (2.1.0)
+ date (3.5.1)
+ devise (5.0.4)
+ bcrypt (~> 3.0)
+ orm_adapter (~> 0.1)
+ railties (>= 7.0)
+ responders
+ warden (~> 1.2.3)
+ diff-lcs (1.6.2)
+ draper (4.0.6)
+ actionpack (>= 5.0)
+ activemodel (>= 5.0)
+ activemodel-serializers-xml (>= 1.0)
+ activesupport (>= 5.0)
+ request_store (>= 1.0)
+ ruby2_keywords
+ drb (2.2.3)
+ erb (6.0.7)
+ erubi (1.13.1)
+ ferrum (0.18.0)
+ addressable (~> 2.5)
+ base64 (~> 0.2)
+ concurrent-ruby (~> 1.1)
+ websocket-driver (~> 0.7)
+ ffi (1.17.4-aarch64-linux-gnu)
+ ffi (1.17.4-arm64-darwin)
+ ffi (1.17.4-x86_64-darwin)
+ ffi (1.17.4-x86_64-linux-gnu)
+ formtastic (6.0.0)
+ actionpack (>= 7.2.0)
+ formtastic_i18n (0.7.0)
+ globalid (1.4.0)
+ activesupport (>= 6.1)
+ has_scope (0.9.0)
+ actionpack (>= 7.0)
+ activesupport (>= 7.0)
+ highline (3.1.2)
+ reline
+ i18n (1.15.2)
+ concurrent-ruby (~> 1.0)
+ i18n-spec (0.6.0)
+ iso
+ i18n-tasks (1.1.2)
+ activesupport (>= 4.0.2)
+ ast (>= 2.1.0)
+ erubi
+ highline (>= 3.0.0)
+ i18n
+ parser (>= 3.2.2.1)
+ prism
+ rails-i18n
+ rainbow (>= 2.2.2, < 4.0)
+ ruby-progressbar (~> 1.8, >= 1.8.1)
+ terminal-table (>= 1.5.1)
+ importmap-rails (2.2.3)
+ actionpack (>= 6.0.0)
+ activesupport (>= 6.0.0)
+ railties (>= 6.0.0)
+ inherited_resources (2.1.0)
+ actionpack (>= 7.0)
+ has_scope (>= 0.6)
+ railties (>= 7.0)
+ responders (>= 2)
+ io-console (0.9.4)
+ irb (1.18.0)
+ pp (>= 0.6.0)
+ prism (>= 1.3.0)
+ rdoc (>= 4.0.0)
+ reline (>= 0.4.2)
+ iso (0.4.0)
+ i18n
+ kaminari (1.2.2)
+ activesupport (>= 4.1.0)
+ kaminari-actionview (= 1.2.2)
+ kaminari-activerecord (= 1.2.2)
+ kaminari-core (= 1.2.2)
+ kaminari-actionview (1.2.2)
+ actionview
+ kaminari-core (= 1.2.2)
+ kaminari-activerecord (1.2.2)
+ activerecord
+ kaminari-core (= 1.2.2)
+ kaminari-core (1.2.2)
+ launchy (3.1.1)
+ addressable (~> 2.8)
+ childprocess (~> 5.0)
+ logger (~> 1.6)
+ logger (1.7.0)
+ loofah (2.25.2)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.12.0)
+ mail (2.9.1)
+ logger
+ mini_mime (>= 0.1.1)
+ net-imap
+ net-pop
+ net-smtp
+ marcel (1.2.1)
+ matrix (0.4.3)
+ memoist3 (1.0.0)
+ mini_mime (1.1.5)
+ minitest (5.27.0)
+ multi_test (1.1.0)
+ net-imap (0.6.7)
+ date
+ net-protocol
+ net-pop (0.1.2)
+ net-protocol
+ net-protocol (0.4.0)
+ timeout
+ net-smtp (0.5.2)
+ net-protocol
+ nio4r (2.7.5)
+ nokogiri (1.19.4-aarch64-linux-gnu)
+ racc (~> 1.4)
+ nokogiri (1.19.4-arm64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.19.4-x86_64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.19.4-x86_64-linux-gnu)
+ racc (~> 1.4)
+ orm_adapter (0.5.0)
+ parallel (2.3.0)
+ parallel_tests (5.8.0)
+ parallel
+ parser (3.3.12.0)
+ ast (~> 2.4.1)
+ racc
+ pp (0.6.4)
+ prettyprint
+ prettyprint (0.2.0)
+ prism (1.9.0)
+ public_suffix (7.0.5)
+ pundit (2.5.2)
+ activesupport (>= 3.0.0)
+ racc (1.8.1)
+ rack (3.2.7)
+ rack-session (2.1.2)
+ base64 (>= 0.1.0)
+ rack (>= 3.0.0)
+ rack-test (2.2.0)
+ rack (>= 1.3)
+ rackup (2.3.1)
+ rack (>= 3)
+ rails (7.2.4)
+ actioncable (= 7.2.4)
+ actionmailbox (= 7.2.4)
+ actionmailer (= 7.2.4)
+ actionpack (= 7.2.4)
+ actiontext (= 7.2.4)
+ actionview (= 7.2.4)
+ activejob (= 7.2.4)
+ activemodel (= 7.2.4)
+ activerecord (= 7.2.4)
+ activestorage (= 7.2.4)
+ activesupport (= 7.2.4)
+ bundler (>= 1.15.0)
+ railties (= 7.2.4)
+ rails-dom-testing (2.3.0)
+ activesupport (>= 5.0.0)
+ minitest
+ nokogiri (>= 1.6)
+ rails-html-sanitizer (1.7.1)
+ loofah (~> 2.25, >= 2.25.2)
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
+ rails-i18n (7.0.10)
+ i18n (>= 0.7, < 2)
+ railties (>= 6.0.0, < 8)
+ railties (7.2.4)
+ actionpack (= 7.2.4)
+ activesupport (= 7.2.4)
+ cgi
+ irb (~> 1.13)
+ rackup (>= 1.0.0)
+ rake (>= 12.2)
+ thor (~> 1.0, >= 1.2.2)
+ tsort (>= 0.2)
+ zeitwerk (~> 2.6)
+ rainbow (3.1.1)
+ rake (13.4.2)
+ ransack (5.0.2)
+ activerecord (>= 7.2)
+ activesupport (>= 7.2)
+ i18n
+ rbs (4.2.0)
+ logger
+ prism (>= 1.6.0)
+ tsort
+ rdoc (8.1.0)
+ erb
+ prism (>= 1.6.0)
+ rbs (>= 4.0.0)
+ tsort
+ regexp_parser (2.13.1)
+ reline (0.7.0)
+ io-console (~> 0.5)
+ request_store (1.7.0)
+ rack (>= 1.4)
+ responders (3.2.1)
+ actionpack (>= 7.0)
+ railties (>= 7.0)
+ rexml (3.4.4)
+ rspec-core (3.13.6)
+ rspec-support (~> 3.13.0)
+ rspec-expectations (3.13.5)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.13.0)
+ rspec-mocks (3.13.8)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.13.0)
+ rspec-rails (8.0.4)
+ actionpack (>= 7.2)
+ activesupport (>= 7.2)
+ railties (>= 7.2)
+ rspec-core (>= 3.13.0, < 5.0.0)
+ rspec-expectations (>= 3.13.0, < 5.0.0)
+ rspec-mocks (>= 3.13.0, < 5.0.0)
+ rspec-support (>= 3.13.0, < 5.0.0)
+ rspec-support (3.13.7)
+ ruby-progressbar (1.13.0)
+ ruby2_keywords (0.0.5)
+ securerandom (0.4.1)
+ simplecov (1.3.2)
+ simplecov-cobertura (4.0.0)
+ rexml
+ simplecov (~> 1.0)
+ sprockets (4.4.1)
+ concurrent-ruby (~> 1.1)
+ logger
+ rack (>= 2.2.4, < 4)
+ sprockets-rails (3.5.2)
+ actionpack (>= 6.1)
+ activesupport (>= 6.1)
+ sprockets (>= 3.0.0)
+ sqlite3 (2.9.6-aarch64-linux-gnu)
+ sqlite3 (2.9.6-arm64-darwin)
+ sqlite3 (2.9.6-x86_64-darwin)
+ sqlite3 (2.9.6-x86_64-linux-gnu)
+ sys-uname (1.5.1)
+ ffi (~> 1.1)
+ memoist3 (~> 1.0.0)
+ terminal-table (4.0.0)
+ unicode-display_width (>= 1.1.1, < 4)
+ thor (1.5.0)
+ timeout (0.6.1)
+ tsort (0.2.0)
+ tzinfo (2.0.6)
+ concurrent-ruby (~> 1.0)
+ unicode-display_width (3.3.0)
+ unicode-emoji (~> 4.3)
+ unicode-emoji (4.3.0)
+ useragent (0.16.11)
+ warden (1.2.9)
+ rack (>= 2.0.9)
+ webrick (1.9.2)
+ websocket-driver (0.8.2)
+ base64
+ websocket-extensions (>= 0.1.0)
+ websocket-extensions (0.1.5)
+ xpath (3.2.0)
+ nokogiri (~> 1.8)
+ zeitwerk (2.8.3)
+
+PLATFORMS
+ aarch64-linux
+ arm64-darwin
+ x86_64-darwin
+ x86_64-linux
+
+DEPENDENCIES
+ activeadmin!
+ cancancan
+ capybara
+ connection_pool (< 3)
+ cssbundling-rails
+ cucumber-rails
+ cuprite
+ database_cleaner-active_record
+ devise
+ draper
+ formtastic (>= 6.0.0)
+ i18n-spec
+ i18n-tasks
+ importmap-rails
+ launchy
+ parallel_tests
+ pundit
+ rails (~> 7.2.0)
+ rails-i18n
+ rake
+ ransack (>= 4.2.0)
+ rspec-rails
+ simplecov
+ simplecov-cobertura
+ sprockets-rails
+ sqlite3
+ webrick
+
+BUNDLED WITH
+ 4.0.22
diff --git a/gemfiles/rails_80/Gemfile b/gemfiles/rails_80/Gemfile
new file mode 100644
index 00000000000..278719f6a40
--- /dev/null
+++ b/gemfiles/rails_80/Gemfile
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+source "https://rubygems.org", cooldown: 5
+
+group :development, :test do
+ gem "rake"
+
+ gem "cancancan"
+ gem "pundit"
+
+ gem "draper"
+ gem "devise"
+
+ gem "rails", "~> 8.0.0"
+
+ gem "sprockets-rails"
+ gem "ransack", ">= 4.2.0"
+ gem "formtastic", ">= 6.0.0"
+
+ gem "cssbundling-rails"
+ gem "importmap-rails"
+end
+
+group :test do
+ gem "cuprite"
+ gem "capybara"
+ gem "webrick"
+
+ gem "simplecov", require: false # Test coverage generator. Go to /coverage/ after running tests
+ gem "simplecov-cobertura", require: false
+ gem "cucumber-rails", require: false
+ gem "database_cleaner-active_record"
+ gem "launchy"
+ gem "parallel_tests"
+ gem "rspec-rails"
+ gem "sqlite3", platform: :mri
+
+ # Translations
+ gem "i18n-tasks"
+ gem "i18n-spec"
+ gem "rails-i18n" # Provides default i18n for many languages
+end
+
+gemspec path: "../.."
diff --git a/gemfiles/rails_80/Gemfile.lock b/gemfiles/rails_80/Gemfile.lock
new file mode 100644
index 00000000000..472f0b966ea
--- /dev/null
+++ b/gemfiles/rails_80/Gemfile.lock
@@ -0,0 +1,455 @@
+PATH
+ remote: ../..
+ specs:
+ activeadmin (4.0.0.beta23)
+ arbre (~> 2.0)
+ csv
+ formtastic (>= 6.0)
+ formtastic_i18n (>= 0.7)
+ inherited_resources (~> 2.0)
+ kaminari (>= 1.2.1)
+ railties (>= 7.2)
+ ransack (>= 4.0)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ actioncable (8.0.5.1)
+ actionpack (= 8.0.5.1)
+ activesupport (= 8.0.5.1)
+ nio4r (~> 2.0)
+ websocket-driver (>= 0.6.1)
+ zeitwerk (~> 2.6)
+ actionmailbox (8.0.5.1)
+ actionpack (= 8.0.5.1)
+ activejob (= 8.0.5.1)
+ activerecord (= 8.0.5.1)
+ activestorage (= 8.0.5.1)
+ activesupport (= 8.0.5.1)
+ mail (>= 2.8.0)
+ actionmailer (8.0.5.1)
+ actionpack (= 8.0.5.1)
+ actionview (= 8.0.5.1)
+ activejob (= 8.0.5.1)
+ activesupport (= 8.0.5.1)
+ mail (>= 2.8.0)
+ rails-dom-testing (~> 2.2)
+ actionpack (8.0.5.1)
+ actionview (= 8.0.5.1)
+ activesupport (= 8.0.5.1)
+ nokogiri (>= 1.8.5)
+ rack (>= 2.2.4)
+ rack-session (>= 1.0.1)
+ rack-test (>= 0.6.3)
+ rails-dom-testing (~> 2.2)
+ rails-html-sanitizer (~> 1.6)
+ useragent (~> 0.16)
+ actiontext (8.0.5.1)
+ actionpack (= 8.0.5.1)
+ activerecord (= 8.0.5.1)
+ activestorage (= 8.0.5.1)
+ activesupport (= 8.0.5.1)
+ globalid (>= 0.6.0)
+ nokogiri (>= 1.8.5)
+ actionview (8.0.5.1)
+ activesupport (= 8.0.5.1)
+ builder (~> 3.1)
+ erubi (~> 1.11)
+ rails-dom-testing (~> 2.2)
+ rails-html-sanitizer (~> 1.6)
+ activejob (8.0.5.1)
+ activesupport (= 8.0.5.1)
+ globalid (>= 0.3.6)
+ activemodel (8.0.5.1)
+ activesupport (= 8.0.5.1)
+ activemodel-serializers-xml (1.0.3)
+ activemodel (>= 5.0.0.a)
+ activesupport (>= 5.0.0.a)
+ builder (~> 3.1)
+ activerecord (8.0.5.1)
+ activemodel (= 8.0.5.1)
+ activesupport (= 8.0.5.1)
+ timeout (>= 0.4.0)
+ activestorage (8.0.5.1)
+ actionpack (= 8.0.5.1)
+ activejob (= 8.0.5.1)
+ activerecord (= 8.0.5.1)
+ activesupport (= 8.0.5.1)
+ marcel (~> 1.0)
+ activesupport (8.0.5.1)
+ base64
+ benchmark (>= 0.3)
+ bigdecimal
+ concurrent-ruby (~> 1.0, >= 1.3.1)
+ connection_pool (>= 2.2.5)
+ drb
+ i18n (>= 1.6, < 2)
+ logger (>= 1.4.2)
+ minitest (>= 5.1)
+ securerandom (>= 0.3)
+ tzinfo (~> 2.0, >= 2.0.5)
+ uri (>= 0.13.1)
+ addressable (2.9.0)
+ public_suffix (>= 2.0.2, < 8.0)
+ arbre (2.2.1)
+ activesupport (>= 7.0)
+ ast (2.4.3)
+ base64 (0.3.0)
+ bcrypt (3.1.22)
+ benchmark (0.5.0)
+ bigdecimal (4.1.3)
+ builder (3.3.0)
+ cancancan (3.6.1)
+ capybara (3.40.0)
+ addressable
+ matrix
+ mini_mime (>= 0.1.3)
+ nokogiri (~> 1.11)
+ rack (>= 1.6.0)
+ rack-test (>= 0.6.3)
+ regexp_parser (>= 1.5, < 3.0)
+ xpath (~> 3.2)
+ childprocess (5.1.0)
+ logger (~> 1.5)
+ concurrent-ruby (1.3.8)
+ connection_pool (3.0.2)
+ crass (1.0.7)
+ cssbundling-rails (1.4.3)
+ railties (>= 6.0.0)
+ csv (3.3.6)
+ cucumber (11.1.1)
+ base64 (~> 0.2)
+ builder (~> 3.2)
+ cucumber-ci-environment (> 9, < 12)
+ cucumber-core (>= 16.2.0, < 17)
+ cucumber-cucumber-expressions (> 17, < 20)
+ cucumber-html-formatter (> 21, < 24)
+ diff-lcs (~> 1.5)
+ logger (~> 1.6)
+ mini_mime (~> 1.1)
+ multi_test (~> 1.1)
+ sys-uname (~> 1.5)
+ cucumber-ci-environment (11.0.0)
+ cucumber-core (16.2.0)
+ cucumber-gherkin (> 36, < 40)
+ cucumber-messages (> 31, < 33)
+ cucumber-tag-expressions (> 6, < 9)
+ cucumber-cucumber-expressions (19.0.1)
+ bigdecimal
+ cucumber-gherkin (39.1.0)
+ cucumber-messages (>= 31, < 33)
+ cucumber-html-formatter (23.1.0)
+ cucumber-messages (> 23, < 33)
+ cucumber-messages (32.3.1)
+ cucumber-rails (4.1.0)
+ capybara (>= 3.25, < 4)
+ cucumber (>= 7, < 12)
+ railties (>= 6.1, < 9)
+ cucumber-tag-expressions (8.1.0)
+ cuprite (0.18)
+ capybara (~> 3.0)
+ ferrum (~> 0.18.0)
+ database_cleaner-active_record (2.2.2)
+ activerecord (>= 5.a)
+ database_cleaner-core (~> 2.0)
+ database_cleaner-core (2.1.0)
+ date (3.5.1)
+ devise (5.0.4)
+ bcrypt (~> 3.0)
+ orm_adapter (~> 0.1)
+ railties (>= 7.0)
+ responders
+ warden (~> 1.2.3)
+ diff-lcs (1.6.2)
+ draper (4.0.6)
+ actionpack (>= 5.0)
+ activemodel (>= 5.0)
+ activemodel-serializers-xml (>= 1.0)
+ activesupport (>= 5.0)
+ request_store (>= 1.0)
+ ruby2_keywords
+ drb (2.2.3)
+ erb (6.0.7)
+ erubi (1.13.1)
+ ferrum (0.18.0)
+ addressable (~> 2.5)
+ base64 (~> 0.2)
+ concurrent-ruby (~> 1.1)
+ websocket-driver (~> 0.7)
+ ffi (1.17.4-aarch64-linux-gnu)
+ ffi (1.17.4-arm64-darwin)
+ ffi (1.17.4-x86_64-darwin)
+ ffi (1.17.4-x86_64-linux-gnu)
+ formtastic (6.0.0)
+ actionpack (>= 7.2.0)
+ formtastic_i18n (0.7.0)
+ globalid (1.4.0)
+ activesupport (>= 6.1)
+ has_scope (0.9.0)
+ actionpack (>= 7.0)
+ activesupport (>= 7.0)
+ highline (3.1.2)
+ reline
+ i18n (1.15.2)
+ concurrent-ruby (~> 1.0)
+ i18n-spec (0.6.0)
+ iso
+ i18n-tasks (1.1.2)
+ activesupport (>= 4.0.2)
+ ast (>= 2.1.0)
+ erubi
+ highline (>= 3.0.0)
+ i18n
+ parser (>= 3.2.2.1)
+ prism
+ rails-i18n
+ rainbow (>= 2.2.2, < 4.0)
+ ruby-progressbar (~> 1.8, >= 1.8.1)
+ terminal-table (>= 1.5.1)
+ importmap-rails (2.2.3)
+ actionpack (>= 6.0.0)
+ activesupport (>= 6.0.0)
+ railties (>= 6.0.0)
+ inherited_resources (2.1.0)
+ actionpack (>= 7.0)
+ has_scope (>= 0.6)
+ railties (>= 7.0)
+ responders (>= 2)
+ io-console (0.9.4)
+ irb (1.18.0)
+ pp (>= 0.6.0)
+ prism (>= 1.3.0)
+ rdoc (>= 4.0.0)
+ reline (>= 0.4.2)
+ iso (0.4.0)
+ i18n
+ kaminari (1.2.2)
+ activesupport (>= 4.1.0)
+ kaminari-actionview (= 1.2.2)
+ kaminari-activerecord (= 1.2.2)
+ kaminari-core (= 1.2.2)
+ kaminari-actionview (1.2.2)
+ actionview
+ kaminari-core (= 1.2.2)
+ kaminari-activerecord (1.2.2)
+ activerecord
+ kaminari-core (= 1.2.2)
+ kaminari-core (1.2.2)
+ launchy (3.1.1)
+ addressable (~> 2.8)
+ childprocess (~> 5.0)
+ logger (~> 1.6)
+ logger (1.7.0)
+ loofah (2.25.2)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.12.0)
+ mail (2.9.1)
+ logger
+ mini_mime (>= 0.1.1)
+ net-imap
+ net-pop
+ net-smtp
+ marcel (1.2.1)
+ matrix (0.4.3)
+ memoist3 (1.0.0)
+ mini_mime (1.1.5)
+ minitest (6.0.6)
+ drb (~> 2.0)
+ prism (~> 1.5)
+ multi_test (1.1.0)
+ net-imap (0.6.7)
+ date
+ net-protocol
+ net-pop (0.1.2)
+ net-protocol
+ net-protocol (0.4.0)
+ timeout
+ net-smtp (0.5.2)
+ net-protocol
+ nio4r (2.7.5)
+ nokogiri (1.19.4-aarch64-linux-gnu)
+ racc (~> 1.4)
+ nokogiri (1.19.4-arm64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.19.4-x86_64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.19.4-x86_64-linux-gnu)
+ racc (~> 1.4)
+ orm_adapter (0.5.0)
+ parallel (2.3.0)
+ parallel_tests (5.8.0)
+ parallel
+ parser (3.3.12.0)
+ ast (~> 2.4.1)
+ racc
+ pp (0.6.4)
+ prettyprint
+ prettyprint (0.2.0)
+ prism (1.9.0)
+ public_suffix (7.0.5)
+ pundit (2.5.2)
+ activesupport (>= 3.0.0)
+ racc (1.8.1)
+ rack (3.2.7)
+ rack-session (2.1.2)
+ base64 (>= 0.1.0)
+ rack (>= 3.0.0)
+ rack-test (2.2.0)
+ rack (>= 1.3)
+ rackup (2.3.1)
+ rack (>= 3)
+ rails (8.0.5.1)
+ actioncable (= 8.0.5.1)
+ actionmailbox (= 8.0.5.1)
+ actionmailer (= 8.0.5.1)
+ actionpack (= 8.0.5.1)
+ actiontext (= 8.0.5.1)
+ actionview (= 8.0.5.1)
+ activejob (= 8.0.5.1)
+ activemodel (= 8.0.5.1)
+ activerecord (= 8.0.5.1)
+ activestorage (= 8.0.5.1)
+ activesupport (= 8.0.5.1)
+ bundler (>= 1.15.0)
+ railties (= 8.0.5.1)
+ rails-dom-testing (2.3.0)
+ activesupport (>= 5.0.0)
+ minitest
+ nokogiri (>= 1.6)
+ rails-html-sanitizer (1.7.1)
+ loofah (~> 2.25, >= 2.25.2)
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
+ rails-i18n (8.1.0)
+ i18n (>= 0.7, < 2)
+ railties (>= 8.0.0, < 9)
+ railties (8.0.5.1)
+ actionpack (= 8.0.5.1)
+ activesupport (= 8.0.5.1)
+ irb (~> 1.13)
+ rackup (>= 1.0.0)
+ rake (>= 12.2)
+ thor (~> 1.0, >= 1.2.2)
+ tsort (>= 0.2)
+ zeitwerk (~> 2.6)
+ rainbow (3.1.1)
+ rake (13.4.2)
+ ransack (5.0.2)
+ activerecord (>= 7.2)
+ activesupport (>= 7.2)
+ i18n
+ rbs (4.2.0)
+ logger
+ prism (>= 1.6.0)
+ tsort
+ rdoc (8.1.0)
+ erb
+ prism (>= 1.6.0)
+ rbs (>= 4.0.0)
+ tsort
+ regexp_parser (2.13.1)
+ reline (0.7.0)
+ io-console (~> 0.5)
+ request_store (1.7.0)
+ rack (>= 1.4)
+ responders (3.2.1)
+ actionpack (>= 7.0)
+ railties (>= 7.0)
+ rexml (3.4.4)
+ rspec-core (3.13.6)
+ rspec-support (~> 3.13.0)
+ rspec-expectations (3.13.5)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.13.0)
+ rspec-mocks (3.13.8)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.13.0)
+ rspec-rails (8.0.4)
+ actionpack (>= 7.2)
+ activesupport (>= 7.2)
+ railties (>= 7.2)
+ rspec-core (>= 3.13.0, < 5.0.0)
+ rspec-expectations (>= 3.13.0, < 5.0.0)
+ rspec-mocks (>= 3.13.0, < 5.0.0)
+ rspec-support (>= 3.13.0, < 5.0.0)
+ rspec-support (3.13.7)
+ ruby-progressbar (1.13.0)
+ ruby2_keywords (0.0.5)
+ securerandom (0.4.1)
+ simplecov (1.3.2)
+ simplecov-cobertura (4.0.0)
+ rexml
+ simplecov (~> 1.0)
+ sprockets (4.4.1)
+ concurrent-ruby (~> 1.1)
+ logger
+ rack (>= 2.2.4, < 4)
+ sprockets-rails (3.5.2)
+ actionpack (>= 6.1)
+ activesupport (>= 6.1)
+ sprockets (>= 3.0.0)
+ sqlite3 (2.9.6-aarch64-linux-gnu)
+ sqlite3 (2.9.6-arm64-darwin)
+ sqlite3 (2.9.6-x86_64-darwin)
+ sqlite3 (2.9.6-x86_64-linux-gnu)
+ sys-uname (1.5.1)
+ ffi (~> 1.1)
+ memoist3 (~> 1.0.0)
+ terminal-table (4.0.0)
+ unicode-display_width (>= 1.1.1, < 4)
+ thor (1.5.0)
+ timeout (0.6.1)
+ tsort (0.2.0)
+ tzinfo (2.0.6)
+ concurrent-ruby (~> 1.0)
+ unicode-display_width (3.3.0)
+ unicode-emoji (~> 4.3)
+ unicode-emoji (4.3.0)
+ uri (1.1.1)
+ useragent (0.16.11)
+ warden (1.2.9)
+ rack (>= 2.0.9)
+ webrick (1.9.2)
+ websocket-driver (0.8.2)
+ base64
+ websocket-extensions (>= 0.1.0)
+ websocket-extensions (0.1.5)
+ xpath (3.2.0)
+ nokogiri (~> 1.8)
+ zeitwerk (2.8.3)
+
+PLATFORMS
+ aarch64-linux
+ arm64-darwin
+ x86_64-darwin
+ x86_64-linux
+
+DEPENDENCIES
+ activeadmin!
+ cancancan
+ capybara
+ cssbundling-rails
+ cucumber-rails
+ cuprite
+ database_cleaner-active_record
+ devise
+ draper
+ formtastic (>= 6.0.0)
+ i18n-spec
+ i18n-tasks
+ importmap-rails
+ launchy
+ parallel_tests
+ pundit
+ rails (~> 8.0.0)
+ rails-i18n
+ rake
+ ransack (>= 4.2.0)
+ rspec-rails
+ simplecov
+ simplecov-cobertura
+ sprockets-rails
+ sqlite3
+ webrick
+
+BUNDLED WITH
+ 4.0.22
diff --git a/lib/active_admin.rb b/lib/active_admin.rb
index 05734224f2c..148f34468e2 100644
--- a/lib/active_admin.rb
+++ b/lib/active_admin.rb
@@ -1,65 +1,71 @@
-require 'meta_search'
-require 'bourbon'
-require 'devise'
-require 'kaminari'
-require 'formtastic'
-require 'sass'
-require 'inherited_resources'
-require 'jquery-rails'
-require 'arbre'
-require 'active_admin/dependency_checker'
-require 'active_admin/sass/helpers'
-require 'active_admin/engine'
+# frozen_string_literal: true
+require "active_support/core_ext"
+require "set"
+
+require "ransack"
+require "kaminari"
+require "formtastic"
+require "formtastic_i18n"
+require "inherited_resources"
+require "arbre"
+
+begin
+ require "importmap-rails"
+rescue LoadError
+ # importmap-rails is optional
+end
module ActiveAdmin
- autoload :VERSION, 'active_admin/version'
- autoload :Application, 'active_admin/application'
- autoload :AssetRegistration, 'active_admin/asset_registration'
- autoload :Authorization, 'active_admin/authorization_adapter'
- autoload :AuthorizationAdapter, 'active_admin/authorization_adapter'
- autoload :Breadcrumbs, 'active_admin/breadcrumbs'
- autoload :CanCanAdapter, 'active_admin/cancan_adapter'
- autoload :Callbacks, 'active_admin/callbacks'
- autoload :Component, 'active_admin/component'
- autoload :BaseController, 'active_admin/base_controller'
- autoload :ControllerAction, 'active_admin/controller_action'
- autoload :CSVBuilder, 'active_admin/csv_builder'
- autoload :Deprecation, 'active_admin/deprecation'
- autoload :Devise, 'active_admin/devise'
- autoload :DSL, 'active_admin/dsl'
- autoload :Event, 'active_admin/event'
- autoload :FormBuilder, 'active_admin/form_builder'
- autoload :Inputs, 'active_admin/inputs'
- autoload :Iconic, 'active_admin/iconic'
- autoload :Menu, 'active_admin/menu'
- autoload :MenuCollection, 'active_admin/menu_collection'
- autoload :MenuItem, 'active_admin/menu_item'
- autoload :Namespace, 'active_admin/namespace'
- autoload :Page, 'active_admin/page'
- autoload :PagePresenter, 'active_admin/page_presenter'
- autoload :PageController, 'active_admin/page_controller'
- autoload :PageDSL, 'active_admin/page_dsl'
- autoload :Reloader, 'active_admin/reloader'
- autoload :Resource, 'active_admin/resource'
- autoload :ResourceController, 'active_admin/resource_controller'
- autoload :ResourceDSL, 'active_admin/resource_dsl'
- autoload :Scope, 'active_admin/scope'
- autoload :ScopeChain, 'active_admin/helpers/scope_chain'
- autoload :SidebarSection, 'active_admin/sidebar_section'
- autoload :TableBuilder, 'active_admin/table_builder'
- autoload :ViewFactory, 'active_admin/view_factory'
- autoload :ViewHelpers, 'active_admin/view_helpers'
- autoload :Views, 'active_admin/views'
+ autoload :VERSION, "active_admin/version"
+ autoload :Application, "active_admin/application"
+ autoload :Authorization, "active_admin/authorization_adapter"
+ autoload :AuthorizationAdapter, "active_admin/authorization_adapter"
+ autoload :Callbacks, "active_admin/callbacks"
+ autoload :Component, "active_admin/component"
+ autoload :CanCanAdapter, "active_admin/cancan_adapter"
+ autoload :ControllerAction, "active_admin/controller_action"
+ autoload :CSVBuilder, "active_admin/csv_builder"
+ autoload :Dependency, "active_admin/dependency"
+ autoload :Devise, "active_admin/devise"
+ autoload :DSL, "active_admin/dsl"
+ autoload :FormBuilder, "active_admin/form_builder"
+ autoload :Inputs, "active_admin/inputs"
+ autoload :Localizers, "active_admin/localizers"
+ autoload :Menu, "active_admin/menu"
+ autoload :MenuCollection, "active_admin/menu_collection"
+ autoload :MenuItem, "active_admin/menu_item"
+ autoload :Namespace, "active_admin/namespace"
+ autoload :OrderClause, "active_admin/order_clause"
+ autoload :Page, "active_admin/page"
+ autoload :PagePresenter, "active_admin/page_presenter"
+ autoload :PageDSL, "active_admin/page_dsl"
+ autoload :PunditAdapter, "active_admin/pundit_adapter"
+ autoload :Resource, "active_admin/resource"
+ autoload :ResourceDSL, "active_admin/resource_dsl"
+ autoload :Scope, "active_admin/scope"
+ autoload :ScopeChain, "active_admin/helpers/scope_chain"
+ autoload :SidebarSection, "active_admin/sidebar_section"
+ autoload :TableBuilder, "active_admin/table_builder"
+ autoload :ViewHelpers, "active_admin/view_helpers"
+ autoload :Views, "active_admin/views"
class << self
- attr_accessor :application
+ attr_accessor :application, :importmap
def application
@application ||= ::ActiveAdmin::Application.new
end
+ def deprecator
+ @deprecator ||= ActiveSupport::Deprecation.new("4.1", "active-admin")
+ end
+
+ def importmap
+ @importmap ||= Importmap::Map.new
+ end
+
# Gets called within the initializer
def setup
application.setup!
@@ -67,17 +73,11 @@ def setup
application.prepare!
end
- delegate :register, :to => :application
- delegate :register_page, :to => :application
- delegate :unload!, :to => :application
- delegate :load!, :to => :application
- delegate :routes, :to => :application
-
- # Returns true if this rails application has the asset
- # pipeline enabled.
- def use_asset_pipeline?
- DependencyChecker.rails_3_1? && Rails.application.config.try(:assets).try(:enabled)
- end
+ delegate :register, to: :application
+ delegate :register_page, to: :application
+ delegate :unload!, to: :application
+ delegate :load!, to: :application
+ delegate :routes, to: :application
# A callback is triggered each time (before) Active Admin loads the configuration files.
# In development mode, this will happen whenever the user changes files. In production
@@ -93,7 +93,7 @@ def use_asset_pipeline?
#
# @param [Block] block A block to call each time (before) AA loads resources
def before_load(&block)
- ActiveAdmin::Event.subscribe ActiveAdmin::Application::BeforeLoadEvent, &block
+ ActiveSupport::Notifications.subscribe ActiveAdmin::Application::BeforeLoadEvent, &wrap_block_for_active_support_notifications(block)
end
# A callback is triggered each time (after) Active Admin loads the configuration files. This
@@ -111,16 +111,26 @@ def before_load(&block)
#
# @param [Block] block A block to call each time (after) AA loads resources
def after_load(&block)
- ActiveAdmin::Event.subscribe ActiveAdmin::Application::AfterLoadEvent, &block
+ ActiveSupport::Notifications.subscribe ActiveAdmin::Application::AfterLoadEvent, &wrap_block_for_active_support_notifications(block)
+ end
+
+ private
+
+ def wrap_block_for_active_support_notifications block
+ proc { |_name, _start, _finish, _id, payload| block.call payload[:active_admin_application] }
end
end
end
-ActiveAdmin::DependencyChecker.check!
+# Require things that don't support autoload
+require_relative "active_admin/engine"
+require_relative "active_admin/error"
+
+# Require internal plugins
+require_relative "active_admin/batch_actions"
+require_relative "active_admin/filters"
-# Require internal Plugins
-require 'active_admin/comments'
-require 'active_admin/batch_actions'
-require 'active_admin/filters'
+# Require ORM-specific plugins
+require_relative "active_admin/orm/active_record" if defined? ActiveRecord
diff --git a/lib/active_admin/abstract_view_factory.rb b/lib/active_admin/abstract_view_factory.rb
deleted file mode 100644
index e5875cae94a..00000000000
--- a/lib/active_admin/abstract_view_factory.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-module ActiveAdmin
- class AbstractViewFactory
- @@default_views = {}
-
- def self.register(view_hash)
- view_hash.each do |view_key, view_class|
- @@default_views[view_key] = view_class
- end
- end
-
- def initialize
- @views = {}
- end
-
- # Register a new view key with the view factory
- #
- # eg:
- #
- # factory = AbstractViewFactory.new
- # factory.register :my_view => SomeViewClass
- #
- # You can setup many at the same time:
- #
- # factory.register :my_view => SomeClass,
- # :another_view => OtherViewClass
- #
- def register(view_hash)
- view_hash.each do |view_key, view_class|
- @views[view_key] = view_class
- end
- end
-
- def default_for(key)
- @@default_views[key.to_sym]
- end
-
- def has_key?(key)
- @views.has_key?(key.to_sym) || @@default_views.has_key?(key.to_sym)
- end
-
- def [](key)
- get_view_for_key(key)
- end
-
- def []=(key, value)
- set_view_for_key(key, value)
- end
-
- # Override respond to to include keys
- def respond_to?(method)
- key = key_from_method_name(method)
- if has_key?(key)
- true
- else
- super
- end
- end
-
- private
-
- def method_missing(method, *args)
- key = key_from_method_name(method)
- if has_key?(key)
- if method.to_s.include?('=')
- self.class_eval <<-EOS
- def #{key}=(value)
- set_view_for_key(:#{key}, value)
- end
- EOS
- else
- self.class_eval <<-EOS
- def #{key}
- get_view_for_key(:#{key})
- end
- EOS
- end
- self.send(method, *args)
- else
- super
- end
- end
-
- def key_from_method_name(method)
- method.to_s.gsub('=', '').to_sym
- end
-
- def get_view_for_key(key)
- @views[key.to_sym] || @@default_views[key.to_sym]
- end
-
- def set_view_for_key(key, view)
- @views[key.to_sym] = view
- end
- end
-end
diff --git a/lib/active_admin/application.rb b/lib/active_admin/application.rb
index 5be537e652e..8be729c29bc 100644
--- a/lib/active_admin/application.rb
+++ b/lib/active_admin/application.rb
@@ -1,131 +1,80 @@
-require 'active_admin/router'
-require 'active_admin/helpers/settings'
+# frozen_string_literal: true
+require_relative "router"
+require_relative "application_settings"
+require_relative "namespace_settings"
module ActiveAdmin
class Application
- include Settings
- include Settings::Inheritance
- settings_inherited_by Namespace
-
- # The default namespace to put controllers and routes inside. Set this
- # in config/initializers/active_admin.rb using:
- #
- # config.default_namespace = :super_admin
- #
- setting :default_namespace, :admin
+ class << self
+ def setting(name, default)
+ ApplicationSettings.register name, default
+ end
- attr_reader :namespaces
- def initialize
- @namespaces = {}
+ def inheritable_setting(name, default)
+ NamespaceSettings.register name, default
+ end
end
- # Load paths for admin configurations. Add folders to this load path
- # to load up other resources for administration. External gems can
- # include their paths in this load path to provide active_admin UIs
- setting :load_paths, [File.expand_path('app/admin', Rails.root)]
-
- # The default number of resources to display on index pages
- inheritable_setting :default_per_page, 30
-
- # The title which gets displayed in the main layout
- inheritable_setting :site_title, ""
-
- # Set the site title link href (defaults to AA dashboard)
- inheritable_setting :site_title_link, ""
-
- # Set the site title image displayed in the main layout (has precendence over :site_title)
- inheritable_setting :site_title_image, ""
-
- # The view factory to use to generate all the view classes. Take
- # a look at ActiveAdmin::ViewFactory
- inheritable_setting :view_factory, ActiveAdmin::ViewFactory.new
-
- # The method to call in controllers to get the current user
- inheritable_setting :current_user_method, false
-
- # The method to call in the controllers to ensure that there
- # is a currently authenticated admin user
- inheritable_setting :authentication_method, false
-
- # The path to log user's out with. If set to a symbol, we assume
- # that it's a method to call which returns the path
- inheritable_setting :logout_link_path, :destroy_admin_user_session_path
-
- # The method to use when generating the link for user logout
- inheritable_setting :logout_link_method, :get
-
- # Whether the batch actions are enabled or not
- inheritable_setting :batch_actions, false
-
- # Whether filters are enabled
- inheritable_setting :filters, true
-
- # The namespace root.
- inheritable_setting :root_to, 'dashboard#index'
-
- # Default CSV options
- inheritable_setting :csv_options, {:col_sep => ','}
-
- # Default Download Links options
- inheritable_setting :download_links, true
-
- # The authorization adapter to use
- inheritable_setting :authorization_adapter, ActiveAdmin::AuthorizationAdapter
-
- # A proc to be used when a user is not authorized to view the current resource
- inheritable_setting :on_unauthorized_access, :rescue_active_admin_access_denied
+ def settings
+ @settings ||= SettingsNode.build(ApplicationSettings)
+ end
- # Active Admin makes educated guesses when displaying objects, this is
- # the list of methods it tries calling in order
- setting :display_name_methods, [ :display_name,
- :full_name,
- :name,
- :username,
- :login,
- :title,
- :email,
- :to_s ]
+ def namespace_settings
+ @namespace_settings ||= SettingsNode.build(NamespaceSettings)
+ end
- # == Deprecated Settings
+ def respond_to_missing?(method, include_private = false)
+ [settings, namespace_settings].any? { |sets| sets.respond_to?(method) } || super
+ end
- # (none currently)
+ def method_missing(method, *args)
+ if settings.respond_to?(method)
+ settings.send(method, *args)
+ elsif namespace_settings.respond_to?(method)
+ namespace_settings.send(method, *args)
+ else
+ super
+ end
+ end
- include AssetRegistration
+ attr_reader :namespaces
+ def initialize
+ @namespaces = Namespace::Store.new
+ end
# Event that gets triggered on load of Active Admin
- BeforeLoadEvent = 'active_admin.application.before_load'.freeze
- AfterLoadEvent = 'active_admin.application.after_load'.freeze
+ BeforeLoadEvent = "active_admin.application.before_load".freeze
+ AfterLoadEvent = "active_admin.application.after_load".freeze
+ # Runs before the app's AA initializer
def setup!
- register_default_assets
end
+ # Runs after the app's AA initializer
def prepare!
remove_active_admin_load_paths_from_rails_autoload_and_eager_load
attach_reloader
- generate_stylesheets
end
# Registers a brand new configuration for the given resource.
def register(resource, options = {}, &block)
- ns_name = namespace_name(options)
- namespace(ns_name).register resource, options, &block
+ ns = options.fetch(:namespace) { default_namespace }
+ namespace(ns).register resource, options, &block
end
# Creates a namespace for the given name
#
# Yields the namespace if a block is given
#
- # @returns [Namespace] the new or existing namespace
+ # @return [Namespace] the new or existing namespace
def namespace(name)
name ||= :root
- if namespaces[name]
- namespace = namespaces[name]
- else
- namespace = namespaces[name] = Namespace.new(self, name)
- ActiveAdmin::Event.dispatch ActiveAdmin::Namespace::RegisterEvent, namespace
+ namespace = namespaces[name.to_sym] ||= begin
+ namespace = Namespace.new(self, name)
+ ActiveSupport::Notifications.instrument ActiveAdmin::Namespace::RegisterEvent, { active_admin_namespace: namespace }
+ namespace
end
yield(namespace) if block_given?
@@ -136,12 +85,12 @@ def namespace(name)
# Register a page
#
# @param name [String] The page name
- # @options [Hash] Accepts option :namespace.
+ # @option [Hash] Accepts option :namespace.
# @&block The registration block.
#
def register_page(name, options = {}, &block)
- ns_name = namespace_name(options)
- namespace(ns_name).register_page name, options, &block
+ ns = options.fetch(:namespace) { default_namespace }
+ namespace(ns).register_page name, options, &block
end
# Whether all configuration files have been loaded
@@ -152,7 +101,7 @@ def loaded?
# Removes all defined controllers from memory. Useful in
# development, where they are reloaded on each request.
def unload!
- namespaces.values.each{ |namespace| namespace.unload! }
+ namespaces.each(&:unload!)
@@loaded = false
end
@@ -160,91 +109,117 @@ def unload!
# To reload everything simply call `ActiveAdmin.unload!`
def load!
unless loaded?
- ActiveAdmin::Event.dispatch BeforeLoadEvent, self # before_load hook
- files.each{ |file| load file } # load files
- namespace(default_namespace) # init AA resources
- ActiveAdmin::Event.dispatch AfterLoadEvent, self # after_load hook
+ ActiveSupport::Notifications.instrument BeforeLoadEvent, { active_admin_application: self } # before_load hook
+ files.each { |file| load file } # load files
+ namespace(default_namespace) # init AA resources
+ ActiveSupport::Notifications.instrument AfterLoadEvent, { active_admin_application: self } # after_load hook
@@loaded = true
end
end
- # Returns ALL the files to be loaded
- def files
- load_paths.flatten.compact.uniq.map{ |path| Dir["#{path}/**/*.rb"] }.flatten
+ def load(file)
+ DatabaseHitDuringLoad.capture { super }
end
- def router
- @router ||= Router.new(self)
+ # Returns ALL the files to be loaded
+ def files
+ load_paths.flatten.compact.uniq.flat_map { |path| Dir["#{path}/**/*.rb"].sort }
end
- # One-liner called by user's config/routes.rb file
+ # Creates all the necessary routes for the ActiveAdmin configurations
+ #
+ # Use this within the routes.rb file:
+ #
+ # Application.routes.draw do |map|
+ # ActiveAdmin.routes(self)
+ # end
+ #
+ # @param rails_router [ActionDispatch::Routing::Mapper]
def routes(rails_router)
load!
- router.apply(rails_router)
+ Router.new(router: rails_router, namespaces: namespaces).apply
end
# Adds before, around and after filters to all controllers.
# Example usage:
- # ActiveAdmin.before_filter :authenticate_admin!
+ # ActiveAdmin.before_action :authenticate_admin!
#
- %w(before_filter skip_before_filter after_filter around_filter).each do |name|
+ AbstractController::Callbacks::ClassMethods.public_instance_methods.
+ select { |m| m.end_with?('_action') }.each do |name|
define_method name do |*args, &block|
- ActiveAdmin::BaseController.send name, *args, &block
- ActiveAdmin::Devise::PasswordsController.send name, *args, &block
- ActiveAdmin::Devise::SessionsController.send name, *args, &block
- ActiveAdmin::Devise::UnlocksController.send name, *args, &block
+ ActiveSupport.on_load(:active_admin_controller) do
+ public_send name, *args, &block
+ end
end
end
- # Helper method to add a dashboard section
- def dashboard_section(name, options = {}, &block)
- ActiveAdmin::Dashboards.add_section(name, options, &block)
+ def controllers_for_filters
+ controllers = [BaseController]
+ controllers.push(*Devise.controllers_for_filters) if Dependency.devise?
+ controllers
end
private
- # Return either the passed in namespace or the default
- def namespace_name(options)
- options.fetch(:namespace){ default_namespace }
- end
-
- def register_default_assets
- register_stylesheet 'active_admin.css', :media => 'screen'
- register_stylesheet 'active_admin/print.css', :media => 'print'
-
- unless ActiveAdmin.use_asset_pipeline?
- register_javascript 'jquery.min.js'
- register_javascript 'jquery-ui.min.js'
- register_javascript 'jquery_ujs.js'
- end
-
- register_javascript 'active_admin.js'
- end
-
- # Since we're dealing with all our own file loading, we need
- # to remove our paths from the ActiveSupport autoload paths.
- # If not, file naming becomes very important and can cause clashes.
+ # Since app/admin is alphabetically before app/models, we have to remove it
+ # from the host app's +autoload_paths+ to prevent missing constant errors.
+ #
+ # As well, we have to remove it from +eager_load_paths+ to prevent the
+ # files from being loaded twice in production.
def remove_active_admin_load_paths_from_rails_autoload_and_eager_load
- ActiveSupport::Dependencies.autoload_paths.reject!{|path| load_paths.include?(path) }
- # Don't eagerload our configs, we'll deal with them ourselves
- Rails.application.config.eager_load_paths = Rails.application.config.eager_load_paths.reject do |path|
- load_paths.include?(path)
- end
+ ActiveSupport::Dependencies.autoload_paths -= load_paths
+ Rails.application.config.eager_load_paths -= load_paths
end
+ # Hook into the Rails code reloading mechanism so that things are reloaded
+ # properly in development mode.
+ #
+ # If any of the app files (e.g. models) has changed, we need to reload all
+ # the admin files. If the admin files themselves has changed, we need to
+ # regenerate the routes as well.
def attach_reloader
- ActiveAdmin::Reloader.build(Rails.application, self, Rails.version).attach!
- end
-
- def generate_stylesheets
- # Create our own asset pipeline in Rails 3.0
- if ActiveAdmin.use_asset_pipeline?
- # Add our mixins to the load path for SASS
- ::Sass::Engine::DEFAULT_OPTIONS[:load_paths] << File.expand_path("../../../app/assets/stylesheets", __FILE__)
- else
- require 'active_admin/sass/css_loader'
- ::Sass::Plugin.add_template_location(File.expand_path("../../../app/assets/stylesheets", __FILE__))
- ::Sass::Plugin.add_template_location(File.expand_path("../sass", __FILE__))
+ Rails.application.config.after_initialize do |app|
+ unload_active_admin = -> { ActiveAdmin.application.unload! }
+
+ if app.config.reload_classes_only_on_change
+ # Rails is about to unload all the app files (e.g. models), so we
+ # should first unload the classes generated by Active Admin, otherwise
+ # they will contain references to the stale (unloaded) classes.
+ ActiveSupport::Reloader.to_prepare(prepend: true, &unload_active_admin)
+ else
+ # If the user has configured the app to always reload app files after
+ # each request, so we should unload the generated classes too.
+ ActiveSupport::Reloader.to_complete(&unload_active_admin)
+ end
+
+ admin_dirs = {}
+
+ load_paths.each do |path|
+ admin_dirs[path] = [:rb]
+ end
+
+ routes_reloader = app.config.file_watcher.new([], admin_dirs) do
+ app.reload_routes!
+ end
+
+ app.reloaders << routes_reloader
+
+ ActiveSupport::Reloader.to_prepare do
+ # Rails might have reloaded the routes for other reasons (e.g.
+ # routes.rb has changed), in which case Active Admin would have been
+ # loaded via the `ActiveAdmin.routes` call in `routes.rb`.
+ #
+ # Otherwise, we should check if any of the admin files are changed
+ # and force the routes to reload if necessary. This would again causes
+ # Active Admin to load via `ActiveAdmin.routes`.
+ #
+ # Finally, if Active Admin is still not loaded at this point, then we
+ # would need to load it manually.
+ unless ActiveAdmin.application.loaded?
+ routes_reloader.execute_if_updated
+ ActiveAdmin.application.load!
+ end
+ end
end
end
end
diff --git a/lib/active_admin/application_settings.rb b/lib/active_admin/application_settings.rb
new file mode 100644
index 00000000000..362705b21fd
--- /dev/null
+++ b/lib/active_admin/application_settings.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+require_relative "settings_node"
+
+module ActiveAdmin
+ class ApplicationSettings < SettingsNode
+
+ # The default namespace to put controllers and routes inside. Set this
+ # in config/initializers/active_admin.rb using:
+ #
+ # config.default_namespace = :super_admin
+ #
+ register :default_namespace, :admin
+
+ register :app_path, Rails.root
+
+ # Load paths for admin configurations. Add folders to this load path
+ # to load up other resources for administration. External gems can
+ # include their paths in this load path to provide active_admin UIs
+ register :load_paths, [File.expand_path("app/admin", Rails.root)]
+
+ # Set default localize format for Date/Time values
+ register :localize_format, :long
+
+ # Active Admin makes educated guesses when displaying objects, this is
+ # the list of methods it tries calling in order
+ # Note that Formtastic also has 'collection_label_methods' similar to this
+ # used by auto generated dropdowns in filter or belongs_to field of Active Admin
+ register :display_name_methods, [ :display_name,
+ :full_name,
+ :name,
+ :username,
+ :login,
+ :title,
+ :email,
+ :to_s ]
+
+ # To make debugging easier, by default don't stream in development
+ register :disable_streaming_in, ["development"]
+
+ # Remove sensitive attributes from being displayed, made editable, or exported by default
+ register :filter_attributes, [:encrypted_password, :password, :password_confirmation]
+ end
+end
diff --git a/lib/active_admin/asset_registration.rb b/lib/active_admin/asset_registration.rb
deleted file mode 100644
index 15dd90cce5a..00000000000
--- a/lib/active_admin/asset_registration.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module ActiveAdmin
- module AssetRegistration
-
- def register_stylesheet(path, options = {})
- stylesheets[path] = options
- end
-
- def stylesheets
- @stylesheets ||= {}
- end
-
- def clear_stylesheets!
- @stylesheets = {}
- end
-
- def register_javascript(name)
- javascripts.add name
- end
-
- def javascripts
- @javascripts ||= Set.new
- end
-
- def clear_javascripts!
- @javascripts = Set.new
- end
-
- end
-end
diff --git a/lib/active_admin/async_count.rb b/lib/active_admin/async_count.rb
new file mode 100644
index 00000000000..6becc62fcd9
--- /dev/null
+++ b/lib/active_admin/async_count.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ class AsyncCount
+ class NotSupportedError < RuntimeError; end
+
+ def initialize(collection)
+ raise NotSupportedError, "#{collection.inspect} does not support :async_count" unless collection.respond_to?(:async_count)
+
+ @collection = collection.except(:select, :order)
+ @promise = @collection.async_count
+ end
+
+ def count
+ @promise.value
+ end
+
+ alias size count
+
+ delegate :except, :group_values, :length, :limit_value, to: :@collection
+ end
+end
diff --git a/lib/active_admin/authorization_adapter.rb b/lib/active_admin/authorization_adapter.rb
index 5d59798ff43..8a2e4d9772e 100644
--- a/lib/active_admin/authorization_adapter.rb
+++ b/lib/active_admin/authorization_adapter.rb
@@ -1,16 +1,18 @@
+# frozen_string_literal: true
module ActiveAdmin
# Default Authorization permissions for Active Admin
module Authorization
- READ = :read
- CREATE = :create
- UPDATE = :update
+ READ = :read
+ NEW = :new
+ CREATE = :create
+ EDIT = :edit
+ UPDATE = :update
DESTROY = :destroy
end
Auth = Authorization
-
# Active Admin's default authorization adapter. This adapter returns true
# for all requests to `#authorized?`. It should be the starting point for
# implementing your own authorization adapter.
@@ -19,7 +21,6 @@ module Authorization
class AuthorizationAdapter
attr_reader :resource, :user
-
# Initialize a new authorization adapter. This happens on each and
# every request to a controller.
#
@@ -47,14 +48,13 @@ def initialize(resource, user)
# the class of the subject also. For example, Active Admin uses the class
# of the resource to decide if the resource should be displayed in the
# global navigation. To deal with this nicely in a case statement, take
- # a look at `#normalized(klasss)`
+ # a look at `#normalized(klass)`
#
- # @returns [Boolean]
+ # @return [Boolean]
def authorized?(action, subject = nil)
true
end
-
# A hook method for authorization libraries to scope the collection. By
# default, we just return the same collection. The returned scope is used
# as the starting point for all queries to the db in the controller.
@@ -66,7 +66,7 @@ def authorized?(action, subject = nil)
# one of the `ActiveAdmin::Auth::*` symbols. Defaults to `Auth::READ` if
# no action passed in.
#
- # @returns [ActiveRecord::Relation] A new collection, scoped to the
+ # @return [ActiveRecord::Relation] A new collection, scoped to the
# objects that the current user has access to.
def scope_collection(collection, action = Auth::READ)
collection
diff --git a/lib/active_admin/base_controller/authorization.rb b/lib/active_admin/base_controller/authorization.rb
deleted file mode 100644
index 9c81facb1c6..00000000000
--- a/lib/active_admin/base_controller/authorization.rb
+++ /dev/null
@@ -1,153 +0,0 @@
-module ActiveAdmin
-
- # Exception class to raise when there is an authorized access
- # exception thrown. The exception has a few goodies that may
- # be useful for capturing / recognizing security issues.
- class AccessDenied < StandardError
- attr_reader :user, :action, :subject
-
- def initialize(user, action, subject)
- @user, @action, @subject = user, action, subject
-
- super()
- end
-
- def message
- I18n.t("active_admin.access_denied.message")
- end
- end
-
-
- class BaseController < ::InheritedResources::Base
- module Authorization
- include MethodOrProcHelper
- extend ActiveSupport::Concern
-
- ACTIONS_DICTIONARY = {
- :index => ActiveAdmin::Authorization::READ,
- :show => ActiveAdmin::Authorization::READ,
- :new => ActiveAdmin::Authorization::CREATE,
- :create => ActiveAdmin::Authorization::CREATE,
- :edit => ActiveAdmin::Authorization::UPDATE,
- :update => ActiveAdmin::Authorization::UPDATE,
- :destroy => ActiveAdmin::Authorization::DESTROY
- }
-
- included do
- rescue_from ActiveAdmin::AccessDenied, :with => :dispatch_active_admin_access_denied
-
- helper_method :authorized?
- helper_method :authorize!
- end
-
- protected
-
- # Authorize the action and subject. Available in the controller
- # as well as all the views.
- #
- # @param [Symbol] action The action to check if the user has permission
- # to perform on the subject.
- #
- # @param [any] subject The subject that the user is trying to perform
- # the action on.
- #
- # @returns [Boolean]
- #
- def authorized?(action, subject = nil)
- active_admin_authorization.authorized?(action, subject)
- end
-
-
- # Authorize the action and subject. Available in the controller
- # as well as all the views. If the action is not allowd, it raises
- # an ActiveAdmin::AccessDenied exception.
- #
- # @param [Symbol] action The action to check if the user has permission
- # to perform on the subject.
- #
- # @param [any] subject The subject that the user is trying to perform
- # the action on.
- #
- # @returns [Boolean] True if authorized, otherwise raises
- # an ActiveAdmin::AccessDenied.
- def authorize!(action, subject = nil)
- unless authorized? action, subject
- raise ActiveAdmin::AccessDenied.new(current_active_admin_user,
- action,
- subject)
- end
- end
-
- # Performs authorization on the resource using the current controller
- # action as the permission action.
- #
- def authorize_resource!(resource)
- permission = action_to_permission(params[:action])
- authorize! permission, resource
- end
-
- # Retrieve or instantiate the authorization instance for this resource
- #
- # @returns [ActiveAdmin::AuthorizationAdapter]
- def active_admin_authorization
- @active_admin_authorization ||= active_admin_authorization_adapter.new(active_admin_config, current_active_admin_user)
- end
-
- # Returns the class to be used as the authorization adapter
- #
- # @returns [Class]
- def active_admin_authorization_adapter
- if active_admin_namespace.authorization_adapter.is_a?(String)
- ActiveSupport::Dependencies.constantize(active_admin_namespace.authorization_adapter)
- else
- active_admin_namespace.authorization_adapter
- end
- end
-
- # Converts a controller action into one of the correct Active Admin
- # authorization names. Uses the ACTIONS_DICTIONARY to convert the
- # action name to permission.
- #
- # @param [String, Symbol] action The controller action name.
- #
- # @returns [Symbol] The permission name to use.
- def action_to_permission(action)
- return nil unless action
-
- action = action.to_sym
-
- if Authorization::ACTIONS_DICTIONARY.has_key?(action)
- Authorization::ACTIONS_DICTIONARY[action]
- else
- action
- end
- end
-
- def dispatch_active_admin_access_denied(exception)
- call_method_or_exec_proc active_admin_namespace.on_unauthorized_access, exception
- end
-
- def rescue_active_admin_access_denied(exception)
- error_message = exception.message
-
- respond_to do |format|
- format.html do
- flash[:error] = error_message
-
- if request.headers.keys.include?("HTTP_REFERER")
- redirect_to :back
- else
- controller, action = active_admin_namespace.root_to.split("#")
- redirect_to :controller => controller, :action => action
- end
- end
-
- format.csv { render :text => error_message, :status => :unauthorized}
- format.json { render :json => { :error => error_message }, :status => :unauthorized}
- format.xml { render :xml => "#{error_message}", :status => :unauthorized}
- end
- end
-
- end
- end
-end
diff --git a/lib/active_admin/base_controller/menu.rb b/lib/active_admin/base_controller/menu.rb
deleted file mode 100644
index 52b7a49b0a2..00000000000
--- a/lib/active_admin/base_controller/menu.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module ActiveAdmin
- class BaseController < ::InheritedResources::Base
- module Menu
- extend ActiveSupport::Concern
-
- included do
- before_filter :set_current_tab
- helper_method :current_menu
- end
-
- protected
-
- def current_menu
- active_admin_config.navigation_menu
- end
-
- # Set's @current_tab to be name of the tab to mark as current
- # Get's called through a before filter
- def set_current_tab
- @current_tab = if current_menu && active_admin_config.belongs_to? && parent?
- parent_item = active_admin_config.belongs_to_config.target.menu_item
- if current_menu.include? parent_item
- parent_item
- else
- active_admin_config.menu_item
- end
- else
- active_admin_config.menu_item
- end
- end
-
- end
- end
-end
diff --git a/lib/active_admin/batch_actions.rb b/lib/active_admin/batch_actions.rb
index a6864da1097..91a362da38b 100644
--- a/lib/active_admin/batch_actions.rb
+++ b/lib/active_admin/batch_actions.rb
@@ -1,17 +1,12 @@
+# frozen_string_literal: true
ActiveAdmin.before_load do |app|
- require "active_admin/batch_actions/resource_extension"
- require "active_admin/batch_actions/controller"
+ require_relative "batch_actions/resource_extension"
+ require_relative "batch_actions/controller"
# Add our Extensions
ActiveAdmin::Resource.send :include, ActiveAdmin::BatchActions::ResourceExtension
ActiveAdmin::ResourceController.send :include, ActiveAdmin::BatchActions::Controller
- # Require all the views
- require "active_admin/batch_actions/views/batch_action_form"
- require "active_admin/batch_actions/views/batch_action_popover"
- require "active_admin/batch_actions/views/selection_cells"
- require "active_admin/batch_actions/views/batch_action_selector"
-
- # Register the views with the view factory
- app.view_factory.register :batch_action_selector => ActiveAdmin::BatchActions::BatchActionSelector
+ require_relative "batch_actions/views/batch_action_form"
+ require_relative "batch_actions/views/selection_cells"
end
diff --git a/lib/active_admin/batch_actions/controller.rb b/lib/active_admin/batch_actions/controller.rb
index ee9685a0ad0..e3cb588ecc9 100644
--- a/lib/active_admin/batch_actions/controller.rb
+++ b/lib/active_admin/batch_actions/controller.rb
@@ -1,13 +1,13 @@
+# frozen_string_literal: true
module ActiveAdmin
module BatchActions
module Controller
-
- # Controller Action that get's called when submitting the batch action form
+ # Controller action that is called when submitting the batch action form
def batch_action
- if selected_batch_action
- selected_ids = params[:collection_selection]
- selected_ids ||= []
- instance_exec selected_ids, &selected_batch_action.block
+ if action_present?
+ selection = params[:collection_selection] || []
+ inputs = JSON.parse(params[:batch_action_inputs].presence || "{}")
+ instance_exec selection, inputs, ¤t_batch_action.block
else
raise "Couldn't find batch action \"#{params[:batch_action]}\""
end
@@ -15,11 +15,24 @@ def batch_action
protected
- def selected_batch_action
- return unless params[:batch_action].present?
- active_admin_config.batch_actions.select { |action| action.sym == params[:batch_action].to_sym }.first
+ def action_present?
+ params[:batch_action].present? && current_batch_action
+ end
+
+ def current_batch_action
+ active_admin_config.batch_actions.detect { |action| action.sym.to_s == params[:batch_action] }
end
+ COLLECTION_APPLIES = [
+ :authorization_scope,
+ :filtering,
+ :scoping,
+ :includes,
+ ].freeze
+
+ def batch_action_collection(only = COLLECTION_APPLIES)
+ find_collection(only: only)
+ end
end
end
end
diff --git a/lib/active_admin/batch_actions/resource_extension.rb b/lib/active_admin/batch_actions/resource_extension.rb
index a1724276c2a..a4b9b8f6636 100644
--- a/lib/active_admin/batch_actions/resource_extension.rb
+++ b/lib/active_admin/batch_actions/resource_extension.rb
@@ -1,11 +1,11 @@
+# frozen_string_literal: true
module ActiveAdmin
-
module BatchActions
module ResourceExtension
def initialize(*)
super
@batch_actions = {}
- add_default_batch_actions
+ add_default_batch_action
end
# @return [Array] The set of batch actions for this resource
@@ -31,7 +31,8 @@ def batch_actions=(bool)
# @param [Hash] options
# => :if is a proc that will be called to determine if the BatchAction should be displayed
# => :sort_order is used to sort the batch actions ascending
- # => :confirm is a string which the user will have to accept in order to process the action
+ # => :confirm is a string to prompt the user with (or a boolean to use the default message)
+ # => :form is a Hash of form fields you want the user to fill out
#
def add_batch_action(sym, title, options = {}, &block)
@batch_actions[sym] = ActiveAdmin::BatchAction.new(sym, title, options, &block)
@@ -39,7 +40,7 @@ def add_batch_action(sym, title, options = {}, &block)
# Remove a batch action
# @param [Symbol] sym
- # @returns [ActiveAdmin::BatchAction] the batch action, if it was present
+ # @return [ActiveAdmin::BatchAction] the batch action, if it was present
#
def remove_batch_action(sym)
@batch_actions.delete(sym.to_sym)
@@ -50,29 +51,28 @@ def clear_batch_actions!
@batch_actions = {}
end
- # Path to the batch action itself
- def batch_action_path(params = {})
- [route_collection_path(params), "batch_action"].join("/")
- end
-
private
# @return [ActiveAdmin::BatchAction] The default "delete" action
- def add_default_batch_actions
+ def add_default_batch_action
destroy_options = {
- :priority => 100,
- :confirm => proc { I18n.t('active_admin.batch_actions.delete_confirmation', :plural_model => active_admin_config.plural_resource_label.downcase) },
- :if => proc{ controller.action_methods.include?('destroy') && authorized?(ActiveAdmin::Auth::DESTROY, active_admin_config.resource_class) }
+ priority: 100,
+ confirm: proc { I18n.t("active_admin.batch_actions.delete_confirmation", plural_model: active_admin_config.plural_resource_label.downcase) },
+ if: proc { destroy_action_authorized?(active_admin_config.resource_class) }
}
- add_batch_action :destroy, proc { I18n.t('active_admin.delete') }, destroy_options do |selected_ids|
- active_admin_config.resource_class.find(selected_ids).each { |r| r.destroy }
+ add_batch_action :destroy, proc { I18n.t("active_admin.delete") }, destroy_options do |selected_ids|
+ batch_action_collection.find(selected_ids).each do |record|
+ authorize! ActiveAdmin::Auth::DESTROY, record
+ destroy_resource(record)
+ end
redirect_to active_admin_config.route_collection_path(params),
- :notice => I18n.t("active_admin.batch_actions.succesfully_destroyed",
- :count => selected_ids.count,
- :model => active_admin_config.resource_label.downcase,
- :plural_model => active_admin_config.plural_resource_label(:count => selected_ids.count).downcase)
+ notice: I18n.t(
+ "active_admin.batch_actions.successfully_destroyed",
+ count: selected_ids.count,
+ model: active_admin_config.resource_label.downcase,
+ plural_model: active_admin_config.plural_resource_label(count: selected_ids.count).downcase)
end
end
@@ -83,7 +83,9 @@ class BatchAction
include Comparable
- attr_reader :block, :title, :sym, :confirm
+ attr_reader :block, :title, :sym, :partial, :link_html_options
+
+ DEFAULT_CONFIRM_MESSAGE = proc { I18n.t "active_admin.batch_actions.default_confirmation" }
# Create a Batch Action
#
@@ -92,20 +94,43 @@ class BatchAction
# BatchAction.new :flag
# => Will create an action that appears in the action list popover
#
- # BatchAction.new( :flag ) { |selection| redirect_to collection_path, :notice => "#{selection.length} users flagged" }
- # => Will create an action that uses a block to process the request (which receives one paramater of the selected objects)
+ # BatchAction.new(:flag) { |selection| redirect_to collection_path, notice: "#{selection.length} users flagged" }
+ # => Will create an action that uses a block to process the request (which receives one parameter of the selected objects)
#
- # BatchAction.new( "Perform Long Operation on the" ) { |selection| }
+ # BatchAction.new("Perform Long Operation on") { |selection| }
# => You can create batch actions with a title instead of a Symbol
#
- # BatchAction.new( :flag, :if => proc { can? :flag, AdminUser } ) { |selection| }
- # => You can provide an optional :if proc to optionally display the batch action
+ # BatchAction.new(:flag, if: proc{ can? :flag, AdminUser }) { |selection| }
+ # => You can provide an `:if` proc to choose when the batch action should be displayed
+ #
+ # BatchAction.new :flag, confirm: true
+ # => You can pass `true` to `:confirm` to use the default confirm message.
+ #
+ # BatchAction.new(:flag, confirm: "Are you sure?") { |selection| }
+ # => You can pass a custom confirmation message through `:confirm`
+ #
+ # BatchAction.new(:flag, partial: "flag_form", link_html_options: { "data-modal-target": "modal-id", "data-modal-show": "modal-id" }) { |selection, inputs| }
+ # => Pass a partial that contains a modal and with a data attribute that opens the modal with the form for the user to fill out.
#
def initialize(sym, title, options = {}, &block)
- @sym, @title, @options, @block, @confirm = sym, title, options, block, options[:confirm]
+ @sym = sym
+ @title = title
+ @options = options
+ @block = block
+ @confirm = options[:confirm]
+ @partial = options[:partial]
+ @link_html_options = options[:link_html_options] || {}
@block ||= proc {}
end
+ def confirm
+ if @confirm == true
+ DEFAULT_CONFIRM_MESSAGE
+ else
+ @confirm
+ end
+ end
+
# Returns the display if block. If the block was not explicitly defined
# a default block always returning true will be returned.
def display_if_block
@@ -121,7 +146,5 @@ def priority
def <=>(other)
self.priority <=> other.priority
end
-
end
-
end
diff --git a/lib/active_admin/batch_actions/views/batch_action_form.rb b/lib/active_admin/batch_actions/views/batch_action_form.rb
index 54531bf77ec..6dbf41f131b 100644
--- a/lib/active_admin/batch_actions/views/batch_action_form.rb
+++ b/lib/active_admin/batch_actions/views/batch_action_form.rb
@@ -1,20 +1,19 @@
-require 'active_admin/component'
+# frozen_string_literal: true
+require_relative "../../component"
module ActiveAdmin
module BatchActions
-
- # Build an BatchActionForm
+ # Build a BatchActionForm
class BatchActionForm < ActiveAdmin::Component
- builder_method :batch_action_form
-
- attr_reader :prefix_html
-
def build(options = {}, &block)
options[:id] ||= "collection_selection"
- # Open a form
- text_node form_tag(active_admin_config.batch_action_path(params), :id => options[:id])
- input(:name => :batch_action, :id => :batch_action, :type => :hidden)
+ # Open a form with two hidden input fields:
+ # batch_action => name of the specific action called
+ # batch_action_inputs => a JSON string of any requested confirmation values
+ text_node form_tag active_admin_config.route_batch_action_path(params, url_options), id: options[:id]
+ input name: :batch_action, id: :batch_action, type: :hidden
+ input name: :batch_action_inputs, id: :batch_action_inputs, type: :hidden
super(options)
end
@@ -27,9 +26,8 @@ def to_s
private
def closing_form_tag
- ''.html_safe
+ "".html_safe
end
-
end
end
end
diff --git a/lib/active_admin/batch_actions/views/batch_action_popover.rb b/lib/active_admin/batch_actions/views/batch_action_popover.rb
deleted file mode 100644
index d7ec0ece4b0..00000000000
--- a/lib/active_admin/batch_actions/views/batch_action_popover.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'active_admin/views/components/action_list_popover'
-
-module ActiveAdmin
- module BatchActions
- # Build an BatchActionPopover
- class BatchActionPopover < ActiveAdmin::Views::ActionListPopover
- builder_method :batch_action_popover
-
- def build(options = {}, &block)
- options[:id] ||= "batch_actions_popover"
- super(options)
- end
-
- def action(batch_action, options = {})
- options[:class] ||= []
- options[:class] += %w(batch_action)
- options.merge! "data-action" => batch_action.sym,
- "data-confirm" => batch_action.confirm
-
- title = I18n.t("active_admin.batch_actions.labels.#{batch_action.sym}", :default => batch_action.title)
- label = I18n.t("active_admin.batch_actions.action_label", :title => title)
-
- super(label, "#", options)
- end
-
- end
- end
-end
diff --git a/lib/active_admin/batch_actions/views/batch_action_selector.rb b/lib/active_admin/batch_actions/views/batch_action_selector.rb
deleted file mode 100644
index 246ced1b2a2..00000000000
--- a/lib/active_admin/batch_actions/views/batch_action_selector.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-require 'active_admin/component'
-
-module ActiveAdmin
- module BatchActions
-
- class BatchActionSelector < ActiveAdmin::Component
- builder_method :batch_action_selector
-
- # Build a new batch actions selector
- #
- # @param [Array] batch_actions An array of batch actions
- def build(batch_actions)
- @batch_actions = Array(batch_actions)
- @drop_down = build_drop_down
- end
-
- # We don't want to wrap the action list (or any other children) in
- # an unecessary div, so instead we just return the children
- def to_s
- children.to_s
- end
-
- private
-
- def build_drop_down
- dropdown_menu I18n.t("active_admin.batch_actions.button_label"),
- :id => "batch_actions_selector",
- :button => { :class => "disabled" } do
- batch_actions_to_display.each do |batch_action|
- confirmation_text = render_or_call_method_or_proc_on(self, batch_action.confirm)
-
- options = {
- :class => "batch_action",
- "data-action" => batch_action.sym,
- "data-confirm" => confirmation_text
- }
-
- default_title = render_or_call_method_or_proc_on(self, batch_action.title)
- title = I18n.t("active_admin.batch_actions.labels.#{batch_action.sym}", :default => default_title)
- label = I18n.t("active_admin.batch_actions.action_label", :title => title)
-
- item label, "#", options
- end
- end
- end
-
- # Return the set of batch actions that should be displayed
- def batch_actions_to_display
- @batch_actions.select do |batch_action|
- call_method_or_proc_on(self, batch_action.display_if_block)
- end
- end
-
- # def build_batch_action_button
- # a :class => 'table_tools_button dropdown_button disabled', :href => "#batch_actions_popover", :id => "batch_actions_button" do
- # text_node I18n.t("active_admin.batch_actions.button_label")
- # end
- # end
-
- # def build_batch_action_popover
- # end
-
- end
-
- end
-end
diff --git a/lib/active_admin/batch_actions/views/selection_cells.rb b/lib/active_admin/batch_actions/views/selection_cells.rb
index 4fccaade5ff..57d1afeb4cd 100644
--- a/lib/active_admin/batch_actions/views/selection_cells.rb
+++ b/lib/active_admin/batch_actions/views/selection_cells.rb
@@ -1,4 +1,5 @@
-require 'active_admin/component'
+# frozen_string_literal: true
+require_relative "../../component"
module ActiveAdmin
module BatchActions
@@ -7,8 +8,11 @@ module BatchActions
class ResourceSelectionToggleCell < ActiveAdmin::Component
builder_method :resource_selection_toggle_cell
- def build
- input( :type => "checkbox", :id => "collection_selection_toggle_all", :name => "collection_selection_toggle_all", :class => "toggle_all" )
+ def build(label_text = "")
+ label do
+ input type: "checkbox", id: "collection_selection_toggle_all", name: "collection_selection_toggle_all", class: "batch-actions-toggle-all"
+ text_node label_text if label_text.present?
+ end
end
end
@@ -17,7 +21,7 @@ class ResourceSelectionCell < ActiveAdmin::Component
builder_method :resource_selection_cell
def build(resource)
- input :type => "checkbox", :id => "batch_action_item_#{resource.id}", :value => resource.id, :class => "collection_selection", :name => "collection_selection[]"
+ input type: "checkbox", id: "batch_action_item_#{resource.id}", value: resource.id, class: "batch-actions-resource-selection", name: "collection_selection[]"
end
end
@@ -26,11 +30,9 @@ class ResourceSelectionTogglePanel < ActiveAdmin::Component
builder_method :resource_selection_toggle_panel
def build
- super(:id => "collection_selection_toggle_panel")
- resource_selection_toggle_cell
- div(:id => "collection_selection_toggle_explaination" ) { I18n.t('active_admin.batch_actions.selection_toggle_explanation', :default => "(Toggle Selection)") }
+ super(id: "collection_selection_toggle_panel")
+ resource_selection_toggle_cell(I18n.t("active_admin.batch_actions.selection_toggle_explanation", default: "(Toggle Selection)"))
end
-
end
end
diff --git a/lib/active_admin/callbacks.rb b/lib/active_admin/callbacks.rb
index 903f5b6c989..3ea5811cb37 100644
--- a/lib/active_admin/callbacks.rb
+++ b/lib/active_admin/callbacks.rb
@@ -1,17 +1,20 @@
+# frozen_string_literal: true
module ActiveAdmin
module Callbacks
extend ActiveSupport::Concern
- protected
+ CALLBACK_TYPES = %i[before after].freeze
+
+ private
# Simple callback system. Implements before and after callbacks for
# use within the controllers.
#
- # We didn't use the ActiveSupport callbacks becuase they do not support
+ # We didn't use the ActiveSupport callbacks because they do not support
# passing in any arbitrary object into the callback method (which we
# need to do)
- def call_callback_with(method, *args)
+ def run_callback(method, *args)
case method
when Symbol
send(method, *args)
@@ -24,6 +27,8 @@ def call_callback_with(method, *args)
module ClassMethods
+ private
+
# Define a new callback.
#
# Example:
@@ -55,33 +60,32 @@ module ClassMethods
#
def define_active_admin_callbacks(*names)
names.each do |name|
- [:before, :after].each do |type|
+ CALLBACK_TYPES.each do |type|
+ callback_name = "#{type}_#{name}_callbacks"
+ callback_ivar = "@#{callback_name}"
- # Define a method to set the callback
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- # def self.before_create_callbacks
- def self.#{type}_#{name}_callbacks
- @#{type}_#{name}_callbacks ||= []
- end
+ # def self.before_create_callbacks
+ singleton_class.send :define_method, callback_name do
+ instance_variable_get(callback_ivar) || instance_variable_set(callback_ivar, [])
+ end
+ singleton_class.send :private, callback_name
- # def self.before_create
- def self.#{type}_#{name}(method = nil, &block)
- #{type}_#{name}_callbacks << (method || block)
- end
- EOS
+ # def self.before_create
+ singleton_class.send :define_method, "#{type}_#{name}" do |method = nil, &block|
+ send(callback_name).push method || block
+ end
end
- # Define a method to run the callbacks
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def run_#{name}_callbacks(*args)
- self.class.before_#{name}_callbacks.each{|callback| call_callback_with(callback, *args)}
- value = yield if block_given?
- self.class.after_#{name}_callbacks.each{|callback| call_callback_with(callback, *args)}
- return value
- end
- EOS
- end
- end
+ # def run_create_callbacks
+ define_method :"run_#{name}_callbacks" do |*args, &block|
+ self.class.send(:"before_#{name}_callbacks").each { |cbk| run_callback(cbk, *args) }
+ value = block.try :call
+ self.class.send(:"after_#{name}_callbacks").each { |cbk| run_callback(cbk, *args) }
+ return value
+ end
+ send :private, "run_#{name}_callbacks"
+ end
+ end
end
end
end
diff --git a/lib/active_admin/cancan_adapter.rb b/lib/active_admin/cancan_adapter.rb
index f25720acd46..51ce4cc2bf0 100644
--- a/lib/active_admin/cancan_adapter.rb
+++ b/lib/active_admin/cancan_adapter.rb
@@ -1,4 +1,9 @@
-require 'cancan'
+# frozen_string_literal: true
+unless ActiveAdmin::Dependency.cancan? || ActiveAdmin::Dependency.cancancan?
+ ActiveAdmin::Dependency.cancan!
+end
+
+require "cancan"
# Add a setting to the application to configure the ability
ActiveAdmin::Application.inheritable_setting :cancan_ability_class, "Ability"
@@ -21,18 +26,10 @@ def scope_collection(collection, action = ActiveAdmin::Auth::READ)
private
- # The setting allows the class to be stored as a string
- # to enable reloading in development.
def initialize_cancan_ability
- ability_class_name = resource.namespace.cancan_ability_class
-
- if ability_class_name.is_a?(String)
- ability_class = ActiveSupport::Dependencies.constantize(ability_class_name)
- else
- ability_class = ability_class_name
- end
-
- ability_class.new(user)
+ klass = resource.namespace.cancan_ability_class
+ klass = klass.constantize if klass.is_a? String
+ klass.new user
end
end
diff --git a/lib/active_admin/collection_decorator.rb b/lib/active_admin/collection_decorator.rb
new file mode 100644
index 00000000000..964622d8f44
--- /dev/null
+++ b/lib/active_admin/collection_decorator.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ # This class decorates a collection of objects delegating
+ # methods to behave like an Array. It's used to decouple ActiveAdmin
+ # from Draper and thus being able to use PORO decorators as well.
+ #
+ # It's implementation is heavily based on the Draper::CollectionDecorator
+ # https://github.com/drapergem/draper/blob/aaa06bd2f1e219838b241a5534e7ca513edd1fe2/lib/draper/collection_decorator.rb
+ class CollectionDecorator
+ # @return the collection being decorated.
+ attr_reader :object
+
+ # @return [Class] the decorator class used to decorate each item, as set by {#initialize}.
+ attr_reader :decorator_class
+
+ array_methods = Array.instance_methods - Object.instance_methods
+ delegate :==, :as_json, *array_methods, to: :decorated_collection
+
+ def initialize(object, with:)
+ @object = object
+ @decorator_class = with
+ end
+
+ class << self
+ alias_method :decorate, :new
+ end
+
+ def decorated_collection
+ @decorated_collection ||= object.map { |item| decorator_class.new(item) }
+ end
+ end
+end
diff --git a/lib/active_admin/comments.rb b/lib/active_admin/comments.rb
deleted file mode 100644
index d34fc5ceca7..00000000000
--- a/lib/active_admin/comments.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'active_admin/comments/comment'
-require 'active_admin/comments/views'
-require 'active_admin/comments/show_page_helper'
-require 'active_admin/comments/namespace_helper'
-require 'active_admin/comments/resource_helper'
-
-# Add the comments configuration
-ActiveAdmin::Application.inheritable_setting :allow_comments, true
-ActiveAdmin::Application.inheritable_setting :show_comments_in_menu, true
-ActiveAdmin::Application.inheritable_setting :comments_registration_name, 'Comment'
-
-# Add the comments module to ActiveAdmin::Namespace
-ActiveAdmin::Namespace.send :include, ActiveAdmin::Comments::NamespaceHelper
-
-# Add the comments module to ActiveAdmin::Resource
-ActiveAdmin::Resource.send :include, ActiveAdmin::Comments::ResourceHelper
-
-# Add the module to the show page
-ActiveAdmin.application.view_factory.show_page.send :include, ActiveAdmin::Comments::ShowPageHelper
-
-# Walk through all the loaded resources after they are loaded
-ActiveAdmin.after_load do |app|
- app.namespaces.values.each do |namespace|
- if namespace.comments?
- namespace.register ActiveAdmin::Comment, :as => namespace.comments_registration_name do
- actions :index, :show, :create
-
- menu false unless namespace.show_comments_in_menu
-
- config.comments = false # Don't allow comments on comments
- config.batch_actions = false # The default destroy batch action isn't showing up anyway...
-
- scope :all, :show_count => false
- # Register a scope for every namespace that exists.
- # The current namespace will be the default scope.
- app.namespaces.values.map(&:name).each do |name|
- scope name, :default => namespace.name == name do |scope|
- scope.where :namespace => name.to_s
- end
- end
-
- # Store the author and namespace
- before_save do |comment|
- comment.namespace = active_admin_config.namespace.name
- comment.author = current_active_admin_user
- end
-
- # Redirect to the resource show page after comment creation
- controller do
- # Prevent N+1 queries
- def scoped_collection
- resource_class.includes :author, :resource
- end unless Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR == 0
- def create
- create! do |success, failure|
- # FYI: below we call `resource.resource`. First is the comment, second is the associated resource.
- resource_config = active_admin_config.namespace.resource_for resource.resource.class
- resource_url = resource_config.route_instance_path resource.resource
- success.html{ redirect_to resource_url }
- failure.html do
- flash[:error] = I18n.t('active_admin.comments.errors.empty_text')
- redirect_to resource_url
- end
- end
- end
- end
-
- index do
- column I18n.t('active_admin.comments.resource_type'), :resource_type
- column I18n.t('active_admin.comments.author_type'), :author_type
- column I18n.t('active_admin.comments.resource'), :resource
- column I18n.t('active_admin.comments.author'), :author
- column I18n.t('active_admin.comments.body'), :body
- actions
- end
- end
- end
- end
-end
diff --git a/lib/active_admin/comments/comment.rb b/lib/active_admin/comments/comment.rb
deleted file mode 100644
index e0c1065a6e9..00000000000
--- a/lib/active_admin/comments/comment.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-require 'kaminari/models/active_record_extension'
-
-module ActiveAdmin
-
- # manually initialize kaminari for this model
- ::ActiveRecord::Base.send :include, Kaminari::ActiveRecordExtension
-
- class Comment < ActiveRecord::Base
- belongs_to :resource, :polymorphic => true
- belongs_to :author, :polymorphic => true
-
- attr_accessible :resource, :resource_id, :resource_type, :body, :namespace
-
- validates_presence_of :body, :namespace, :resource
-
- # @returns [String] The name of the record to use for the polymorphic relationship
- def self.resource_type(record)
- record.class.base_class.name.to_s
- end
-
- def self.resource_id_cast(record)
- # Postgres adapters won't compare strings to numbers (issue 34)
- if resource_id_type == :string
- record.id.to_s
- else
- record.id
- end
- end
-
- def self.find_for_resource_in_namespace(resource, namespace)
- where :resource_type => resource_type(resource),
- :resource_id => resource_id_cast(resource),
- :namespace => namespace.to_s
- end
-
- def self.resource_id_type
- columns.detect{ |i| i.name == "resource_id" }.type
- end
-
- def self.table_name
- @table_name ||= ActiveRecord::Migrator.proper_table_name("active_admin_comments")
- end
-
- end
-
-end
-
diff --git a/lib/active_admin/comments/show_page_helper.rb b/lib/active_admin/comments/show_page_helper.rb
deleted file mode 100644
index 3f8529569bf..00000000000
--- a/lib/active_admin/comments/show_page_helper.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module ActiveAdmin
- module Comments
-
- # Adds #active_admin_comments to the show page for use
- # and sets it up on the default main content
- module ShowPageHelper
-
- # Add admin comments to the main content if they are
- # turned on for the current resource
- def default_main_content
- super
- active_admin_comments if active_admin_config.comments?
- end
-
- # Display the comments for the resource. Same as calling
- # #active_admin_comments_for with the current resource
- def active_admin_comments(*args, &block)
- active_admin_comments_for(resource, *args, &block)
- end
- end
-
- end
-end
diff --git a/lib/active_admin/comments/views.rb b/lib/active_admin/comments/views.rb
deleted file mode 100644
index f4b811164da..00000000000
--- a/lib/active_admin/comments/views.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-require 'active_admin/views'
-require 'active_admin/comments/views/active_admin_comments'
diff --git a/lib/active_admin/comments/views/active_admin_comments.rb b/lib/active_admin/comments/views/active_admin_comments.rb
deleted file mode 100644
index cd5978a6648..00000000000
--- a/lib/active_admin/comments/views/active_admin_comments.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'active_admin/views'
-require 'active_admin/views/components/panel'
-
-module ActiveAdmin
- module Comments
- module Views
-
- class Comments < ActiveAdmin::Views::Panel
- builder_method :active_admin_comments_for
-
- def build(resource)
- @resource = resource
- @comments = ActiveAdmin::Comment.find_for_resource_in_namespace @resource, active_admin_namespace.name
- super(title, :for => resource)
- build_comments
- end
-
- protected
-
- def title
- I18n.t 'active_admin.comments.title_content', :count => @comments.count
- end
-
- def build_comments
- @comments.any? ? @comments.each(&method(:build_comment)) : build_empty_message
- build_comment_form
- end
-
- def build_comment(comment)
- div :for => comment do
- div :class => 'active_admin_comment_meta' do
- h4 :class => 'active_admin_comment_author' do
- comment.author ? auto_link(comment.author) : 'Anonymous'
- end
- span pretty_format comment.created_at
- end
- div :class => 'active_admin_comment_body' do
- simple_format comment.body
- end
- end
- end
-
- def build_empty_message
- span I18n.t('active_admin.comments.no_comments_yet'), :class => 'empty'
- end
-
- def comment_form_url
- parts = []
- parts << active_admin_namespace.name unless active_admin_namespace.root?
- parts << active_admin_namespace.comments_registration_name.underscore.pluralize
- parts << 'path'
- send parts.join '_'
- end
-
- def build_comment_form
- self << active_admin_form_for(ActiveAdmin::Comment.new, :url => comment_form_url) do |f|
- f.inputs do
- f.input :resource_type, :as => :hidden, :input_html => { :value => ActiveAdmin::Comment.resource_type(@resource) }
- f.input :resource_id, :as => :hidden, :input_html => { :value => @resource.id }
- f.input :body, :label => false, :input_html => { :size => '80x8' }
- end
- f.actions do
- f.action :submit, :label => I18n.t('active_admin.comments.add')
- end
- end
- end
-
- def default_id_for_prefix
- 'active_admin_comments_for'
- end
- end
-
- end
- end
-end
diff --git a/lib/active_admin/component.rb b/lib/active_admin/component.rb
index 8343552f612..784aa048041 100644
--- a/lib/active_admin/component.rb
+++ b/lib/active_admin/component.rb
@@ -1,5 +1,5 @@
+# frozen_string_literal: true
module ActiveAdmin
class Component < Arbre::Component
-
end
end
diff --git a/lib/active_admin/controller_action.rb b/lib/active_admin/controller_action.rb
index bd657926197..4e92d6903dd 100644
--- a/lib/active_admin/controller_action.rb
+++ b/lib/active_admin/controller_action.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
module ActiveAdmin
class ControllerAction
attr_reader :name
def initialize(name, options = {})
- @name, @options = name, options
+ @name = name
+ @options = options
end
def http_verb
diff --git a/lib/active_admin/csv_builder.rb b/lib/active_admin/csv_builder.rb
index 20c56a60c3c..4122e34b944 100644
--- a/lib/active_admin/csv_builder.rb
+++ b/lib/active_admin/csv_builder.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
# CSVBuilder stores CSV configuration
#
@@ -6,8 +7,11 @@ module ActiveAdmin
# csv_builder = CSVBuilder.new
# csv_builder.column :id
# csv_builder.column("Name") { |resource| resource.full_name }
+ # csv_builder.column(:name, humanize_name: false)
+ # csv_builder.column("name", humanize_name: false) { |resource| resource.full_name }
#
- # csv_builder = CSVBuilder.new :col_sep => ";"
+ # csv_builder = CSVBuilder.new col_sep: ";"
+ # csv_builder = CSVBuilder.new humanize_name: false
# csv_builder.column :id
#
#
@@ -17,33 +21,124 @@ class CSVBuilder
# The CSVBuilder's columns would be Id followed by this
# resource's content columns
def self.default_for_resource(resource)
- new.tap do |csv_builder|
- csv_builder.column(:id)
- resource.content_columns.each do |content_column|
- csv_builder.column(content_column.name.to_sym)
- end
+ new resource: resource do
+ column :id
+ resource.content_columns.each { |c| column c }
+ end
+ end
+
+ attr_reader :columns, :options, :view_context
+
+ COLUMN_TRANSITIVE_OPTIONS = [:humanize_name].freeze
+
+ def initialize(options = {}, &block)
+ @resource = options.delete(:resource)
+ @columns = []
+ @options = ActiveAdmin.application.csv_options.merge options
+ @block = block
+ end
+
+ def column(name, options = {}, &block)
+ @columns << Column.new(name, @resource, column_transitive_options.merge(options), block)
+ end
+
+ def build(controller, csv)
+ columns = exec_columns controller.view_context
+ bom = options[:byte_order_mark]
+ column_names = options.delete(:column_names) { true }
+ csv_options = options.except :encoding_options, :humanize_name, :byte_order_mark
+
+ csv << bom if bom
+
+ if column_names
+ csv << CSV.generate_line(columns.map { |c| sanitize(encode(c.name, options)) }, **csv_options)
end
+
+ controller.send(:in_paginated_batches) do |resource|
+ csv << CSV.generate_line(build_row(resource, columns, options), **csv_options)
+ end
+
+ csv
end
- attr_reader :columns, :options
+ def exec_columns(view_context = nil)
+ @view_context = view_context
+ @columns = [] # we want to re-render these every instance
+ instance_exec(&@block) if @block.present?
+ columns
+ end
- def initialize(options={}, &block)
- @columns, @options = [], options
- instance_eval &block if block_given?
+ def build_row(resource, columns, options)
+ columns.map do |column|
+ sanitize(encode(call_method_or_proc_on(resource, column.data), options))
+ end
end
- # Add a column
- def column(name, &block)
- @columns << Column.new(name, block)
+ def encode(content, options)
+ if options[:encoding]
+ if options[:encoding_options]
+ content.to_s.encode options[:encoding], **options[:encoding_options]
+ else
+ content.to_s.encode options[:encoding]
+ end
+ else
+ content
+ end
+ end
+
+ def sanitize(content)
+ Sanitizer.sanitize(content)
+ end
+
+ def method_missing(method, *args, &block)
+ if @view_context.respond_to? method
+ @view_context.public_send method, *args, &block
+ else
+ super
+ end
end
class Column
- attr_reader :name, :data
+ attr_reader :name, :data, :options
+
+ DEFAULT_OPTIONS = { humanize_name: true }
- def initialize(name, block = nil)
- @name = name.is_a?(Symbol) ? name.to_s.titleize : name
+ def initialize(name, resource = nil, options = {}, block = nil)
+ @options = options.reverse_merge(DEFAULT_OPTIONS)
+ @name = humanize_name(name, resource, @options[:humanize_name])
@data = block || name.to_sym
end
+
+ def humanize_name(name, resource, humanize_name_option)
+ if humanize_name_option
+ name.is_a?(Symbol) && resource ? resource.resource_class.human_attribute_name(name) : name.to_s.humanize
+ else
+ name.to_s
+ end
+ end
+ end
+
+ private
+
+ def column_transitive_options
+ @column_transitive_options ||= @options.slice(*COLUMN_TRANSITIVE_OPTIONS)
+ end
+ end
+
+ # Prevents CSV Injection according to https://owasp.org/www-community/attacks/CSV_Injection
+ module Sanitizer
+ extend self
+
+ ATTACK_CHARACTERS = ['=', '+', '-', '@', "\t", "\r"].freeze
+
+ def sanitize(value)
+ return "'#{value}" if require_sanitization?(value)
+
+ value
+ end
+
+ def require_sanitization?(value)
+ value.is_a?(String) && value.starts_with?(*ATTACK_CHARACTERS)
end
end
end
diff --git a/lib/active_admin/dependency.rb b/lib/active_admin/dependency.rb
new file mode 100644
index 00000000000..9cb44d3306d
--- /dev/null
+++ b/lib/active_admin/dependency.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Dependency
+ module Requirements
+ DEVISE = ">= 4.0", "< 6"
+ end
+
+ # Provides a clean interface to check for gem dependencies at runtime.
+ #
+ # ActiveAdmin::Dependency.rails
+ # => #
+ #
+ # ActiveAdmin::Dependency.rails?
+ # => true
+ #
+ # ActiveAdmin::Dependency.rails? '>= 6.1'
+ # => false
+ #
+ # ActiveAdmin::Dependency.rails? '= 6.0.3.2'
+ # => true
+ #
+ # ActiveAdmin::Dependency.rails? '~> 6.0.3'
+ # => true
+ #
+ # ActiveAdmin::Dependency.rails? '>= 6.0.3', '<= 6.1.0'
+ # => true
+ #
+ # ActiveAdmin::Dependency.rails! '5'
+ # -> ActiveAdmin::DependencyError: You provided rails 4.2.7 but we need: 5.
+ #
+ # ActiveAdmin::Dependency.devise!
+ # -> ActiveAdmin::DependencyError: To use devise you need to specify it in your Gemfile.
+ #
+ #
+ # All but the pessimistic operator (~>) can also be run using Ruby's comparison syntax.
+ #
+ # ActiveAdmin::Dependency.rails >= '4.2.7'
+ # => true
+ #
+ # Which is especially useful if you're looking up a gem with dashes in the name.
+ #
+ # ActiveAdmin::Dependency['jquery-rails'] < 5
+ # => false
+ #
+ def self.method_missing(name, *args)
+ if name[-1] == "?"
+ Matcher.new(name[0..-2]).match? args
+ elsif name[-1] == "!"
+ Matcher.new(name[0..-2]).match! args
+ else
+ Matcher.new name.to_s
+ end
+ end
+
+ def self.[](name)
+ Matcher.new name.to_s
+ end
+
+ class Matcher
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def spec
+ @spec ||= Gem.loaded_specs[name]
+ end
+
+ def spec!
+ spec || raise(DependencyError, "To use #{name} you need to specify it in your Gemfile.")
+ end
+
+ def match?(*reqs)
+ !!spec && Gem::Requirement.create(reqs).satisfied_by?(spec.version)
+ end
+
+ def match!(*reqs)
+ unless match? reqs
+ raise DependencyError, "You provided #{spec!.name} #{spec!.version} but we need: #{reqs.join ', '}."
+ end
+ end
+
+ include Comparable
+
+ def <=>(other)
+ spec!.version <=> Gem::Version.create(other)
+ end
+
+ def inspect
+ info = spec ? "#{spec.name} #{spec.version}" : "(missing)"
+ ""
+ end
+ end
+
+ end
+end
diff --git a/lib/active_admin/dependency_checker.rb b/lib/active_admin/dependency_checker.rb
deleted file mode 100644
index cb6d8166dfe..00000000000
--- a/lib/active_admin/dependency_checker.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require 'rails/version'
-
-module ActiveAdmin
- module DependencyChecker
- class << self
- def check!
- if rails_3_1?
- unless meta_search_1_1? && sass_rails_3_1?
- warn "ActiveAdmin requires meta_search >= 1.1.0.pre and sass-rails ~> 3.1.0.rc to work with rails >= 3.1.0"
- end
- end
-
- if pry_rails_before_0_1_6?
- warn "ActiveAdmin is not compatible with pry-rails < 0.1.6. Please upgrade pry-rails."
- end
- end
-
- def rails_3_1?
- Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR >= 1
- end
-
- def meta_search_1_1?
- Gem.loaded_specs['meta_search'].version.to_s >= "1.1"
- end
-
- def sass_rails_3_1?
- require 'sass/rails/version'
- ::Sass::Rails::VERSION >= "3.1"
- rescue LoadError
- false
- end
-
- def pry_rails_before_0_1_6?
- begin
- PryRails::VERSION < "0.1.6"
- rescue NameError
- false
- end
- end
- end
- end
-end
diff --git a/lib/active_admin/deprecation.rb b/lib/active_admin/deprecation.rb
deleted file mode 100644
index 9ab6e06c2b4..00000000000
--- a/lib/active_admin/deprecation.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module ActiveAdmin
- module Deprecation
- module_function
-
- def warn(message, callstack = caller)
- ActiveSupport::Deprecation.warn "Active Admin: #{message}", callstack
- end
-
- # Deprecate a method.
- #
- # @param [Module] klass the Class or Module to deprecate the method on
- # @param [Symbol] method the method to deprecate
- # @param [String] message the message to display to the end user
- #
- # Example:
- #
- # class MyClass
- # def my_method
- # # ...
- # end
- # ActiveAdmin::Deprecation.deprecate self, :my_method,
- # "MyClass#my_method is being removed in the next release"
- # end
- #
- def deprecate(klass, method, message)
- klass.class_eval <<-EOC, __FILE__, __LINE__ + 1
- alias_method :deprecated_#{method}, :#{method}
- def #{method}(*args)
- ActiveAdmin::Deprecation.warn('#{message}', caller)
- send(:deprecated_#{method}, *args)
- end
- EOC
- end
-
- end
-end
diff --git a/lib/active_admin/devise.rb b/lib/active_admin/devise.rb
index 472cc753343..8de2fd29603 100644
--- a/lib/active_admin/devise.rb
+++ b/lib/active_admin/devise.rb
@@ -1,69 +1,90 @@
-require 'devise'
+# frozen_string_literal: true
+ActiveAdmin::Dependency.devise! ActiveAdmin::Dependency::Requirements::DEVISE
+
+require "devise"
module ActiveAdmin
module Devise
def self.config
- config = {
- :path => ActiveAdmin.application.default_namespace,
- :controllers => ActiveAdmin::Devise.controllers,
- :path_names => { :sign_in => 'login', :sign_out => "logout" }
+ {
+ path: ActiveAdmin.application.default_namespace || "/",
+ controllers: ActiveAdmin::Devise.controllers,
+ path_names: { sign_in: "login", sign_out: "logout" }
}
-
- if ::Devise.respond_to?(:sign_out_via)
- logout_methods = [::Devise.sign_out_via, ActiveAdmin.application.logout_link_method].flatten.uniq
- config.merge!( :sign_out_via => logout_methods)
- end
-
- config
end
def self.controllers
{
- :sessions => "active_admin/devise/sessions",
- :passwords => "active_admin/devise/passwords",
- :unlocks => "active_admin/devise/unlocks"
+ sessions: "active_admin/devise/sessions",
+ passwords: "active_admin/devise/passwords",
+ unlocks: "active_admin/devise/unlocks",
+ registrations: "active_admin/devise/registrations",
+ confirmations: "active_admin/devise/confirmations"
}
end
module Controller
extend ::ActiveSupport::Concern
included do
- layout 'active_admin_logged_out'
- helper ::ActiveAdmin::ViewHelpers
+ layout "active_admin_logged_out"
+ helper ::ActiveAdmin::LayoutHelper
+ helper ::ActiveAdmin::FormHelper
end
# Redirect to the default namespace on logout
def root_path
namespace = ActiveAdmin.application.default_namespace.presence
- root_path_method = [namespace, :root_path].compact.join('_')
+ root_path_method = [namespace, :root_path].compact.join("_")
- url_helpers = Rails.application.routes.url_helpers
-
- path = if url_helpers.respond_to? root_path_method
- url_helpers.send root_path_method
+ path = if Helpers::Routes.respond_to? root_path_method
+ Helpers::Routes.send root_path_method
else
# Guess a root_path when url_helpers not helpful
"/#{namespace}"
end
- # NOTE: `relative_url_root` is deprecated by rails.
+ # NOTE: `relative_url_root` is deprecated by Rails.
# Remove prefix here if it is removed completely.
- prefix = Rails.configuration.action_controller[:relative_url_root] || ''
+ prefix = Rails.configuration.action_controller[:relative_url_root] || ""
prefix + path
end
end
class SessionsController < ::Devise::SessionsController
include ::ActiveAdmin::Devise::Controller
+
+ ActiveSupport.run_load_hooks(:active_admin_controller, self)
end
class PasswordsController < ::Devise::PasswordsController
include ::ActiveAdmin::Devise::Controller
+
+ ActiveSupport.run_load_hooks(:active_admin_controller, self)
end
class UnlocksController < ::Devise::UnlocksController
include ::ActiveAdmin::Devise::Controller
+
+ ActiveSupport.run_load_hooks(:active_admin_controller, self)
+ end
+
+ class RegistrationsController < ::Devise::RegistrationsController
+ include ::ActiveAdmin::Devise::Controller
+
+ ActiveSupport.run_load_hooks(:active_admin_controller, self)
+ end
+
+ class ConfirmationsController < ::Devise::ConfirmationsController
+ include ::ActiveAdmin::Devise::Controller
+
+ ActiveSupport.run_load_hooks(:active_admin_controller, self)
+ end
+
+ def self.controllers_for_filters
+ [SessionsController, PasswordsController, UnlocksController,
+ RegistrationsController, ConfirmationsController
+ ]
end
end
diff --git a/lib/active_admin/dsl.rb b/lib/active_admin/dsl.rb
index d5747b4dd89..cbe6a9de6c9 100644
--- a/lib/active_admin/dsl.rb
+++ b/lib/active_admin/dsl.rb
@@ -1,9 +1,9 @@
+# frozen_string_literal: true
module ActiveAdmin
- #
# The Active Admin DSL. This class is where all the registration blocks
- # are instance eval'd. This is the central place for the API given to
- # users of Active Admin
+ # are evaluated. This is the central place for the API given to
+ # users of Active Admin.
#
class DSL
@@ -13,10 +13,10 @@ def initialize(config)
# Runs the registration block inside this object
def run_registration_block(&block)
- instance_eval &block if block_given?
+ instance_exec(&block) if block
end
- # The instance of ActiveAdmin::Config that's being registered
+ # The instance of ActiveAdmin::Resource that's being registered
# currently. You can use this within your registration blocks to
# modify options:
#
@@ -30,7 +30,7 @@ def config
@config
end
- # Include a module with this resource. The modules's `included` method
+ # Include a module with this resource. The modules' `included` method
# is called with the instance of the `ActiveAdmin::DSL` passed into it.
#
# eg:
@@ -51,13 +51,13 @@ def config
#
# @param [Module] mod A module to include
#
- # @returns [Nil]
+ # @return [Nil]
def include(mod)
mod.included(self)
end
# Returns the controller for this resource. If you pass a
- # block, it will be eval'd in the controller
+ # block, it will be evaluated in the controller.
#
# Example:
#
@@ -72,19 +72,20 @@ def include(mod)
# end
#
def controller(&block)
- @config.controller.class_eval(&block) if block_given?
+ @config.controller.class_exec(&block) if block
@config.controller
end
# Add a new action item to the resource
#
+ # @param [Symbol] name
# @param [Hash] options valid keys include:
# :only: A single or array of controller actions to display
# this action item on.
# :except: A single or array of controller actions not to
# display this action item on.
- def action_item(options = {}, &block)
- config.add_action_item(options, &block)
+ def action_item(name, options = {}, &block)
+ config.add_action_item(name, options, &block)
end
# Add a new batch action item to the resource
@@ -101,7 +102,7 @@ def action_item(options = {}, &block)
def batch_action(title, options = {}, &block)
# Create symbol & title information
if title.is_a? String
- sym = title.titleize.gsub(' ', '').underscore.to_sym
+ sym = title.titleize.tr(" ", "").underscore.to_sym
else
sym = title
title = sym.to_s.titleize
@@ -109,7 +110,7 @@ def batch_action(title, options = {}, &block)
# Either add/remove the batch action
unless options == false
- config.add_batch_action( sym, title, options, &block )
+ config.add_batch_action(sym, title, options, &block)
else
config.remove_batch_action sym
end
@@ -121,7 +122,7 @@ def menu(options = {})
config.menu_item_options = options
end
- # Set the name of the navigation menu to display. This is mainly used in conjuction with the
+ # Set the name of the navigation menu to display. This is mainly used in conjunction with the
# `#belongs_to` functionality.
#
# @param [Symbol] menu_name The name of the menu to display as the global navigation
@@ -130,7 +131,7 @@ def menu(options = {})
# Pass a block returning the name of a menu you want rendered for the request, being
# executed in the context of the controller
#
- def navigation_menu(menu_name=nil, &block)
+ def navigation_menu(menu_name = nil, &block)
config.navigation_menu_name = menu_name || block
end
@@ -156,11 +157,5 @@ def sidebar(name, options = {}, &block)
config.sidebar_sections << ActiveAdmin::SidebarSection.new(name, options, &block)
end
- def decorate_with(decorator_class)
- # Force storage as a string. This will help us with reloading issues.
- # Assuming decorator_class.to_s will return the name of the class allows
- # us to handle a string or a class.
- config.decorator_class_name = "::#{ decorator_class }"
- end
end
end
diff --git a/lib/active_admin/dynamic_setting.rb b/lib/active_admin/dynamic_setting.rb
new file mode 100644
index 00000000000..69461379f11
--- /dev/null
+++ b/lib/active_admin/dynamic_setting.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+module ActiveAdmin
+
+ class DynamicSetting
+ def self.build(setting, type)
+ (type ? klass(type) : self).new(setting)
+ end
+
+ def self.klass(type)
+ klass = "#{type.to_s.camelcase}Setting"
+ raise ArgumentError, "Unknown type: #{type}" unless ActiveAdmin.const_defined?(klass)
+ ActiveAdmin.const_get(klass)
+ end
+
+ def initialize(setting)
+ @setting = setting
+ end
+
+ def value(*_args)
+ @setting
+ end
+ end
+
+ # Many configuration options (Ex: site_title, title_image) could either be
+ # static (String), methods (Symbol) or procs (Proc). This wrapper takes care of
+ # returning the content when String or using instance_eval when Symbol or Proc.
+ #
+ class StringSymbolOrProcSetting < DynamicSetting
+ def value(context = self)
+ case @setting
+ when Symbol, Proc
+ context.instance_eval(&@setting)
+ else
+ @setting
+ end
+ end
+ end
+
+end
diff --git a/lib/active_admin/dynamic_settings_node.rb b/lib/active_admin/dynamic_settings_node.rb
new file mode 100644
index 00000000000..9227ea2db9e
--- /dev/null
+++ b/lib/active_admin/dynamic_settings_node.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+require_relative "dynamic_setting"
+require_relative "settings_node"
+
+module ActiveAdmin
+
+ class DynamicSettingsNode < SettingsNode
+ class << self
+ def register(name, value, type = nil)
+ class_attribute "#{name}_setting"
+ add_reader(name)
+ add_writer(name, type)
+ send :"#{name}=", value
+ end
+
+ def add_reader(name)
+ define_singleton_method(name) do |*args|
+ send(:"#{name}_setting").value(*args)
+ end
+ end
+
+ def add_writer(name, type)
+ define_singleton_method(:"#{name}=") do |value|
+ send(:"#{name}_setting=", DynamicSetting.build(value, type))
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/engine.rb b/lib/active_admin/engine.rb
index cee896738ea..0d1930bc2bc 100644
--- a/lib/active_admin/engine.rb
+++ b/lib/active_admin/engine.rb
@@ -1,9 +1,45 @@
+# frozen_string_literal: true
module ActiveAdmin
class Engine < ::Rails::Engine
- if Rails.version > "3.1"
- initializer "ActiveAdmin precompile hook", :group => :all do |app|
- app.config.assets.precompile += %w(active_admin.js active_admin.css active_admin/print.css)
+ isolate_namespace ActiveAdmin
+
+ # Set default values for app_path and load_paths before running initializers
+ initializer "active_admin.load_app_path", before: :load_config_initializers do |app|
+ ActiveAdmin::Application.setting :app_path, app.root
+ ActiveAdmin::Application.setting :load_paths, [File.expand_path("app/admin", app.root)]
+ end
+
+ initializer "active_admin.precompile", group: :all do |app|
+ if app.config.respond_to?(:assets)
+ app.config.assets.precompile += %w(active_admin.js active_admin.css active_admin_manifest.js)
+ end
+ end
+
+ initializer "active_admin.importmap", after: "importmap" do |app|
+ # Skip if importmap-rails is not installed
+ next unless app.config.respond_to?(:importmap)
+
+ ActiveAdmin.importmap.draw(Engine.root.join("config", "importmap.rb"))
+ package_path = Engine.root.join("app/javascript")
+ if app.config.respond_to?(:assets)
+ app.config.assets.paths << package_path
+ app.config.assets.paths << Engine.root.join("vendor/javascript")
end
+
+ if app.config.importmap.sweep_cache
+ ActiveAdmin.importmap.cache_sweeper(watches: package_path)
+ ActiveSupport.on_load(:action_controller_base) do
+ before_action { ActiveAdmin.importmap.cache_sweeper.execute_if_updated }
+ end
+ end
+ end
+
+ initializer "active_admin.routes" do
+ require_relative "helpers/routes/url_helpers"
+ end
+
+ initializer "active_admin.deprecator" do |app|
+ app.deprecators[:activeadmin] = ActiveAdmin.deprecator if app.respond_to?(:deprecators)
end
end
end
diff --git a/lib/active_admin/error.rb b/lib/active_admin/error.rb
new file mode 100644
index 00000000000..824a7fe79f6
--- /dev/null
+++ b/lib/active_admin/error.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ # Exception class to raise when there is an authorized access
+ # exception thrown. The exception has a few goodies that may
+ # be useful for capturing / recognizing security issues.
+ class AccessDenied < StandardError
+ attr_reader :user, :action, :subject
+
+ def initialize(user, action, subject = nil)
+ @user = user
+ @action = action
+ @subject = subject
+
+ super()
+ end
+
+ def message
+ I18n.t("active_admin.access_denied.message")
+ end
+ end
+
+ class Error < RuntimeError
+ end
+
+ class ErrorLoading < Error
+ # Locates the most recent file and line from the caught exception's backtrace.
+ def find_cause(folder, backtrace)
+ backtrace.grep(/\/(#{folder}\/.*\.rb):(\d+)/) { [$1, $2] }.first
+ end
+ end
+
+ class DatabaseHitDuringLoad < ErrorLoading
+ def initialize(exception)
+ file, line = find_cause(:app, exception.backtrace)
+
+ super "Your file, #{file} (line #{line}), caused a database error while Active Admin was loading. This " +
+ "is most common when your database is missing or doesn't have the latest migrations applied. To " +
+ "prevent this error, move the code to a place where it will only be run when a page is rendered. " +
+ "One solution can be, to wrap the query in a Proc. " +
+ "Original error message: #{exception.message}"
+ end
+
+ def self.capture
+ yield
+ rescue *database_error_classes => exception
+ raise new exception
+ end
+
+ def self.database_error_classes
+ @classes ||= []
+ end
+ end
+
+ class DependencyError < ErrorLoading
+ end
+
+ class NoMenuError < KeyError
+ end
+
+ class GeneratorError < Error
+ end
+
+end
diff --git a/lib/active_admin/event.rb b/lib/active_admin/event.rb
deleted file mode 100644
index 68caf336398..00000000000
--- a/lib/active_admin/event.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-module ActiveAdmin
-
- class EventDispatcher
- def initialize
- @events = {}
- end
-
- def clear_all_subscribers!
- @events = {}
- end
-
- def subscribe(*event_names, &block)
- event_names.each do |event|
- @events[event] ||= []
- @events[event] << block
- end
- end
-
- def subscribers(event)
- @events[event] || []
- end
-
- def dispatch(event, *args)
- subscribers(event).each do |subscriber|
- subscriber.call(*args)
- end
- end
- end
-
- # ActiveAdmin::Event is set to a dispatcher
- Event = EventDispatcher.new
-
-end
diff --git a/lib/active_admin/filters.rb b/lib/active_admin/filters.rb
index 13485d79232..c69094f5d99 100644
--- a/lib/active_admin/filters.rb
+++ b/lib/active_admin/filters.rb
@@ -1,9 +1,10 @@
-require 'active_admin/filters/dsl'
-require 'active_admin/filters/resource_extension'
-require 'active_admin/filters/formtastic_addons'
-require 'active_admin/filters/forms'
+# frozen_string_literal: true
+require_relative "filters/dsl"
+require_relative "filters/resource_extension"
+require_relative "filters/formtastic_addons"
+require_relative "filters/forms"
+require_relative "helpers/optional_display"
# Add our Extensions
ActiveAdmin::ResourceDSL.send :include, ActiveAdmin::Filters::DSL
-ActiveAdmin::Resource.send :include, ActiveAdmin::Filters::ResourceExtension
-ActiveAdmin::ViewHelpers.send :include, ActiveAdmin::Filters::ViewHelper
+ActiveAdmin::Resource.send :include, ActiveAdmin::Filters::ResourceExtension
diff --git a/lib/active_admin/filters/active.rb b/lib/active_admin/filters/active.rb
new file mode 100644
index 00000000000..a68dd718a1d
--- /dev/null
+++ b/lib/active_admin/filters/active.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+require_relative "active_filter"
+
+module ActiveAdmin
+ module Filters
+ class Active
+ attr_reader :filters, :resource, :scopes
+
+ # Instantiate a `Active` object containing collection of current active filters
+
+ # @param resource [ActiveAdmin::Resource] current resource
+ # @param search [Ransack::Search] search object
+ #
+ # @see ActiveAdmin::ResourceController::DataAccess#apply_filtering
+ def initialize(resource, search)
+ @resource = resource
+ # Ransack::Search uses method_missing for #conditions, so gate on the class.
+ @filters = search.is_a?(::Ransack::Search) ? build_filters(search.conditions) : []
+ @scopes = search.instance_variable_get(:@scope_args)
+ end
+
+ def all_blank?
+ filters.blank? && scopes.blank?
+ end
+
+ private
+
+ def build_filters(conditions)
+ conditions.map { |condition| ActiveFilter.new(resource, condition.dup) }
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/filters/active_filter.rb b/lib/active_admin/filters/active_filter.rb
new file mode 100644
index 00000000000..a0e971f8f53
--- /dev/null
+++ b/lib/active_admin/filters/active_filter.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Filters
+
+ class ActiveFilter
+ attr_reader :resource, :condition, :related_class
+
+ # Instantiate a `ActiveFilter`
+ #
+ # @param resource [ActiveAdmin::Resource] current resource
+ # @param condition [Ransack::Nodes::Condition] applied ransack condition
+ def initialize(resource, condition)
+ @resource = resource
+ @condition = condition
+ @related_class = find_class if find_class?
+ end
+
+ def values
+ condition_values = condition.values.map(&:value)
+ if related_class
+ related_class.where(related_primary_key => condition_values)
+ else
+ condition_values
+ end
+ end
+
+ def label
+ translated_predicate = predicate_name.downcase
+ if filter_label && filter_label.is_a?(Proc)
+ "#{filter_label.call} #{translated_predicate}"
+ elsif filter_label
+ "#{filter_label} #{translated_predicate}"
+ elsif related_class
+ "#{related_class_name} #{translated_predicate}"
+ else
+ "#{attribute_name} #{translated_predicate}"
+ end.strip
+ end
+
+ def predicate_name
+ Ransack::Translate.predicate(condition.predicate.name)
+ end
+
+ def html_options
+ { "data-filter": condition.key }
+ end
+
+ private
+
+ def resource_class
+ resource.resource_class
+ end
+
+ def attribute_name
+ resource_class.human_attribute_name(name)
+ end
+
+ def related_class_name
+ return unless related_class
+
+ related_class.model_name.human
+ end
+
+ def filter_label
+ return unless filter
+
+ filter[:label] || I18n.t(name, scope: ["formtastic", "labels"], default: nil)
+ end
+
+ #@return Ransack::Nodes::Attribute
+ def condition_attribute
+ condition.attributes[0]
+ end
+
+ def name
+ condition_attribute.attr_name
+ end
+
+ def find_class?
+ ["eq", "in"].include? condition.predicate.arel_predicate
+ end
+
+ # detect related class for Ransack::Nodes::Attribute
+ def find_class
+ if condition_attribute.klass != resource_class && condition_attribute.klass.primary_key == name.to_s
+ condition_attribute.klass
+ elsif predicate_association
+ predicate_association.klass
+ end
+ end
+
+ def filter
+ resource.filters[name.to_sym] || resource.filters[condition.key.to_sym]
+ end
+
+ def related_primary_key
+ if predicate_association
+ predicate_association.association_primary_key
+ elsif related_class
+ related_class.primary_key
+ end
+ end
+
+ def predicate_association
+ @predicate_association = find_predicate_association unless defined?(@predicate_association)
+ @predicate_association
+ end
+
+ def find_predicate_association
+ condition_attribute.klass.reflect_on_all_associations.
+ reject { |r| r.options[:polymorphic] }. #skip polymorphic
+ detect { |r| r.foreign_key.to_s == name.to_s }
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/filters/dsl.rb b/lib/active_admin/filters/dsl.rb
index 207c2c872e9..56a40473e9f 100644
--- a/lib/active_admin/filters/dsl.rb
+++ b/lib/active_admin/filters/dsl.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
module Filters
module DSL
@@ -8,8 +9,8 @@ def filter(attribute, options = {})
end
# For docs, please see ActiveAdmin::Filters::ResourceExtension#remove_filter
- def remove_filter(attribute)
- config.remove_filter(attribute)
+ def remove_filter(*attributes)
+ config.remove_filter(*attributes)
end
# For docs, please see ActiveAdmin::Filters::ResourceExtension#preserve_default_filters!
diff --git a/lib/active_admin/filters/forms.rb b/lib/active_admin/filters/forms.rb
index cd7da1575a6..7c71fdf74a4 100644
--- a/lib/active_admin/filters/forms.rb
+++ b/lib/active_admin/filters/forms.rb
@@ -1,35 +1,34 @@
+# frozen_string_literal: true
module ActiveAdmin
module Filters
-
# This form builder defines methods to build filter forms such
# as the one found in the sidebar of the index page of a standard resource.
class FormBuilder < ::ActiveAdmin::FormBuilder
include ::ActiveAdmin::Filters::FormtasticAddons
-
- def initialize(*args)
- @use_form_buffer = true # force ActiveAdmin::FormBuilder to use the form buffer
- super
- end
+ self.input_namespaces = [::Object, ::ActiveAdmin::Inputs::Filters, ::ActiveAdmin::Inputs, ::Formtastic::Inputs]
def filter(method, options = {})
if method.present? && options[:as] ||= default_input_type(method)
- input(method, options)
+ template.concat input(method, options)
end
end
protected
- # Returns the default filter type for a given attribute
+ # Returns the default filter type for a given attribute. If you want
+ # to use a custom search method, you have to specify the type yourself.
def default_input_type(method, options = {})
- if method =~ /_(contains|starts_with|ends_with)\z/
+ if /_(eq|cont|start|end)\z/.match?(method)
:string
+ elsif klass._ransackers.key?(method.to_s)
+ klass._ransackers[method.to_s].type
elsif reflection_for(method) || polymorphic_foreign_type?(method)
:select
elsif column = column_for(method)
case column.type
when :date, :datetime
:date_range
- when :string, :text
+ when :string, :text, :citext
:string
when :integer, :float, :decimal
:numeric
@@ -38,52 +37,6 @@ def default_input_type(method, options = {})
end
end
end
-
- def custom_input_class_name(as)
- "Filter#{as.to_s.camelize}Input"
- end
-
- def active_admin_input_class_name(as)
- "ActiveAdmin::Inputs::Filter#{as.to_s.camelize}Input"
- end
-
- end
-
-
- # This module is included into the view
- module ViewHelper
-
- # Helper method to render a filter form
- def active_admin_filters_form_for(search, filters, options = {})
- defaults = { :builder => ActiveAdmin::Filters::FormBuilder,
- :url => collection_path,
- :html => {:class => 'filter_form'} }
- required = { :html => {:method => :get},
- :as => :q }
- options = defaults.deep_merge(options).deep_merge(required)
-
- form_for search, options do |f|
- filters.group_by{ |o| o[:attribute] }.each do |attribute, array|
- opts = array.last # grab last-defined `filter` call from DSL
- should = opts.delete(:if) || proc{ true }
- shouldnt = opts.delete(:unless) || proc{ false }
-
- if call_method_or_proc_on(self, should) && !call_method_or_proc_on(self, shouldnt)
- f.filter attribute, opts
- end
- end
-
- buttons = content_tag :div, :class => "buttons" do
- f.submit(I18n.t('active_admin.filters.buttons.filter')) +
- link_to(I18n.t('active_admin.filters.buttons.clear'), '#', :class => 'clear_filters_btn') +
- hidden_field_tags_for(params, :except => [:q, :page])
- end
-
- f.form_buffers.last + buttons
- end
- end
-
end
-
end
end
diff --git a/lib/active_admin/filters/formtastic_addons.rb b/lib/active_admin/filters/formtastic_addons.rb
index ac5a1f8ba2a..7df3f0571e1 100644
--- a/lib/active_admin/filters/formtastic_addons.rb
+++ b/lib/active_admin/filters/formtastic_addons.rb
@@ -1,43 +1,79 @@
+# frozen_string_literal: true
module ActiveAdmin
module Filters
module FormtasticAddons
#
- # The below are Formtastic overrides to use `base` instead of `class` for MetaSearch.
+ # The below are Formtastic method overrides that jump inside of the Ransack
+ # search object to get at the object being searched upon.
#
- # Returns the default label for a given attribute. Uses ActiveModel I18n if available.
def humanized_method_name
- if object.base.respond_to?(:human_attribute_name)
- object.base.human_attribute_name(method)
+ if klass.respond_to?(:human_attribute_name)
+ klass.human_attribute_name(method)
else
- method.to_s.send(builder.label_str_method)
+ method.to_s.public_send(builder.label_str_method)
end
end
- # Returns the association reflection for the method if it exists
def reflection_for(method)
- @object.base.reflect_on_association(method) if @object.base.respond_to?(:reflect_on_association)
+ klass.reflect_on_association(method) if klass.respond_to? :reflect_on_association
end
- # Returns the column for an attribute on the object being searched if it exists.
def column_for(method)
- @object.base.columns_hash[method.to_s] if @object.base.respond_to?(:columns_hash)
+ klass.columns_hash[method.to_s] if klass.respond_to? :columns_hash
+ end
+
+ def column
+ column_for method
end
#
# The below are custom methods that Formtastic does not provide.
#
- def foreign_key?(method)
- @object.base.reflections.select{ |_,r| r.macro == :belongs_to }.values
- .map(&:foreign_key).include? method.to_s
+ # The resource class, unwrapped from Ransack or `nil` for non-Ransack search objects.
+ def klass
+ @object.object.klass if @object.respond_to?(:object) && @object.object.respond_to?(:klass)
end
def polymorphic_foreign_type?(method)
- type = Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR == 0 ? proc{ |r| r.options[:foreign_type] } : :foreign_type
- @object.base.reflections.values.select{ |r| r.macro == :belongs_to && r.options[:polymorphic] }
- .map(&type).include? method.to_s
+ return false if klass.nil?
+
+ klass.reflect_on_all_associations.select { |r| r.macro == :belongs_to && r.options[:polymorphic] }
+ .map(&:foreign_type).include? method.to_s
+ end
+
+ #
+ # These help figure out whether the given method or association will be recognized by Ransack.
+ #
+
+ def searchable_has_many_through?
+ return false if klass.nil?
+
+ if klass.ransackable_associations.include?(method.to_s) && reflection && reflection.options[:through]
+ reflection.through_reflection.klass.ransackable_attributes.include? reflection.foreign_key
+ else
+ false
+ end
+ end
+
+ def seems_searchable?
+ column_for(method).nil? && (has_predicate? || scope?)
+ end
+
+ # If the given method has a predicate (like _eq or _lteq), it's pretty
+ # likely we're dealing with a valid search method.
+ def has_predicate?
+ !!Ransack::Predicate.detect_from_string(method.to_s)
+ end
+
+ # Ransack supports exposing selected scopes on your model for advanced searches.
+ def scope?
+ return false if klass.nil?
+
+ context = Ransack::Context.for klass
+ context.respond_to?(:ransackable_scope?) && context.ransackable_scope?(method.to_s, klass)
end
end
diff --git a/lib/active_admin/filters/resource_extension.rb b/lib/active_admin/filters/resource_extension.rb
index ba400054b51..1e73d0af880 100644
--- a/lib/active_admin/filters/resource_extension.rb
+++ b/lib/active_admin/filters/resource_extension.rb
@@ -1,20 +1,27 @@
+# frozen_string_literal: true
+require_relative "active"
+
module ActiveAdmin
module Filters
+ class Disabled < RuntimeError
+ def initialize(action)
+ super "Cannot #{action} a filter when filters are disabled. Enable filters with 'config.filters = true'"
+ end
+ end
module ResourceExtension
-
def initialize(*)
super
add_filters_sidebar_section
+ add_active_search_sidebar_section
end
# Returns the filters for this resource. If filters are not enabled,
- # it will always return an empty array.
+ # it will always return an empty hash.
#
- # @return [Array] Filters that apply for this resource
+ # @return [Hash] Filters that apply for this resource
def filters
- return [] unless filters_enabled?
- filter_lookup
+ filters_enabled? ? filter_lookup : {}
end
# Setter to enable / disable filters on this resource.
@@ -24,11 +31,23 @@ def filters=(bool)
@filters_enabled = bool
end
+ # Setter to enable/disable showing current filters on this resource.
+ #
+ # Set to `nil` to inherit the setting from the namespace
+ def current_filters=(bool)
+ @current_filters_enabled = bool
+ end
+
# @return [Boolean] If filters are enabled for this resource
def filters_enabled?
@filters_enabled.nil? ? namespace.filters : @filters_enabled
end
+ # @return [Boolean] If show current filters are enabled for this resource
+ def current_filters_enabled?
+ @current_filters_enabled.nil? ? namespace.current_filters : @current_filters_enabled
+ end
+
def preserve_default_filters!
@preserve_default_filters = true
end
@@ -40,14 +59,11 @@ def preserve_default_filters?
# Remove a filter for this resource. If filters are not enabled, this method
# will raise a RuntimeError
#
- # @param [Symbol] attribute The attribute to not filter on
- def remove_filter(attribute)
- unless filters_enabled?
- raise RuntimeError, "Can't remove a filter when filters are disabled. Enable filters with 'config.filters = true'"
- end
+ # @param [Symbol] attributes The attributes to not filter on
+ def remove_filter(*attributes)
+ raise Disabled, "remove" unless filters_enabled?
- @filters_to_remove ||= []
- @filters_to_remove << attribute
+ attributes.each { |attribute| (@filters_to_remove ||= []) << attribute.to_sym }
end
# Add a filter for this resource. If filters are not enabled, this method
@@ -55,14 +71,11 @@ def remove_filter(attribute)
#
# @param [Symbol] attribute The attribute to filter on
# @param [Hash] options The set of options that are passed through to
- # metasearch for the field definition.
+ # ransack for the field definition.
def add_filter(attribute, options = {})
- unless filters_enabled?
- raise RuntimeError, "Can't add a filter when filters are disabled. Enable filters with 'config.filters = true'"
- end
+ raise Disabled, "add" unless filters_enabled?
- @filters ||= []
- @filters << options.merge({ :attribute => attribute })
+ (@filters ||= {})[attribute.to_sym] = options
end
# Reset the filters to use defaults
@@ -76,47 +89,75 @@ def reset_filters!
# Collapses the waveform, if you will, of which filters should be displayed.
# Removes filters and adds in default filters as desired.
def filter_lookup
- filters = @filters.try(:dup) || []
- filters.push *default_filters if filters.empty? || preserve_default_filters?
+ filters = @filters.try(:dup) || {}
- if @filters_to_remove
- @filters_to_remove.each do |attr|
- filters.delete_if{ |f| f.fetch(:attribute) == attr }
+ if filters.empty? || preserve_default_filters?
+ default_filters.each do |f|
+ filters[f] ||= {}
end
end
+ if @filters_to_remove
+ @filters_to_remove.each { |filter| filters.delete(filter) }
+ end
+
filters
end
# @return [Array] The array of default filters for this resource
def default_filters
- default_association_filters + default_content_filters
+ result = []
+ result.concat default_association_filters if namespace.include_default_association_filters
+ result.concat content_columns
+ result.concat custom_ransack_filters
+ result
+ end
+
+ def custom_ransack_filters
+ if resource_class.respond_to?(:_ransackers)
+ resource_class._ransackers.keys.map(&:to_sym)
+ else
+ []
+ end
end
# Returns a default set of filters for the associations
def default_association_filters
- if resource_class.respond_to?(:reflections)
- block = if Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR == 0
- proc{ |_,r| [r.options[:foreign_type], r.primary_key_name] }
- else
- proc{ |_,r| [r.foreign_type, r.foreign_key] }
+ if resource_class.respond_to?(:reflect_on_all_associations)
+ poly, not_poly = resource_class.reflect_on_all_associations.partition { |r| r.macro == :belongs_to && r.options[:polymorphic] }
+
+ # remove deeply nested associations
+ not_poly.reject! { |r| r.chain.length > 2 }
+
+ filters = poly.map(&:foreign_type) + not_poly.map(&:name)
+
+ # Check high-arity associations for filterable columns
+ max = namespace.maximum_association_filter_arity
+ if max != :unlimited
+ high_arity, low_arity = not_poly.partition do |r|
+ r.klass.reorder(nil).limit(max + 1).count > max
+ end
+
+ # Remove high-arity associations with no searchable column
+ high_arity = high_arity.select { |r| searchable_column_for(r) }
+
+ high_arity = high_arity.map { |r| r.name.to_s + "_" + searchable_column_for(r) + namespace.filter_method_for_large_association }
+
+ filters = poly.map(&:foreign_type) + low_arity.map(&:name) + high_arity
end
- poly, not_poly = resource_class.reflections.partition{ |_,r| r.macro == :belongs_to && r.options[:polymorphic] }
- filters = poly.map(&block).flatten + not_poly.map(&:first)
- filters.collect{ |name| { :attribute => name.to_sym } }
+ filters.map(&:to_sym)
else
[]
end
end
- # Returns a default set of filters for the content columns
- def default_content_filters
- if resource_class.respond_to?(:content_columns)
- resource_class.content_columns.collect{|c| { :attribute => c.name.to_sym } }
- else
- []
- end
+ def search_columns
+ @search_columns ||= namespace.filter_columns_for_large_association.map(&:to_s)
+ end
+
+ def searchable_column_for(relation)
+ relation.klass.column_names.find { |name| search_columns.include?(name) }
end
def add_filters_sidebar_section
@@ -124,12 +165,24 @@ def add_filters_sidebar_section
end
def filters_sidebar_section
- ActiveAdmin::SidebarSection.new(:filters, :only => :index, :if => proc{ active_admin_config.filters.any? } ) do
- active_admin_filters_form_for assigns[:search], active_admin_config.filters
+ name = :filters
+ ActiveAdmin::SidebarSection.new name, only: :index, if: -> { active_admin_config.filters.any? } do
+ h3 I18n.t("active_admin.sidebars.#{name}", default: name.to_s.titlecase), class: "filters-form-title"
+ active_admin_filters_form_for assigns[:search], **active_admin_config.filters
end
end
- end
+ def add_active_search_sidebar_section
+ self.sidebar_sections << active_search_sidebar_section
+ end
+ def active_search_sidebar_section
+ name = :active_search
+ ActiveAdmin::SidebarSection.new name, only: :index, if: -> { active_admin_config.current_filters_enabled? && (params[:q] || params[:scope]) } do
+ filters = ActiveAdmin::Filters::Active.new(active_admin_config, assigns[:search])
+ render "active_filters", active_filters: filters
+ end
+ end
+ end
end
end
diff --git a/lib/active_admin/form_builder.rb b/lib/active_admin/form_builder.rb
index 07519a75d5e..5b0e0525fea 100644
--- a/lib/active_admin/form_builder.rb
+++ b/lib/active_admin/form_builder.rb
@@ -1,202 +1,183 @@
+# frozen_string_literal: true
+# Provides an intuitive way to build has_many associated records in the same form.
+module Formtastic
+ module Inputs
+ module Base
+ def input_wrapping(&block)
+ html = super
+ template.concat(html) if template.output_buffer && template.assigns[:has_many_block]
+ html
+ end
+ end
+ end
+end
+
module ActiveAdmin
class FormBuilder < ::Formtastic::FormBuilder
- attr_reader :form_buffers
+ self.input_namespaces = [::Object, ::ActiveAdmin::Inputs, ::Formtastic::Inputs]
- def initialize(*args)
- @form_buffers = ["".html_safe]
- super
+ def cancel_link(url = { action: "index" }, html_options = {}, li_attrs = {})
+ li_attrs[:class] ||= "action cancel"
+ html_options[:class] ||= "cancel-link"
+ li_content = template.link_to I18n.t("active_admin.cancel"), url, html_options
+ template.content_tag(:li, li_content, li_attrs)
end
- def inputs(*args, &block)
- @use_form_buffer = block_given?
- form_buffers.last << with_new_form_buffer{ super }
- end
-
- # If this `input` call is inside a `inputs` block, add the content
- # to the form buffer. Else, return it directly.
- def input(method, *args)
- content = with_new_form_buffer{ super }
- @use_form_buffer ? form_buffers.last << content : content
- end
+ attr_accessor :already_in_an_inputs_block
- def cancel_link(url = {:action => "index"}, html_options = {}, li_attrs = {})
- li_attrs[:class] ||= "cancel"
- li_content = template.link_to I18n.t('active_admin.cancel'), url, html_options
- form_buffers.last << template.content_tag(:li, li_content, li_attrs)
+ def has_many(assoc, options = {}, &block)
+ HasManyBuilder.new(self, assoc, options).render(&block)
end
+ end
- def actions(*args, &block)
- form_buffers.last << with_new_form_buffer do
- block_given? ? super : super{ commit_action_with_cancel_link }
+ # Decorates a FormBuilder with the additional attributes and methods
+ # to build a has_many block. Nested has_many blocks are handled by
+ # nested decorators.
+ class HasManyBuilder < SimpleDelegator
+ attr_reader :assoc
+ attr_reader :options
+ attr_reader :heading, :sortable_column, :sortable_start
+ attr_reader :new_record, :destroy_option, :remove_record
+
+ def initialize(has_many_form, assoc, options)
+ super has_many_form
+ @assoc = assoc
+ @options = extract_custom_settings!(options.dup)
+ @options.reverse_merge!(for: assoc)
+ @options[:class] = [options[:class], "inputs has-many-fields"].compact.join(" ")
+
+ if sortable_column
+ @options[:for] = [assoc, sorted_children(sortable_column)]
end
end
- def action(*args)
- form_buffers.last << with_new_form_buffer{ super }
+ def render(&block)
+ html = "".html_safe
+ html << template.content_tag(:h3, class: "has-many-fields-title") { heading } if heading.present?
+ html << template.capture { content_has_many(&block) }
+ html = wrap_div_or_li(html)
+ template.concat(html) if template.output_buffer
+ html
end
- def commit_action_with_cancel_link
- action(:submit)
- cancel_link
- end
-
- def has_many(association, options = {}, &block)
- options = { :for => association, :new_record => true }.merge(options)
- options[:class] ||= ""
- options[:class] << "inputs has_many_fields"
+ protected
- # Add Delete Links
- form_block = proc do |has_many_form|
- # @see https://github.com/justinfrench/formtastic/blob/2.2.1/lib/formtastic/helpers/inputs_helper.rb#L373
- contents = if block.arity == 1 # for backwards compatibility with REE & Ruby 1.8.x
- block.call(has_many_form)
- else
- index = parent_child_index(options[:parent]) if options[:parent]
- block.call(has_many_form, index)
- end
+ # remove options that should not render as attributes
+ def extract_custom_settings!(options)
+ @heading = options.key?(:heading) ? options.delete(:heading) : default_heading
+ @sortable_column = options.delete(:sortable)
+ @sortable_start = options.delete(:sortable_start) || 0
+ @new_record = options.key?(:new_record) ? options.delete(:new_record) : true
+ @destroy_option = options.delete(:allow_destroy)
+ @remove_record = options.delete(:remove_record)
+ options
+ end
- if has_many_form.object.new_record?
- contents += template.content_tag(:li, :class => 'has_many_delete') do
- template.link_to I18n.t('active_admin.has_many_delete'), "#", :onclick => "$(this).closest('.has_many_fields').remove(); return false;", :class => "button"
- end
- elsif options[:allow_destroy]
- has_many_form.input :_destroy, :as => :boolean, :wrapper_html => {:class => "has_many_remove"},
- :label => I18n.t('active_admin.has_many_remove')
+ def default_heading
+ assoc_klass.model_name.human(count: 2.1)
+ end
- end
+ def assoc_klass
+ @assoc_klass ||= __getobj__.object.class.reflect_on_association(assoc).klass
+ end
- contents
+ def content_has_many(&block)
+ form_block = proc do |form_builder|
+ render_has_many_form(form_builder, options[:parent], &block)
end
- form_buffers.last << with_new_form_buffer do
- template.content_tag :div, :class => "has_many #{association}" do
- # Allow customization of the nested form heading
- unless options.key?(:heading) && !options[:heading]
- form_heading = options[:heading] ||
- object.class.reflect_on_association(association).klass.model_name.human(:count => 1.1)
- form_buffers.last << template.content_tag(:h3, form_heading)
- end
+ template.assigns[:has_many_block] = true
+ contents = without_wrapper { inputs(options, &form_block) }
+ contents ||= "".html_safe
- inputs options, &form_block
-
- js = options[:new_record] ? js_for_has_many(association, form_block, template) : ""
- form_buffers.last << js.html_safe
- end
- end
+ js = new_record ? js_for_has_many(&form_block) : ""
+ contents << js
end
- def semantic_errors(*args)
- form_buffers.last << with_new_form_buffer{ super }
+ # Renders the Formtastic inputs then appends ActiveAdmin delete and sort actions.
+ def render_has_many_form(form_builder, parent, &block)
+ index = parent && form_builder.send(:parent_child_index, parent)
+ template.concat template.capture { yield(form_builder, index) }
+ template.concat has_many_actions(form_builder, "".html_safe)
end
- # These methods are deprecated and removed from Formtastic, however are
- # supported here to help with transition.
- module DeprecatedMethods
-
- # Formtastic has depreciated #commit_button in favor of #action(:submit)
- def commit_button(*args)
- ::ActiveSupport::Deprecation.warn("f.commit_button is deprecated in favour of f.action(:submit)")
-
- options = args.extract_options!
- if String === args.first
- options[:label] = args.first unless options.has_key?(:label)
+ def has_many_actions(form_builder, contents)
+ if form_builder.object.new_record?
+ contents << template.content_tag(:li, class: "input") do
+ remove_text = remove_record.is_a?(String) ? remove_record : I18n.t("active_admin.has_many_remove")
+ template.link_to remove_text, "#", class: "has-many-remove"
end
-
- action(:submit, options)
+ elsif allow_destroy?(form_builder.object)
+ form_builder.input(
+ :_destroy, as: :boolean,
+ wrapper_html: { class: "has-many-delete" },
+ label: I18n.t("active_admin.has_many_delete"))
end
- def commit_button_with_cancel_link
- # Formtastic has depreciated #buttons in favor of #actions
- ::ActiveSupport::Deprecation.warn("f.commit_button_with_cancel_link is deprecated in favour of f.commit_action_with_cancel_link")
+ if sortable_column
+ form_builder.input sortable_column, as: :hidden
- commit_action_with_cancel_link
+ # contents << template.content_tag(:li, class: "handle") do
+ # I18n.t("active_admin.move")
+ # end
end
- # The buttons method always needs to be wrapped in a new buffer
- def buttons(*args, &block)
- # Formtastic has depreciated #buttons in favor of #actions
- ::ActiveSupport::Deprecation.warn("f.buttons is deprecated in favour of f.actions")
-
- actions args, &block
- end
-
- end
- include DeprecatedMethods
-
- protected
-
- def active_admin_input_class_name(as)
- "ActiveAdmin::Inputs::#{as.to_s.camelize}Input"
+ contents
end
- # Overrides Formtastic's version to include ActiveAdmin::Inputs::*
- def input_class_with_const_defined(as)
- input_class_name = custom_input_class_name(as)
-
- if ::Object.const_defined?(input_class_name)
- input_class_name.constantize
- elsif ActiveAdmin::Inputs.const_defined?(input_class_name)
- active_admin_input_class_name(as).constantize
- elsif Formtastic::Inputs.const_defined?(input_class_name)
- standard_input_class_name(as).constantize
- else
- raise Formtastic::UnknownInputError, "Unable to find input class #{input_class_name}"
- end
+ def allow_destroy?(form_object)
+ !! case destroy_option
+ when Symbol, String
+ form_object.public_send destroy_option
+ when Proc
+ destroy_option.call form_object
+ else
+ destroy_option
+ end
end
- # use auto-loading in development environment
- def input_class_by_trying(as)
- begin
- custom_input_class_name(as).constantize
- rescue NameError
- begin
- active_admin_input_class_name(as).constantize
- rescue NameError
- standard_input_class_name(as).constantize
- end
+ def sorted_children(column)
+ __getobj__.object.public_send(assoc).sort_by do |o|
+ attribute = o.public_send column
+ [attribute.nil? ? Float::INFINITY : attribute, o.id || Float::INFINITY]
end
- rescue NameError
- raise Formtastic::UnknownInputError
end
- # This method calls the block it's passed (in our case, the `f.inputs` block)
- # and wraps the resulting HTML in a fieldset. If your block doesn't have a
- # valid return value but it was otherwise built correctly, we instead use
- # the most recent part of the Active Admin form buffer.
- def field_set_and_list_wrapping(*args, &block)
- block_given? ? super{
- (val = yield).is_a?(String) ? val : form_buffers.last
- } : super
- end
+ def without_wrapper
+ is_being_wrapped = already_in_an_inputs_block
+ self.already_in_an_inputs_block = false
- private
+ html = yield
- def with_new_form_buffer
- form_buffers << ''.html_safe
- return_value = (yield || '').html_safe
- form_buffers.pop
- return_value
+ self.already_in_an_inputs_block = is_being_wrapped
+ html
end
# Capture the ADD JS
- def js_for_has_many(association, form_block, template)
- assoc_reflection = object.class.reflect_on_association(association)
- assoc_name = assoc_reflection.klass.model_name
- placeholder = "NEW_#{assoc_name.to_s.upcase.split(' ').join('_')}_RECORD"
- opts = {
- :for => [association, assoc_reflection.klass.new],
- :class => "inputs has_many_fields",
- :for_options => { :child_index => placeholder }
+ def js_for_has_many(&form_block)
+ assoc_name = assoc_klass.model_name
+ placeholder = "NEW_#{assoc_name.to_s.underscore.upcase.tr('/', '_')}_RECORD"
+ opts = options.merge(
+ for: [assoc, assoc_klass.new],
+ for_options: { child_index: placeholder }
+ )
+ html = template.capture { __getobj__.send(:inputs_for_nested_attributes, opts, &form_block) }
+ text = new_record.is_a?(String) ? new_record : I18n.t("active_admin.has_many_new", model: assoc_name.human)
+
+ template.link_to text, "#", class: "has-many-add", data: {
+ html: CGI.escapeHTML(html).html_safe, placeholder: placeholder
}
- js = with_new_form_buffer{ inputs_for_nested_attributes opts, &form_block }
- js = template.escape_javascript js
-
- onclick = "$(this).before('#{js}'.replace(/#{placeholder}/g, new Date().getTime())); return false;"
- text = I18n.t 'active_admin.has_many_new', :model => assoc_name.human
-
- template.link_to(text, "#", :onclick => onclick, :class => "button").html_safe
end
+ def wrap_div_or_li(html)
+ template.content_tag(
+ already_in_an_inputs_block ? :li : :div,
+ html,
+ class: "has-many-container",
+ "data-has-many-association" => assoc,
+ "data-sortable" => sortable_column,
+ "data-sortable-start" => sortable_start)
+ end
end
end
diff --git a/lib/active_admin/helpers/collection.rb b/lib/active_admin/helpers/collection.rb
deleted file mode 100644
index c3f2a77377b..00000000000
--- a/lib/active_admin/helpers/collection.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module ActiveAdmin
- module Helpers
- module Collection
- # Works around this issue: https://github.com/rails/rails/issues/7121
- #
- # GROUP BY + COUNT drops SELECT statement. This leads to SQL error when
- # the ORDER statement mentions a column defined in the SELECT statement.
- #
- # We remove the ORDER statement to work around this issue.
- def collection_size(collection=collection)
- size = collection.reorder("").count
- # when GROUP BY is used, AR returns Hash instead of Fixnum for .size
- size = size.size if size.kind_of?(Hash)
-
- size
- end
-
- def collection_is_empty?(collection=collection)
- collection_size(collection) == 0
- end
- end
- end
-end
diff --git a/lib/active_admin/helpers/optional_display.rb b/lib/active_admin/helpers/optional_display.rb
index 01d04ebd0c9..92f2446b49f 100644
--- a/lib/active_admin/helpers/optional_display.rb
+++ b/lib/active_admin/helpers/optional_display.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
# Shareable module to give a #display_on?(action) method
@@ -5,24 +6,24 @@ module ActiveAdmin
#
# The options hash accepts:
#
- # :only => :index
- # :only => [:index, :show]
- # :except => :index
- # :except => [:index, :show]
+ # only: :index
+ # only: [:index, :show]
+ # except: :index
+ # except: [:index, :show]
#
# call #normalize_display_options! after @options has been set
# to ensure that the display options are setup correctly
module OptionalDisplay
def display_on?(action, render_context = self)
- return false if @options[:only] && !@options[:only].include?(action.to_sym)
+ return false if @options[:only] && !@options[:only].include?(action.to_sym)
return false if @options[:except] && @options[:except].include?(action.to_sym)
case condition = @options[:if]
when Symbol, String
- render_context.send condition
+ render_context.public_send condition
when Proc
- render_context.instance_exec &condition
+ render_context.instance_exec(&condition)
else
true
end
@@ -31,7 +32,7 @@ def display_on?(action, render_context = self)
private
def normalize_display_options!
- @options[:only] = Array(@options[:only]) if @options[:only]
+ @options[:only] = Array(@options[:only]) if @options[:only]
@options[:except] = Array(@options[:except]) if @options[:except]
end
end
diff --git a/lib/active_admin/helpers/routes/url_helpers.rb b/lib/active_admin/helpers/routes/url_helpers.rb
new file mode 100644
index 00000000000..bacbe721d21
--- /dev/null
+++ b/lib/active_admin/helpers/routes/url_helpers.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Helpers
+ module Routes
+ module UrlHelpers
+ include Rails.application.routes.url_helpers
+ end
+
+ extend UrlHelpers
+
+ def self.default_url_options
+ Rails.application.routes.default_url_options || {}
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/helpers/scope_chain.rb b/lib/active_admin/helpers/scope_chain.rb
index d27d33d347d..d585fd804d3 100644
--- a/lib/active_admin/helpers/scope_chain.rb
+++ b/lib/active_admin/helpers/scope_chain.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
module ActiveAdmin
module ScopeChain
+ private
# Scope an ActiveRecord::Relation chain
#
# Example:
@@ -12,7 +14,7 @@ module ScopeChain
#
def scope_chain(scope, chain)
if scope.scope_method
- chain.send(scope.scope_method)
+ chain.public_send scope.scope_method
elsif scope.scope_block
instance_exec chain, &scope.scope_block
else
diff --git a/lib/active_admin/helpers/settings.rb b/lib/active_admin/helpers/settings.rb
deleted file mode 100644
index f2406791deb..00000000000
--- a/lib/active_admin/helpers/settings.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-module ActiveAdmin
-
- # Adds a class method to a class to create settings with default values.
- #
- # Example:
- #
- # class Configuration
- # include ActiveAdmin::Settings
- #
- # setting :site_title, "Default Site Title"
- # end
- #
- # conf = Configuration.new
- # conf.site_title #=> "Default Site Title"
- # conf.site_title = "Override Default"
- # conf.site_title #=> "Override Default"
- #
- module Settings
-
- def self.included(base)
- base.extend ClassMethods
- end
-
- def read_default_setting(name)
- default_settings[name]
- end
-
- private
-
- def default_settings
- self.class.default_settings
- end
-
- module ClassMethods
-
- def setting(name, default)
- default_settings[name] = default
- attr_writer name
-
- # Creates a reader that will grab the default if no value has been set.
- class_eval <<-EOC, __FILE__, __LINE__ + 1
- def #{name}
- if instance_variable_defined? :@#{name}
- @#{name}
- else
- read_default_setting(:#{name})
- end
- end
- EOC
- end
-
- def deprecated_setting(name, default, message = nil)
- setting(name, default)
-
- message ||= "The #{name} setting is deprecated and will be removed."
- ActiveAdmin::Deprecation.deprecate self, name, message
- ActiveAdmin::Deprecation.deprecate self, :"#{name}=", message
- end
-
- def default_settings
- @default_settings ||= {}
- end
-
- end
-
-
- # Allows you to define child classes that should receive the same
- # settings, as well as the same default values.
- #
- # Example from the codebase:
- #
- # class Application
- # include Settings
- # include Settings::Inheritance
- #
- # settings_inherited_by :Namespace
- #
- # inheritable_setting :root_to, 'dashboard#index'
- # end
- #
- module Inheritance
-
- def self.included(base)
- base.extend ClassMethods
- end
-
- module ClassMethods
-
- def settings_inherited_by(heir)
- (@setting_heirs ||= []) << heir
- heir.send :include, ActiveAdmin::Settings
- end
-
- def inheritable_setting(name, default)
- setting name, default
- @setting_heirs.each{ |c| c.setting name, default }
- end
-
- def deprecated_inheritable_setting(name, default)
- deprecated_setting name, default
- @setting_heirs.each{ |c| c.deprecated_setting name, default }
- end
-
- end
- end
-
- end
-end
diff --git a/lib/active_admin/iconic.rb b/lib/active_admin/iconic.rb
deleted file mode 100644
index 4c4bf557681..00000000000
--- a/lib/active_admin/iconic.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'active_admin/iconic/icons'
-
-module ActiveAdmin
- module Iconic
-
- # Default color to use for icons
- @@default_color = "#5E6469"
- mattr_accessor :default_color
-
- # Default width to use for icons
- @@default_width = 15
- mattr_accessor :default_width
-
- # Default height to use for icons
- @@default_height = 15
- mattr_accessor :default_height
-
- # Render an icon:
- # Iconic.icon :loop
- def self.icon(name, options = {})
- options = {
- :color => default_color,
- :width => default_width,
- :height => default_height,
- :id => ""
- }.merge(options)
-
-
- options[:style] = "fill:#{options[:color]};"
- options[:fill] = options.delete(:color)
-
- # Convert to strings representations of pixels
- [:width, :height].each do |key|
- options[key] = "#{options[key]}px" unless options[key].is_a?(String)
- end
-
- template = ICONS[name.to_sym]
-
- if template
- svg = template.dup
- options.each do |key, value|
- svg.gsub!("{#{key}}", value)
- end
- "#{svg}".html_safe
- else
- raise "Could not find the icon named #{name}"
- end
- end
-
- end
-end
diff --git a/lib/active_admin/iconic/icons.rb b/lib/active_admin/iconic/icons.rb
deleted file mode 100644
index bee44720a87..00000000000
--- a/lib/active_admin/iconic/icons.rb
+++ /dev/null
@@ -1,142 +0,0 @@
-module ActiveAdmin
- module Iconic
- ICONS = {
- :arrow_down => '',
- :arrow_down_alt1 => '',
- :arrow_down_alt2 => '',
- :arrow_left => '',
- :arrow_left_alt1 => '',
- :arrow_left_alt2 => '',
- :arrow_right => '',
- :arrow_right_alt1 => '',
- :arrow_right_alt2 => '',
- :arrow_up => '',
- :arrow_up_alt1 => '',
- :arrow_up_alt2 => '',
- :article => '',
- :at => '',
- :battery_charging => '',
- :battery_empty => '',
- :battery_full => '',
- :battery_half => '',
- :beaker => '',
- :beaker_alt => '',
- :bolt => '',
- :book => '',
- :book_alt => '',
- :box => '',
- :calendar => '',
- :calendar_alt_fill => '',
- :calendar_alt_stroke => '',
- :cd => '',
- :chat => '',
- :chat_alt_fill => '',
- :chat_alt_stroke => '',
- :check => '',
- :check_alt => '',
- :clock => '',
- :cloud => '',
- :cog => '',
- :cog_alt => '',
- :comment_alt1_fill => '',
- :comment_alt1_stroke => '',
- :comment_alt2_fill => '',
- :comment_alt2_stroke => '',
- :comment_fill => '',
- :comment_stroke => '',
- :compass => '',
- :cursor => '',
- :denied => '',
- :denied_alt => '',
- :dial => '',
- :document_fill => '',
- :document_stroke => '',
- :eject => '',
- :equalizer => '',
- :eyedropper => '',
- :first => '',
- :folder_fill => '',
- :folder_stroke => '',
- :fork => '',
- :fullscreen => '',
- :fullscreen_alt => '',
- :fullscreen_exit => '',
- :fullscreen_exit_alt => '',
- :headphones => '',
- :heart_fill => '',
- :heart_stroke => '',
- :home => '',
- :image => '',
- :info => '',
- :iphone => '',
- :key_fill => '',
- :key_stroke => '',
- :last => '',
- :left_quote => '',
- :left_quote_alt => '',
- :lightbulb => '',
- :link => '',
- :lock_fill => '',
- :lock_stroke => '',
- :loop => '',
- :loop_alt1 => '',
- :loop_alt2 => '',
- :loop_alt3 => '',
- :loop_alt4 => '',
- :magnifying_glass => '',
- :magnifying_glass_alt => '',
- :mail => '',
- :mail_alt => '',
- :map_pin_fill => '',
- :map_pin_stroke => '',
- :minus => '',
- :minus_alt => '',
- :moon_fill => '',
- :moon_stroke => '',
- :move => '',
- :move_alt1 => '',
- :move_alt2 => '',
- :move_horizontal => '',
- :move_horizontal_alt1 => '',
- :move_horizontal_alt2 => '',
- :move_vertical => '',
- :move_vertical_alt1 => '',
- :move_vertical_alt2 => '',
- :movie => '',
- :new_window => '',
- :pause => '',
- :pen => '',
- :pen_alt_fill => '',
- :pen_alt_stroke => '',
- :pin => '',
- :play => '',
- :play_alt => '',
- :plus => '',
- :plus_alt => '',
- :read_more => '',
- :reload => '',
- :reload_alt => '',
- :right_quote => '',
- :right_quote_alt => '',
- :rss => '',
- :rss_alt => '',
- :spin => '',
- :spin_alt => '',
- :star => '',
- :stop => '',
- :sun => '',
- :tag_fill => '',
- :tag_stroke => '',
- :trash_fill => '',
- :trash_stroke => '',
- :undo => '',
- :unlock_fill => '',
- :unlock_stroke => '',
- :user => '',
- :volume => '',
- :volume_mute => '',
- :x => '',
- :x_alt => '',
- }
- end
-end
diff --git a/lib/active_admin/inputs.rb b/lib/active_admin/inputs.rb
index 1ca41d81160..110eb0a437d 100644
--- a/lib/active_admin/inputs.rb
+++ b/lib/active_admin/inputs.rb
@@ -1,15 +1,19 @@
+# frozen_string_literal: true
module ActiveAdmin
module Inputs
extend ActiveSupport::Autoload
- autoload :DatepickerInput
+ module Filters
+ extend ActiveSupport::Autoload
- autoload :FilterBase
- autoload :FilterStringInput
- autoload :FilterDateRangeInput
- autoload :FilterNumericInput
- autoload :FilterSelectInput
- autoload :FilterCheckBoxesInput
- autoload :FilterBooleanInput
+ autoload :Base
+ autoload :StringInput
+ autoload :TextInput
+ autoload :DateRangeInput
+ autoload :NumericInput
+ autoload :SelectInput
+ autoload :CheckBoxesInput
+ autoload :BooleanInput
+ end
end
end
diff --git a/lib/active_admin/inputs/datepicker_input.rb b/lib/active_admin/inputs/datepicker_input.rb
deleted file mode 100644
index baba4bc0d0b..00000000000
--- a/lib/active_admin/inputs/datepicker_input.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActiveAdmin
- module Inputs
- class DatepickerInput < ::Formtastic::Inputs::StringInput
- def input_html_options
- options = super
- options[:class] = [options[:class], "datepicker"].compact.join(' ')
- options
- end
- end
- end
-end
diff --git a/lib/active_admin/inputs/filter_base.rb b/lib/active_admin/inputs/filter_base.rb
deleted file mode 100644
index 29862229d5c..00000000000
--- a/lib/active_admin/inputs/filter_base.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-module ActiveAdmin
- module Inputs
- module FilterBase
- include ::Formtastic::Inputs::Base
- include ::ActiveAdmin::Filters::FormtasticAddons
-
- extend ::ActiveSupport::Autoload
- autoload :SearchMethodSelect
-
- def input_wrapping(&block)
- template.content_tag :div, template.capture(&block), wrapper_html_options
- end
-
- def required?
- false
- end
-
- def wrapper_html_options
- { :class => "filter_form_field #{as}" }
- end
-
- # Override the standard finder to accept a proc
- def collection_from_options
- if options[:collection].is_a?(Proc)
- template.instance_eval(&options[:collection])
- else
- super
- end
- end
-
- end
- end
-end
diff --git a/lib/active_admin/inputs/filter_base/search_method_select.rb b/lib/active_admin/inputs/filter_base/search_method_select.rb
deleted file mode 100644
index 68384aaa185..00000000000
--- a/lib/active_admin/inputs/filter_base/search_method_select.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# This is a common set of Formtastic overrides needed to build a filter form
-# that lets you select from a set of search methods for a given attribute.
-#
-# Your class must declare available filters for this module to work.
-# Those filters must be recognizable by Metasearch. For example:
-#
-# class FilterNumericInput < ::Formtastic::Inputs::NumberInput
-# include FilterBase
-# include FilterBase::SearchMethodSelect
-#
-# filter :equals, :greater_than, :less_than
-# end
-#
-module ActiveAdmin
- module Inputs
- module FilterBase
- module SearchMethodSelect
-
- def self.included(base)
- base.extend ClassMethods
- end
-
- module ClassMethods
- attr_reader :filters
-
- def filter(*filters)
- (@filters ||= []).push *filters
- end
- end
-
- def wrapper_html_options
- opts = super
- (opts[:class] ||= '') << ' select_and_search'
- opts
- end
-
- def to_html
- input_wrapping do
- label_html << # your label
- select_html << # the dropdown that holds the available search methods
- input_html # your input field
- end
- end
-
- def input_html
- builder.text_field current_filter, input_html_options
- end
-
- def select_html
- template.select_tag '', template.options_for_select(filter_options, current_filter)
- end
-
- def filters
- options[:filters] || self.class.filters
- end
-
- def current_filter
- @current_filter ||= begin
- methods = filters.map{ |f| "#{method}_#{f}" }
- methods.detect{ |m| @object.send m } || methods.first
- end
- end
-
- def filter_options
- filters.collect do |filter|
- [I18n.t("active_admin.filters.predicates.#{filter}"), "#{method}_#{filter}"]
- end
- end
-
- end
- end
- end
-end
diff --git a/lib/active_admin/inputs/filter_boolean_input.rb b/lib/active_admin/inputs/filter_boolean_input.rb
deleted file mode 100644
index 0e71c6355ca..00000000000
--- a/lib/active_admin/inputs/filter_boolean_input.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-module ActiveAdmin
- module Inputs
- class FilterBooleanInput < ::Formtastic::Inputs::BooleanInput
- include FilterBase
-
- def to_html
- input_wrapping do
- [ label_html,
- check_box_html
- ].join("\n").html_safe
- end
- end
-
- def search_method
- method.to_s.match(search_conditions) ? method : "#{method}_eq"
- end
-
- def checked?
- object && boolean_checked?(object.send(search_method), checked_value)
- end
-
- def input_html_options
- { :name => "q[#{ search_method }]" }
- end
-
- def search_conditions
- /(is_true|is_false|is_present|is_blank|is_null|is_not_null)\z/
- end
-
- end
- end
-end
diff --git a/lib/active_admin/inputs/filter_check_boxes_input.rb b/lib/active_admin/inputs/filter_check_boxes_input.rb
deleted file mode 100644
index 464a2c0bcbf..00000000000
--- a/lib/active_admin/inputs/filter_check_boxes_input.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-module ActiveAdmin
- module Inputs
- class FilterCheckBoxesInput < ::Formtastic::Inputs::CheckBoxesInput
- include FilterBase
-
- def input_name
- "#{object_name}[#{association_primary_key || method}_in][]"
- end
-
- def selected_values
- @object.send("#{association_primary_key || method}_in") || []
- end
-
- # Add whitespace before label
- def choice_label(choice)
- ' ' + super
- end
-
- # Don't wrap in UL tag
- def choices_group_wrapping(&block)
- template.capture(&block)
- end
-
- # Don't wrap in LI tag
- def choice_wrapping(html_options, &block)
- template.capture(&block)
- end
-
- # Don't render hidden fields
- def hidden_field_for_all
- ""
- end
-
- # Don't render hidden fields
- def hidden_fields?
- false
- end
- end
- end
-end
diff --git a/lib/active_admin/inputs/filter_date_range_input.rb b/lib/active_admin/inputs/filter_date_range_input.rb
deleted file mode 100644
index 94f80cbdf97..00000000000
--- a/lib/active_admin/inputs/filter_date_range_input.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module ActiveAdmin
- module Inputs
- class FilterDateRangeInput < ::Formtastic::Inputs::StringInput
- include FilterBase
-
- def to_html
- input_wrapping do
- [ label_html,
- builder.text_field(gt_input_name, input_html_options(gt_input_name)),
- template.content_tag(:span, "-", :class => "seperator"),
- builder.text_field(lt_input_name, input_html_options(lt_input_name)),
- ].join("\n").html_safe
- end
- end
-
- def gt_input_name
- "#{method}_gte"
- end
- alias :input_name :gt_input_name
-
- def lt_input_name
- "#{method}_lte"
- end
-
- def input_html_options(input_name = gt_input_name)
- current_value = @object.send(input_name)
- { :size => 12,
- :class => "datepicker",
- :max => 10,
- :value => current_value.respond_to?(:strftime) ? current_value.strftime("%Y-%m-%d") : "" }
- end
- end
- end
-end
diff --git a/lib/active_admin/inputs/filter_numeric_input.rb b/lib/active_admin/inputs/filter_numeric_input.rb
deleted file mode 100644
index 0342293512f..00000000000
--- a/lib/active_admin/inputs/filter_numeric_input.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module ActiveAdmin
- module Inputs
- class FilterNumericInput < ::Formtastic::Inputs::NumberInput
- include FilterBase
- include FilterBase::SearchMethodSelect
-
- filter :equals, :greater_than, :less_than
- end
- end
-end
diff --git a/lib/active_admin/inputs/filter_select_input.rb b/lib/active_admin/inputs/filter_select_input.rb
deleted file mode 100644
index 85ddafcdeec..00000000000
--- a/lib/active_admin/inputs/filter_select_input.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-module ActiveAdmin
- module Inputs
- class FilterSelectInput < ::Formtastic::Inputs::SelectInput
- include FilterBase
-
- # When it's a HABTM or has_many association, Formtastic builds "object_ids".
- # That doesn't fit our scenario, so we override it here.
- def input_name
- name = method.to_s
- name.concat '_id' if reflection
- name.concat multiple? ? '_in' : '_eq'
- end
-
- # Include the "Any" option if it's a dropdown, but not if it's a multi-select.
- def input_options
- super.merge :include_blank => multiple? ? false : I18n.t('active_admin.any')
- end
-
- # was "#{object_name}[#{association_primary_key}]"
- def input_html_options_name
- "#{object_name}[#{input_name}]"
- end
-
- # Would normally return true for has_many and HABTM, which would subsequently
- # cause the select field to be multi-select instead of a dropdown.
- def multiple_by_association?
- false
- end
-
- # Provides an efficient default lookup query if the attribute is a DB column.
- def collection
- unless Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR < 2
- return pluck_column if !options[:collection] && column_for(method)
- end
- super
- end
-
- def pluck_column
- @object.base.reorder("#{method} asc").uniq.pluck method
- end
-
- end
- end
-end
diff --git a/lib/active_admin/inputs/filter_string_input.rb b/lib/active_admin/inputs/filter_string_input.rb
deleted file mode 100644
index cbc27a0f045..00000000000
--- a/lib/active_admin/inputs/filter_string_input.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-module ActiveAdmin
- module Inputs
- class FilterStringInput < ::Formtastic::Inputs::StringInput
- include FilterBase
- include FilterBase::SearchMethodSelect
-
- filter :contains, :equals, :starts_with, :ends_with
-
- # If the filter method includes a search condition, build a normal string search field.
- # Else, build a search field with a companion dropdown to choose a search condition from.
- def to_html
- if method.to_s =~ /_(#{filters.join('|')})\z/
- input_wrapping do
- label_html <<
- builder.text_field(method, input_html_options)
- end
- else
- super # SearchMethodSelect#to_html
- end
- end
-
- end
- end
-end
diff --git a/lib/active_admin/inputs/filters/base.rb b/lib/active_admin/inputs/filters/base.rb
new file mode 100644
index 00000000000..3eba474bcea
--- /dev/null
+++ b/lib/active_admin/inputs/filters/base.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Inputs
+ module Filters
+ module Base
+ include ::Formtastic::Inputs::Base
+ include ::ActiveAdmin::Filters::FormtasticAddons
+
+ extend ::ActiveSupport::Autoload
+ autoload :SearchMethodSelect
+
+ def input_wrapping(&block)
+ template.content_tag :div, template.capture(&block), wrapper_html_options
+ end
+
+ def required?
+ false
+ end
+
+ # Can pass proc to filter label option
+ def label_from_options
+ res = super
+ res = res.call if res.is_a? Proc
+ res
+ end
+
+ def wrapper_html_options
+ opts = super
+ (opts[:class] ||= "") << " filters-form-field"
+ opts
+ end
+
+ # Override the standard finder to accept a proc
+ def collection_from_options
+ if options[:collection].is_a?(Proc)
+ template.instance_exec(&options[:collection])
+ else
+ super
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/inputs/filters/base/search_method_select.rb b/lib/active_admin/inputs/filters/base/search_method_select.rb
new file mode 100644
index 00000000000..06046e54c42
--- /dev/null
+++ b/lib/active_admin/inputs/filters/base/search_method_select.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+# This is a common set of Formtastic overrides needed to build a filter form
+# that lets you select from a set of search methods for a given attribute.
+#
+# Your class must declare available filters for this module to work.
+# Those filters must be recognizable by Ransack. For example:
+#
+# class NumericInput < ::Formtastic::Inputs::NumberInput
+# include Base
+# include Base::SearchMethodSelect
+#
+# filter :eq, :gt, :lt
+# end
+#
+module ActiveAdmin
+ module Inputs
+ module Filters
+ module Base
+ module SearchMethodSelect
+
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ attr_reader :filters
+
+ def filter(*filters)
+ (@filters ||= []).push(*filters)
+ end
+ end
+
+ def to_html
+ input_wrapping do
+ [
+ label_html, # your label
+ '
',
+ select_html, # the dropdown that holds the available search methods
+ input_html, # your input field
+ '
'
+ ].join("\n").html_safe
+ end
+ end
+
+ def input_html
+ builder.text_field current_filter, input_html_options
+ end
+
+ def select_html
+ template.select_tag "", template.options_for_select(filter_options, current_filter), "data-search-methods": ""
+ end
+
+ def filters
+ options[:filters] || self.class.filters
+ end
+
+ def current_filter
+ @current_filter ||= begin
+ methods = filters.map { |f| "#{method}_#{f}" }
+ methods.detect { |m| @object.public_send m } || methods.first
+ end
+ end
+
+ def filter_options
+ filters.collect do |filter|
+ [Ransack::Translate.predicate(filter).capitalize, "#{method}_#{filter}"]
+ end
+ end
+
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/inputs/filters/boolean_input.rb b/lib/active_admin/inputs/filters/boolean_input.rb
new file mode 100644
index 00000000000..978addee236
--- /dev/null
+++ b/lib/active_admin/inputs/filters/boolean_input.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Inputs
+ module Filters
+ class BooleanInput < ::Formtastic::Inputs::SelectInput
+ include Base
+
+ def input_name
+ return method if seems_searchable?
+
+ "#{method}_eq"
+ end
+
+ def input_html_options_name
+ "#{object_name}[#{input_name}]" # was "#{object_name}[#{association_primary_key}]"
+ end
+
+ # Provide the AA translation to the blank input field.
+ def include_blank
+ I18n.t "active_admin.any" if super
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/inputs/filters/check_boxes_input.rb b/lib/active_admin/inputs/filters/check_boxes_input.rb
new file mode 100644
index 00000000000..79440992ed2
--- /dev/null
+++ b/lib/active_admin/inputs/filters/check_boxes_input.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Inputs
+ module Filters
+ class CheckBoxesInput < ::Formtastic::Inputs::CheckBoxesInput
+ include Base
+
+ def input_name
+ "#{object_name}[#{searchable_method_name}_in][]"
+ end
+
+ def checked?(value)
+ selected_values.include?(value.to_s)
+ end
+
+ def selected_values
+ @selected_values ||= (@object.public_send(:"#{searchable_method_name}_in") || []).map(&:to_s)
+ end
+
+ def searchable_method_name
+ if searchable_has_many_through?
+ "#{reflection.through_reflection.name}_#{reflection.foreign_key}"
+ else
+ association_primary_key || method
+ end
+ end
+
+ # Don't wrap in UL tag
+ def choices_group_wrapping(&block)
+ template.capture(&block)
+ end
+
+ # Don't wrap in LI tag
+ def choice_wrapping(html_options, &block)
+ template.capture(&block)
+ end
+
+ # Don't render hidden fields
+ def hidden_field_for_all
+ ""
+ end
+
+ # Don't render hidden fields
+ def hidden_fields?
+ false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/inputs/filters/date_range_input.rb b/lib/active_admin/inputs/filters/date_range_input.rb
new file mode 100644
index 00000000000..edb33f215cf
--- /dev/null
+++ b/lib/active_admin/inputs/filters/date_range_input.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Inputs
+ module Filters
+ class DateRangeInput < ::Formtastic::Inputs::StringInput
+ include Base
+
+ def to_html
+ input_wrapping do
+ [ label_html,
+ '
'
+ ].join("\n").html_safe
+ end
+ end
+
+ def gt_input_name
+ "#{method}_gteq"
+ end
+ alias :input_name :gt_input_name
+
+ def lt_input_name
+ "#{method}_lteq"
+ end
+
+ def input_html_options
+ { size: 12,
+ class: "datepicker",
+ maxlength: 10 }.merge(options[:input_html] || {})
+ end
+
+ def input_html_options_for(input_name, placeholder)
+ current_value = begin
+ #cast value to date object before rendering input
+ @object.public_send(input_name).to_s.to_date
+ rescue
+ nil
+ end
+ { placeholder: placeholder,
+ value: current_value ? current_value.strftime("%Y-%m-%d") : "" }.merge(input_html_options)
+ end
+
+ def gt_input_placeholder
+ I18n.t("active_admin.filters.predicates.from")
+ end
+
+ def lt_input_placeholder
+ I18n.t("active_admin.filters.predicates.to")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/inputs/filters/numeric_input.rb b/lib/active_admin/inputs/filters/numeric_input.rb
new file mode 100644
index 00000000000..62838cab9e3
--- /dev/null
+++ b/lib/active_admin/inputs/filters/numeric_input.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Inputs
+ module Filters
+ class NumericInput < ::Formtastic::Inputs::NumberInput
+ include Base
+ include Base::SearchMethodSelect
+
+ filter :eq, :gt, :lt
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/inputs/filters/select_input.rb b/lib/active_admin/inputs/filters/select_input.rb
new file mode 100644
index 00000000000..23c9a041022
--- /dev/null
+++ b/lib/active_admin/inputs/filters/select_input.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Inputs
+ module Filters
+ class SelectInput < ::Formtastic::Inputs::SelectInput
+ include Base
+
+ def input_name
+ return method if seems_searchable?
+
+ searchable_method_name + (multiple? ? "_in" : "_eq")
+ end
+
+ def searchable_method_name
+ if searchable_has_many_through?
+ "#{reflection.through_reflection.name}_#{reflection.foreign_key}"
+ else
+ reflection_searchable? ? "#{method}_#{reflection.association_primary_key}" : method.to_s
+ end
+ end
+
+ # Provide the AA translation to the blank input field.
+ def include_blank
+ I18n.t "active_admin.any" if super
+ end
+
+ def input_html_options_name
+ "#{object_name}[#{input_name}]" # was "#{object_name}[#{association_primary_key}]"
+ end
+
+ # Would normally return true for has_many and HABTM, which would subsequently
+ # cause the select field to be multi-select instead of a dropdown.
+ def multiple_by_association?
+ false
+ end
+
+ # Provides an efficient default lookup query if the attribute is a DB column.
+ def collection
+ if !options[:collection] && column
+ pluck_column
+ else
+ super
+ end
+ rescue ActiveRecord::QueryCanceled => error
+ raise ActiveRecord::QueryCanceled.new "#{error.message.strip} while querying the values for the ActiveAdmin :#{method} filter"
+ end
+
+ def pluck_column
+ klass.reorder("#{method} asc").distinct.pluck method
+ end
+
+ def reflection_searchable?
+ reflection && !reflection.polymorphic?
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/inputs/filters/string_input.rb b/lib/active_admin/inputs/filters/string_input.rb
new file mode 100644
index 00000000000..d1574a369dc
--- /dev/null
+++ b/lib/active_admin/inputs/filters/string_input.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Inputs
+ module Filters
+ class StringInput < ::Formtastic::Inputs::StringInput
+ include Base
+ include Base::SearchMethodSelect
+
+ filter :cont, :eq, :start, :end
+
+ # If the filter method includes a search condition, build a normal string search field.
+ # Else, build a search field with a companion dropdown to choose a search condition from.
+ def to_html
+ if seems_searchable?
+ input_wrapping do
+ label_html <<
+ builder.text_field(method, input_html_options)
+ end
+ else
+ super # SearchMethodSelect#to_html
+ end
+ end
+
+ def filters
+ options[:filters] || resource_default_filters || namespace_default_filters || self.class.filters
+ end
+
+ private
+
+ def resource_default_filters
+ config = template.active_admin_config if template.respond_to?(:active_admin_config)
+ config.string_input_filters if config.respond_to?(:string_input_filters)
+ end
+
+ def namespace_default_filters
+ template.respond_to?(:active_admin_config) &&
+ template.active_admin_config&.namespace&.string_input_filters
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/inputs/filters/text_input.rb b/lib/active_admin/inputs/filters/text_input.rb
new file mode 100644
index 00000000000..1b228f15604
--- /dev/null
+++ b/lib/active_admin/inputs/filters/text_input.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Inputs
+ module Filters
+ class TextInput < ::Formtastic::Inputs::TextInput
+ include Base
+ include Base::SearchMethodSelect
+
+ def input_html_options
+ {
+ cols: builder.default_text_area_width,
+ rows: builder.default_text_area_height
+ }.merge(super)
+ end
+
+ def to_html
+ input_wrapping do
+ label_html <<
+ builder.text_area(method, input_html_options)
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/localizers.rb b/lib/active_admin/localizers.rb
new file mode 100644
index 00000000000..a0a8f1f4b12
--- /dev/null
+++ b/lib/active_admin/localizers.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+require_relative "localizers/resource_localizer"
+
+module ActiveAdmin
+ module Localizers
+ class << self
+ def resource(active_admin_config)
+ ResourceLocalizer.from_resource(active_admin_config)
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/localizers/resource_localizer.rb b/lib/active_admin/localizers/resource_localizer.rb
new file mode 100644
index 00000000000..2e65e3f8158
--- /dev/null
+++ b/lib/active_admin/localizers/resource_localizer.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Localizers
+ class ResourceLocalizer
+ class << self
+ def from_resource(resource_config)
+ new(resource_config.resource_name.i18n_key, resource_config.resource_label)
+ end
+
+ def translate(key, options)
+ new(options.delete(:model_name), options.delete(:model)).translate(key, options)
+ end
+ alias_method :t, :translate
+ end
+
+ def initialize(model_name, model = nil)
+ @model_name = model_name
+ @model = model || model_name.to_s.titleize
+ end
+
+ def translate(key, options = {})
+ scope = options.delete(:scope)
+ specific_key = array_to_key("resources", @model_name, scope, key)
+ defaults = [array_to_key(scope, key), key.to_s.titleize]
+ ::I18n.t specific_key, **options.reverse_merge(model: @model, default: defaults, scope: "active_admin")
+ end
+ alias_method :t, :translate
+
+ protected
+
+ def array_to_key(*arr)
+ arr.flatten.compact.join(".").to_sym
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/menu.rb b/lib/active_admin/menu.rb
index 7f1de9bfb67..9662ca88dd9 100644
--- a/lib/active_admin/menu.rb
+++ b/lib/active_admin/menu.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
# Each Namespace builds up it's own menu as the global navigation
@@ -27,6 +28,7 @@ def initialize
def [](id)
@children[normalize_id(id)]
end
+
def []=(id, child)
@children[normalize_id(id)] = child
end
@@ -46,11 +48,12 @@ def []=(id, child)
# menu.add parent: 'Dashboard', label: 'My Child Dashboard'
#
def add(options)
+ options = options.dup # Make sure parameter is not modified
item = if parent = options.delete(:parent)
- (self[parent] || add(:label => parent)).add options
- else
- _add options.merge :parent => self
- end
+ (self[parent] || add(label: parent)).add options
+ else
+ _add options.merge parent: self
+ end
yield(item) if block_given?
@@ -59,19 +62,19 @@ def add(options)
# Whether any children match the given item.
def include?(item)
- @children.values.include? item
+ @children.value?(item)
end
# Used in the UI to visually distinguish which menu item is selected.
- def current?(item)
- self == item || include?(item)
+ def current?(item, children: true)
+ self == item || (children && include?(item))
end
# Returns sorted array of menu items that should be displayed in this context.
# Sorts by priority first, then alphabetically by label if needed.
def items(context = nil)
- @children.values.select{ |i| i.display?(context) }.sort do |a,b|
- result = a.priority <=> b.priority
+ @children.values.select { |i| i.display?(context) }.sort do |a, b|
+ result = a.priority <=> b.priority
result = a.label(context) <=> b.label(context) if result == 0
result
end
@@ -91,8 +94,10 @@ def _add(options)
def normalize_id(id)
case id
- when String, Symbol
- id.to_s.downcase.gsub ' ', '_'
+ when String, Symbol, ActiveModel::Name
+ id.to_s.downcase.tr " ", "_"
+ when ActiveAdmin::Resource::Name
+ id.param_key
else
raise TypeError, "#{id.class} isn't supported as a Menu ID"
end
diff --git a/lib/active_admin/menu_collection.rb b/lib/active_admin/menu_collection.rb
index ca7ee359bd5..a6f604e672c 100644
--- a/lib/active_admin/menu_collection.rb
+++ b/lib/active_admin/menu_collection.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
DEFAULT_MENU = :default
@@ -5,9 +6,6 @@ module ActiveAdmin
# A MenuCollection stores multiple menus for any given namespace. Namespaces delegate
# the addition of menu items to this class.
class MenuCollection
-
- NoMenuError = Class.new(KeyError)
-
def initialize
@menus = {}
@build_callbacks = []
@@ -27,14 +25,14 @@ def clear!
end
def exists?(menu_name)
- @menus.keys.include? menu_name
+ @menus.key?(menu_name)
end
def fetch(menu_name)
build_menus!
@menus[menu_name] or
- raise NoMenuError, "No menu by the name of #{menu_name.inspect} in availble menus: #{@menus.keys.join(", ")}"
+ raise NoMenuError, "No menu by the name of #{menu_name.inspect} in available menus: #{@menus.keys.join(", ")}"
end
# Add callbacks that will be run when the menu is going to be built. This
@@ -89,8 +87,6 @@ def find_or_create(menu_name)
@menus[menu_name] ||= ActiveAdmin::Menu.new
end
-
-
end
end
diff --git a/lib/active_admin/menu_item.rb b/lib/active_admin/menu_item.rb
index bb67cc890d0..e9b3f707671 100644
--- a/lib/active_admin/menu_item.rb
+++ b/lib/active_admin/menu_item.rb
@@ -1,4 +1,5 @@
-require 'active_admin/view_helpers/method_or_proc_helper'
+# frozen_string_literal: true
+require_relative "view_helpers/method_or_proc_helper"
module ActiveAdmin
class MenuItem
@@ -48,13 +49,13 @@ class MenuItem
#
def initialize(options = {})
super() # MenuNode
- @label = options[:label]
- @dirty_id = options[:id] || options[:label]
- @url = options[:url] || '#'
- @priority = options[:priority] || 10
- @html_options = options[:html_options] || {}
- @should_display = options[:if] || proc{true}
- @parent = options[:parent]
+ @label = options[:label]
+ @dirty_id = options[:id] || options[:label]
+ @url = options[:url] || "#"
+ @priority = options[:priority] || 10
+ @html_options = options[:html_options] || {}
+ @should_display = options[:if] || proc { true }
+ @parent = options[:parent]
yield(self) if block_given? # Builder style syntax
end
@@ -64,27 +65,21 @@ def id
end
def label(context = nil)
- render_in_context context, @label
+ render_in_context(context, @label)
end
def url(context = nil)
- render_in_context context, @url
+ render_in_context(context, @url)
end
# Don't display if the :if option passed says so
# Don't display if the link isn't real, we have children, and none of the children are being displayed.
def display?(context = nil)
return false unless render_in_context(context, @should_display)
- return false if !real_url?(context) && @children.any? && !items(context).any?
+ return false if !real_url?(context) && @children.any? && !items(context).any?
true
end
- # Returns an array of the ancestory of this menu item.
- # The first item is the immediate parent of the item.
- def ancestors
- parent ? [parent, parent.ancestors].flatten : []
- end
-
private
# URL is not nil, empty, or '#'
@@ -92,6 +87,5 @@ def real_url?(context = nil)
url = url context
url.present? && url != '#'
end
-
end
end
diff --git a/lib/active_admin/namespace.rb b/lib/active_admin/namespace.rb
index 6542782f0d2..7c28032123a 100644
--- a/lib/active_admin/namespace.rb
+++ b/lib/active_admin/namespace.rb
@@ -1,4 +1,5 @@
-require 'active_admin/resource_collection'
+# frozen_string_literal: true
+require_relative "resource_collection"
module ActiveAdmin
@@ -11,7 +12,7 @@ module ActiveAdmin
#
# For example:
#
- # ActiveAdmin.register Post, :namespace => :admin
+ # ActiveAdmin.register Post, namespace: :admin
#
# Will register the Post model into the "admin" namespace. This will namespace the
# urls for the resource to "/admin/posts" and will set the controller to
@@ -19,25 +20,47 @@ module ActiveAdmin
#
# You can also register to the "root" namespace, which is to say no namespace at all.
#
- # ActiveAdmin.register Post, :namespace => false
+ # ActiveAdmin.register Post, namespace: false
#
# This will register the resource to an instantiated namespace called :root. The
# resource will be accessible from "/posts" and the controller will be PostsController.
#
class Namespace
- RegisterEvent = 'active_admin.namespace.register'.freeze
+ class << self
+ def setting(name, default)
+ ActiveAdmin.deprecator.warn "This method does not do anything and will be removed."
+ end
+ end
+
+ RegisterEvent = "active_admin.namespace.register".freeze
- attr_reader :application, :resources, :name, :menus
+ attr_reader :application, :resources, :menus
def initialize(application, name)
@application = application
- @name = name.to_s.underscore.to_sym
+ @name = name.to_s.underscore
@resources = ResourceCollection.new
register_module unless root?
build_menu_collection
end
- # Register a resource into this namespace. The preffered method to access this is to
+ def name
+ @name.to_sym
+ end
+
+ def settings
+ @settings ||= SettingsNode.build(application.namespace_settings)
+ end
+
+ def respond_to_missing?(method, include_private = false)
+ settings.respond_to?(method) || super
+ end
+
+ def method_missing(method, *args)
+ settings.respond_to?(method) ? settings.send(method, *args) : super
+ end
+
+ # Register a resource into this namespace. The preferred method to access this is to
# use the global registration ActiveAdmin.register which delegates to the proper
# namespace instance.
def register(resource_class, options = {}, &block)
@@ -45,11 +68,11 @@ def register(resource_class, options = {}, &block)
# Register the resource
register_resource_controller(config)
- parse_registration_block(config, &block) if block_given?
+ parse_registration_block(config, &block) if block
reset_menu!
# Dispatch a registration event
- ActiveAdmin::Event.dispatch ActiveAdmin::Resource::RegisterEvent, config
+ ActiveSupport::Notifications.instrument ActiveAdmin::Resource::RegisterEvent, { active_admin_resource: config }
# Return the config
config
@@ -60,7 +83,7 @@ def register_page(name, options = {}, &block)
# Register the resource
register_page_controller(config)
- parse_page_registration_block(config, &block) if block_given?
+ parse_page_registration_block(config, &block) if block
reset_menu!
config
@@ -78,8 +101,11 @@ def root?
# Namespace.new(:root).module_name # => nil
#
def module_name
- return nil if root?
- @module_name ||= name.to_s.camelize
+ root? ? nil : @name.camelize
+ end
+
+ def route_prefix
+ root? ? nil : @name
end
# Unload all the registered resources for this namespace
@@ -93,12 +119,6 @@ def resource_for(klass)
resources[klass]
end
- # Override from ActiveAdmin::Settings to inherit default attributes
- # from the application
- def read_default_setting(name)
- application.send(name)
- end
-
def fetch_menu(name)
@menus.fetch(name)
end
@@ -110,65 +130,30 @@ def reset_menu!
# Add a callback to be ran when we build the menu
#
# @param [Symbol] name The name of the menu. Default: :default
- # @param [Proc] block The block to be ran when the menu is built
+ # @yield [ActiveAdmin::Menu] The block to be ran when the menu is built
#
- # @returns [void]
- def build_menu(name = DEFAULT_MENU, &block)
+ # @return [void]
+ def build_menu(name = DEFAULT_MENU)
@menus.before_build do |menus|
menus.menu name do |menu|
- block.call(menu)
+ yield menu
end
end
end
- # Add the default logout button to the menu, using the ActiveAdmin configuration settings
- #
- # @param [ActiveAdmin::MenuItem] menu The menu to add the logout link to
- # @param [Fixnum] priority Override the default priority of 100 to position the logout button where you want
- # @param [Hash] html_options An options hash to pass along to link_to
- #
- # @returns [void]
- def add_logout_button_to_menu(menu, priority=100, html_options={})
- if logout_link_path
- logout_method = logout_link_method || :get
- menu.add :id => 'logout',
- :priority => priority,
- :label => proc{ I18n.t('active_admin.logout') },
- :html_options => html_options.reverse_merge(:method => logout_method),
- :url => proc{ render_or_call_method_or_proc_on self, active_admin_namespace.logout_link_path },
- :if => proc{ current_active_admin_user? }
- end
- end
-
protected
def build_menu_collection
@menus = MenuCollection.new
- @menus.on_build do |menus|
- build_default_utility_nav
-
+ @menus.on_build do
resources.each do |resource|
resource.add_to_menu(@menus)
end
end
end
- # Builds the default utility navigation in top right header with current user & logout button
- def build_default_utility_nav
- return if @menus.exists? :utility_navigation
- @menus.menu :utility_navigation do |menu|
- menu.add :label => proc{ display_name current_active_admin_user },
- :url => '#',
- :id => 'current_user',
- :if => proc{ current_active_admin_user? }
-
- add_logout_button_to_menu menu
- end
- end
-
- # Either returns an existing Resource instance or builds a new
- # one for the resource and options
+ # Either returns an existing Resource instance or builds a new one.
def find_or_build_resource(resource_class, options)
resources.add Resource.new(self, resource_class, options)
end
@@ -177,6 +162,7 @@ def build_page(name, options)
resources.add Page.new(self, name, options)
end
+ # TODO: replace `eval` with `Class.new`
def register_page_controller(config)
eval "class ::#{config.controller_name} < ActiveAdmin::PageController; end"
config.controller.active_admin_config = config
@@ -184,10 +170,9 @@ def register_page_controller(config)
def unload_resources!
resources.each do |resource|
- parent = (module_name || 'Object').constantize
- const_name = resource.controller_name.split('::').last
- # Remove the const if its been defined
- parent.send(:remove_const, const_name) if parent.const_defined?(const_name)
+ parent = (module_name || "Object").constantize
+ name = resource.controller_name.split("::").last
+ parent.send(:remove_const, name) if parent.const_defined?(name, false)
# Remove circular references
resource.controller.active_admin_config = nil
@@ -200,9 +185,12 @@ def unload_resources!
# Creates a ruby module to namespace all the classes in if required
def register_module
- eval "module ::#{module_name}; end"
+ unless Object.const_defined? module_name
+ Object.const_set module_name, Module.new
+ end
end
+ # TODO: replace `eval` with `Class.new`
def register_resource_controller(config)
eval "class ::#{config.controller_name} < ActiveAdmin::ResourceController; end"
config.controller.active_admin_config = config
@@ -217,5 +205,21 @@ def parse_page_registration_block(config, &block)
PageDSL.new(config).run_registration_block(&block)
end
+ class Store
+ include Enumerable
+ delegate :[], :[]=, :empty?, to: :@namespaces
+
+ def initialize
+ @namespaces = {}
+ end
+
+ def each(&block)
+ @namespaces.values.each(&block)
+ end
+
+ def names
+ @namespaces.keys
+ end
+ end
end
end
diff --git a/lib/active_admin/namespace_settings.rb b/lib/active_admin/namespace_settings.rb
new file mode 100644
index 00000000000..0c817be8c0c
--- /dev/null
+++ b/lib/active_admin/namespace_settings.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+require_relative "dynamic_settings_node"
+
+module ActiveAdmin
+ class NamespaceSettings < DynamicSettingsNode
+ # The default number of resources to display on index pages
+ register :default_per_page, 30
+
+ # The max number of resources to display on index pages and batch exports
+ register :max_per_page, 10_000
+
+ # The title which gets displayed in the main layout
+ register :site_title, "", :string_symbol_or_proc
+
+ # The method to call in controllers to get the current user
+ register :current_user_method, false
+
+ # The method to call in the controllers to ensure that there
+ # is a currently authenticated admin user
+ register :authentication_method, false
+
+ # The path to log user's out with. If set to a symbol, we assume
+ # that it's a method to call which returns the path
+ register :logout_link_path, :destroy_admin_user_session_path
+
+ # Whether the batch actions are enabled or not
+ register :batch_actions, false
+
+ # Whether filters are enabled
+ register :filters, true
+
+ # Default `filters` list for string filters in the namespace.
+ # When `nil`, the global `ActiveAdmin::Inputs::Filters::StringInput.filters` list is used.
+ register :string_input_filters, nil
+
+ # The namespace root
+ register :root_to, "dashboard#index"
+
+ # Options that are passed to root_to
+ register :root_to_options, {}
+
+ # Options passed to the routes, i.e. { path: '/custom' }
+ register :route_options, {}
+
+ # Display breadcrumbs
+ register :breadcrumb, true
+
+ # Display create another checkbox on a new page
+ # @return [Boolean] (true)
+ register :create_another, false
+
+ # Default CSV options
+ register :csv_options, { col_sep: ",", byte_order_mark: "\xEF\xBB\xBF" }
+
+ # Default Download Links options
+ register :download_links, true
+
+ # The authorization adapter to use
+ register :authorization_adapter, ActiveAdmin::AuthorizationAdapter
+
+ # A proc to be used when a user is not authorized to view the current resource
+ register :on_unauthorized_access, :rescue_active_admin_access_denied
+
+ # Whether to display 'Current Filters' on search screen
+ register :current_filters, true
+
+ # class to handle ordering
+ register :order_clause, ActiveAdmin::OrderClause
+
+ # default show_count for scopes
+ register :scopes_show_count, true
+
+ # Request parameters that are permitted by default
+ register :permitted_params, [
+ :utf8, :_method, :authenticity_token, :commit, :id
+ ]
+
+ # Set flash message keys that shouldn't show in ActiveAdmin.
+ # By default, we remove the `timedout` key from Devise.
+ register :flash_keys_to_except, ["timedout"]
+
+ # Include association filters by default
+ register :include_default_association_filters, true
+
+ register :maximum_association_filter_arity, :unlimited
+
+ register :filter_columns_for_large_association, [
+ :display_name,
+ :full_name,
+ :name,
+ :username,
+ :login,
+ :title,
+ :email,
+ ]
+ register :filter_method_for_large_association, "_start"
+ end
+end
diff --git a/lib/active_admin/order_clause.rb b/lib/active_admin/order_clause.rb
new file mode 100644
index 00000000000..75c803f4df4
--- /dev/null
+++ b/lib/active_admin/order_clause.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ class OrderClause
+ attr_reader :field, :order, :active_admin_config
+
+ def initialize(active_admin_config, clause)
+ clause =~ /^([\w\.]+)(->'\w+')?_(desc|asc)$/
+ @column = $1
+ @op = $2
+ @order = $3
+ @active_admin_config = active_admin_config
+ @field = [@column, @op].compact.join
+ end
+
+ def valid?
+ @field.present? && @order.present?
+ end
+
+ def apply(chain)
+ chain.reorder(Arel.sql sql)
+ end
+
+ def to_sql
+ [table_column, @op, " ", @order].compact.join
+ end
+
+ def table
+ active_admin_config.resource_column_names.include?(@column) ? active_admin_config.resource_table_name : nil
+ end
+
+ def table_column
+ if (@column.include?('.'))
+ @column
+ else
+ [table, active_admin_config.resource_quoted_column_name(@column)].compact.join(".")
+ end
+ end
+
+ def sql
+ custom_sql || to_sql
+ end
+
+ protected
+
+ def custom_sql
+ if active_admin_config.ordering[@column].present?
+ active_admin_config.ordering[@column].call(self)
+ end
+ end
+
+ end
+end
diff --git a/lib/active_admin/orm/active_record.rb b/lib/active_admin/orm/active_record.rb
new file mode 100644
index 00000000000..7fa22c66cc7
--- /dev/null
+++ b/lib/active_admin/orm/active_record.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+# ActiveRecord-specific plugins should be required here
+
+ActiveAdmin::DatabaseHitDuringLoad.database_error_classes << ActiveRecord::StatementInvalid
+
+require_relative "active_record/comments"
diff --git a/lib/active_admin/orm/active_record/comments.rb b/lib/active_admin/orm/active_record/comments.rb
new file mode 100644
index 00000000000..f2db90c8b92
--- /dev/null
+++ b/lib/active_admin/orm/active_record/comments.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+require_relative "comments/views"
+require_relative "comments/namespace_helper"
+require_relative "comments/resource_helper"
+
+# Add the comments configuration
+ActiveAdmin::Application.inheritable_setting :comments, true
+ActiveAdmin::Application.inheritable_setting :comments_registration_name, "Comment"
+ActiveAdmin::Application.inheritable_setting :comments_order, "created_at ASC"
+ActiveAdmin::Application.inheritable_setting :comments_menu, {}
+
+# Insert helper modules
+ActiveAdmin::Namespace.send :include, ActiveAdmin::Comments::NamespaceHelper
+ActiveAdmin::Resource.send :include, ActiveAdmin::Comments::ResourceHelper
+
+# Load the model as soon as it's referenced. By that point, Rails & Kaminari will be ready
+ActiveAdmin.autoload :Comment, "active_admin/orm/active_record/comments/comment"
+
+# Hint i18n-tasks about model and attribute translations used by default install
+# i18n-tasks-use t('activerecord.models.comment')
+# i18n-tasks-use t('activerecord.models.active_admin/comment')
+# i18n-tasks-use t('activerecord.attributes.active_admin/comment.author_type')
+# i18n-tasks-use t('activerecord.attributes.active_admin/comment.body')
+# i18n-tasks-use t('activerecord.attributes.active_admin/comment.created_at')
+# i18n-tasks-use t('activerecord.attributes.active_admin/comment.namespace')
+# i18n-tasks-use t('activerecord.attributes.active_admin/comment.resource_type')
+# i18n-tasks-use t('activerecord.attributes.active_admin/comment.updated_at')
+# i18n-tasks-use t('active_admin.scopes.all')
+
+# Walk through all the loaded namespaces after they're loaded
+ActiveAdmin.after_load do |app|
+ app.namespaces.each do |namespace|
+ namespace.register ActiveAdmin::Comment, as: namespace.comments_registration_name do
+ actions :index, :show, :create, :destroy
+
+ menu namespace.comments ? namespace.comments_menu : false
+
+ config.comments = false # Don't allow comments on comments
+ config.batch_actions = false # The default destroy batch action isn't showing up anyway...
+
+ scope :all, show_count: false
+ # Register a scope for every namespace that exists.
+ # The current namespace will be the default scope.
+ app.namespaces.map(&:name).each do |name|
+ scope name, default: namespace.name == name do |scope|
+ scope.where namespace: name.to_s
+ end
+ end
+
+ # Store the author and namespace
+ before_save do |comment|
+ comment.namespace = active_admin_config.namespace.name
+ comment.author = current_active_admin_user
+ end
+
+ controller do
+ # Prevent N+1 queries
+ def scoped_collection
+ super.includes(:author, :resource)
+ end
+
+ # Redirect to the resource show page after comment creation
+ def create
+ create! do |success, failure|
+ success.html do
+ redirect_back fallback_location: active_admin_root
+ end
+ failure.html do
+ flash[:error] = I18n.t "active_admin.comments.errors.empty_text"
+ redirect_back fallback_location: active_admin_root
+ end
+ end
+ end
+
+ def destroy
+ destroy! do |success, failure|
+ success.html do
+ if params[:return_to] == "back"
+ redirect_back fallback_location: active_admin_root
+ else
+ redirect_to action: :index
+ end
+ end
+ failure.html do
+ redirect_back fallback_location: active_admin_root
+ end
+ end
+ end
+ end
+
+ permit_params :body, :namespace, :resource_id, :resource_type
+
+ index do
+ column I18n.t("active_admin.comments.resource_type"), :resource_type
+ column I18n.t("active_admin.comments.resource"), :resource, class: "min-w-28"
+ column I18n.t("active_admin.comments.author_type"), :author_type
+ column I18n.t("active_admin.comments.author"), :author
+ column I18n.t("active_admin.comments.body"), :body, class: "min-w-52" do |comment|
+ truncate(comment.body, length: 50, separator: " ")
+ end
+ column I18n.t("active_admin.comments.created_at"), :created_at, class: "min-w-52"
+ actions
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/orm/active_record/comments/comment.rb b/lib/active_admin/orm/active_record/comments/comment.rb
new file mode 100644
index 00000000000..e1b2643eb2a
--- /dev/null
+++ b/lib/active_admin/orm/active_record/comments/comment.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ class Comment < ActiveRecord::Base
+
+ self.table_name = "#{table_name_prefix}active_admin_comments#{table_name_suffix}"
+
+ belongs_to :resource, polymorphic: true, optional: true
+ belongs_to :author, polymorphic: true
+
+ validates_presence_of :body, :namespace, :resource
+
+ before_create :set_resource_type
+
+ # @return [String] The name of the record to use for the polymorphic relationship
+ def self.resource_type(resource)
+ ResourceController::Decorators.undecorate(resource).class.base_class.name.to_s
+ end
+
+ def self.find_for_resource_in_namespace(resource, namespace)
+ where(
+ resource_type: resource_type(resource),
+ resource_id: resource.id,
+ namespace: namespace.to_s
+ ).order(ActiveAdmin.application.namespaces[namespace.to_sym].comments_order)
+ end
+
+ def set_resource_type
+ self.resource_type = self.class.resource_type(resource)
+ end
+
+ def self.ransackable_attributes(auth_object = nil)
+ authorizable_ransackable_attributes
+ end
+
+ def self.ransackable_associations(auth_object = nil)
+ authorizable_ransackable_associations
+ end
+
+ end
+end
diff --git a/lib/active_admin/comments/namespace_helper.rb b/lib/active_admin/orm/active_record/comments/namespace_helper.rb
similarity index 51%
rename from lib/active_admin/comments/namespace_helper.rb
rename to lib/active_admin/orm/active_record/comments/namespace_helper.rb
index 0639ff80563..14157f49c80 100644
--- a/lib/active_admin/comments/namespace_helper.rb
+++ b/lib/active_admin/orm/active_record/comments/namespace_helper.rb
@@ -1,11 +1,12 @@
+# frozen_string_literal: true
module ActiveAdmin
module Comments
module NamespaceHelper
- # Returns true of the namespace allows comments
+ # Returns true if the namespace allows comments
def comments?
- allow_comments == true
+ comments == true
end
end
diff --git a/lib/active_admin/comments/resource_helper.rb b/lib/active_admin/orm/active_record/comments/resource_helper.rb
similarity index 67%
rename from lib/active_admin/comments/resource_helper.rb
rename to lib/active_admin/orm/active_record/comments/resource_helper.rb
index 14922a9f22c..b7e6f746731 100644
--- a/lib/active_admin/comments/resource_helper.rb
+++ b/lib/active_admin/orm/active_record/comments/resource_helper.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
module Comments
@@ -9,7 +10,7 @@ module ResourceHelper
end
def comments?
- namespace.comments? && comments != false
+ (namespace.comments? && comments != false) || comments == true
end
end
diff --git a/lib/active_admin/orm/active_record/comments/views.rb b/lib/active_admin/orm/active_record/comments/views.rb
new file mode 100644
index 00000000000..f844704792b
--- /dev/null
+++ b/lib/active_admin/orm/active_record/comments/views.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+require_relative "../../../views"
+require_relative "views/active_admin_comments"
diff --git a/lib/active_admin/orm/active_record/comments/views/active_admin_comments.rb b/lib/active_admin/orm/active_record/comments/views/active_admin_comments.rb
new file mode 100644
index 00000000000..fdaa962f55a
--- /dev/null
+++ b/lib/active_admin/orm/active_record/comments/views/active_admin_comments.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+require_relative "../../../../views"
+
+module ActiveAdmin
+ module Comments
+ module Views
+ class Comments < Arbre::Element
+ builder_method :active_admin_comments_for
+
+ def build(resource)
+ if authorized?(ActiveAdmin::Auth::READ, ActiveAdmin::Comment)
+ comments = active_admin_authorization.scope_collection(ActiveAdmin::Comment.find_for_resource_in_namespace(resource, active_admin_namespace.name).includes(:author).page(params[:page]))
+ render("active_admin/shared/resource_comments", resource: resource, comments: comments, comment_form_url: comment_form_url)
+ end
+ end
+
+ protected
+
+ def comment_form_url
+ parts = []
+ parts << active_admin_namespace.name unless active_admin_namespace.root?
+ parts << active_admin_namespace.comments_registration_name.underscore.pluralize
+ parts << "path"
+ send parts.join "_"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/page.rb b/lib/active_admin/page.rb
index 0333fbdb5c1..3912dc4e15a 100644
--- a/lib/active_admin/page.rb
+++ b/lib/active_admin/page.rb
@@ -1,7 +1,8 @@
+# frozen_string_literal: true
module ActiveAdmin
# Page is the primary data storage for page configuration in Active Admin
#
- # When you register a page (ActiveAdmin.page "Status") you are actually creating
+ # When you register a page (ActiveAdmin.register_page "Status") you are actually creating
# a new Page instance within the given Namespace.
#
# The instance of the current page is available in PageController and views
@@ -49,19 +50,23 @@ def resource_name
end
def underscored_resource_name
- resource_name.parameterize.underscore
+ resource_name.to_s.parameterize.underscore
end
def camelized_resource_name
underscored_resource_name.camelize
end
+ def namespace_name
+ namespace.name.to_s
+ end
+
def default_menu_options
- super.merge(:id => resource_name)
+ super.merge(id: resource_name)
end
def controller_name
- [namespace.module_name, camelized_resource_name + "Controller"].compact.join('::')
+ [namespace.module_name, camelized_resource_name + "Controller"].compact.join("::")
end
# Override from `ActiveAdmin::Resource::Controllers`
@@ -69,10 +74,6 @@ def route_uncountable?
false
end
- def belongs_to?
- false
- end
-
def add_default_action_items
end
@@ -84,5 +85,28 @@ def clear_page_actions!
@page_actions = []
end
+ def belongs_to(target, options = {})
+ @belongs_to = Resource::BelongsTo.new(self, target, options)
+ self.navigation_menu_name = target unless @belongs_to.optional?
+ controller.send :belongs_to, target, options.dup
+ end
+
+ def belongs_to_config
+ @belongs_to
+ end
+
+ # Do we belong to another resource?
+ def belongs_to?
+ !!belongs_to_config
+ end
+
+ def breadcrumb
+ instance_variable_defined?(:@breadcrumb) ? @breadcrumb : namespace.breadcrumb
+ end
+
+ def order_clause
+ @order_clause || namespace.order_clause
+ end
+
end
end
diff --git a/lib/active_admin/page_dsl.rb b/lib/active_admin/page_dsl.rb
index e1ab7f872b9..94607aa81b4 100644
--- a/lib/active_admin/page_dsl.rb
+++ b/lib/active_admin/page_dsl.rb
@@ -1,5 +1,6 @@
+# frozen_string_literal: true
module ActiveAdmin
- # This is the class where all the register_page blocks are instance eval'd
+ # This is the class where all the register_page blocks are evaluated.
class PageDSL < DSL
# Page content.
@@ -21,8 +22,12 @@ def content(options = {}, &block)
def page_action(name, options = {}, &block)
config.page_actions << ControllerAction.new(name, options)
controller do
- define_method(name, &block || Proc.new{})
+ define_method(name, &block || Proc.new {})
end
end
+
+ def belongs_to(target, options = {})
+ config.belongs_to(target, options)
+ end
end
end
diff --git a/lib/active_admin/page_presenter.rb b/lib/active_admin/page_presenter.rb
index c1d61b4f7fe..f48441a55f3 100644
--- a/lib/active_admin/page_presenter.rb
+++ b/lib/active_admin/page_presenter.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
# A simple object that gets used to present different aspects of views
@@ -7,7 +8,7 @@ module ActiveAdmin
#
# Usage:
#
- # presenter = PagePresenter.new :as => :table do
+ # presenter = PagePresenter.new as: :table do
# # some awesome stuff
# end
#
@@ -18,10 +19,11 @@ class PagePresenter
attr_reader :block, :options
- delegate :has_key?, :to => :options
+ delegate :has_key?, :fetch, to: :options
def initialize(options = {}, &block)
- @options, @block = options, block
+ @options = options
+ @block = block
end
def [](key)
diff --git a/lib/active_admin/pundit_adapter.rb b/lib/active_admin/pundit_adapter.rb
new file mode 100644
index 00000000000..c81abe11ce4
--- /dev/null
+++ b/lib/active_admin/pundit_adapter.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+ActiveAdmin::Dependency.pundit!
+
+require "pundit"
+
+# Add a setting to the application to configure the pundit default policy
+ActiveAdmin::Application.inheritable_setting :pundit_default_policy, nil
+ActiveAdmin::Application.inheritable_setting :pundit_policy_namespace, nil
+
+module ActiveAdmin
+
+ class PunditAdapter < AuthorizationAdapter
+
+ def authorized?(action, subject = nil)
+ policy = retrieve_policy(subject)
+ action = format_action(action, subject)
+
+ policy.respond_to?(action) && policy.public_send(action)
+ end
+
+ def scope_collection(collection, action = Auth::READ)
+ # scoping is applicable only to read/index action
+ # which means there is no way how to scope other actions
+ Pundit.policy_scope!(user, namespace(collection))
+ rescue Pundit::NotDefinedError => e
+ if default_policy_class&.const_defined?(:Scope)
+ default_policy_class::Scope.new(user, collection).resolve
+ else
+ raise e
+ end
+ end
+
+ def retrieve_policy(subject)
+ target = policy_target(subject)
+ if (policy = policy(namespace(target)) || compat_policy(subject))
+ policy
+ elsif default_policy_class
+ default_policy(subject)
+ else
+ raise Pundit::NotDefinedError, "unable to find a compatible policy for `#{target}`"
+ end
+ end
+
+ def format_action(action, subject)
+ # https://github.com/varvet/pundit/blob/main/lib/generators/pundit/install/templates/application_policy.rb
+ case action
+ when Auth::READ then subject.is_a?(Class) ? :index? : :show?
+ when Auth::DESTROY then subject.is_a?(Class) ? :destroy_all? : :destroy?
+ else "#{action}?"
+ end
+ end
+
+ private
+
+ def policy_target(subject)
+ case subject
+ when nil then resource
+ when Class then subject.new
+ else subject
+ end
+ end
+
+ # This method is needed to fallback to our previous policy searching logic.
+ # I.e.: when class name contains `default_policy_namespace` (eg: ShopAdmin)
+ # we should try to search it without namespace. This is because that's
+ # the only thing that worked in this case before we fixed our buggy namespace
+ # detection, so people are probably relying on it.
+ # This fallback might be removed in future versions of ActiveAdmin, so
+ # pundit_adapter search will work consistently with provided namespaces
+ def compat_policy(subject)
+ return unless default_policy_namespace
+
+ target = policy_target(subject)
+
+ return unless target.class.to_s.include?(default_policy_module) &&
+ (policy = policy(target))
+
+ policy_name = policy.class.to_s
+
+ ActiveAdmin.deprecator.warn "You have `pundit_policy_namespace` configured as `#{default_policy_namespace}`, " \
+ "but ActiveAdmin was unable to find policy #{default_policy_module}::#{policy_name}. " \
+ "#{policy_name} will be used instead. " \
+ "This behavior will be removed in future versions of ActiveAdmin. " \
+ "To fix this warning, move your #{policy_name} policy to the #{default_policy_module} namespace"
+
+ policy
+ end
+
+ def namespace(object)
+ if default_policy_namespace && !object.class.to_s.start_with?("#{default_policy_module}::")
+ [default_policy_namespace.to_sym, object]
+ else
+ object
+ end
+ end
+
+ def default_policy_class
+ ActiveAdmin.application.pundit_default_policy&.constantize
+ end
+
+ def default_policy(subject)
+ default_policy_class.new(user, subject)
+ end
+
+ def default_policy_namespace
+ ActiveAdmin.application.pundit_policy_namespace
+ end
+
+ def default_policy_module
+ default_policy_namespace.to_s.camelize
+ end
+
+ def policy(target)
+ policies[target] ||= Pundit.policy(user, target)
+ end
+
+ def policies
+ @policies ||= {}
+ end
+
+ end
+
+end
diff --git a/lib/active_admin/reloader.rb b/lib/active_admin/reloader.rb
deleted file mode 100644
index 0830357a050..00000000000
--- a/lib/active_admin/reloader.rb
+++ /dev/null
@@ -1,130 +0,0 @@
-module ActiveAdmin
-
- module Reloader
-
- # Builds the proper Reloader implementation class given the
- # current version of Rails.
- #
- # @param [Rails::Application] rails_app The current rails application
- # @param [ActiveAdmin::Application] active_admin_app The current Active Admin app
- # @param [String] rails_version The version of Rails we're using.
- #
- # @returns A concrete subclass of AbstractReloader
- def self.build(rails_app, active_admin_app, rails_version)
- reloader_class = rails_version.to_f >= 3.2 ? Rails32Reloader : RailsLessThan31Reloader
- reloader_class.new(rails_app, active_admin_app, rails_version)
- end
-
- class AbstractReloader
-
- attr_reader :active_admin_app, :rails_app, :rails_version
-
- def initialize(rails_app, active_admin_app, rails_version)
- @rails_app = rails_app
- @active_admin_app = active_admin_app
- @rails_version = rails_version.to_s
- end
-
- def attach!
- raise "Please implement #{self.class}#attach!"
- end
-
- def reload!
- active_admin_app.unload!
- rails_app.reload_routes!
- end
-
- def major_rails_version
- @rails_version[0..2]
- end
-
- end
-
- # Reloads the application when using Rails 3.2
- #
- # 3.2 introduced a to_prepare block that only gets called when
- # files have actually changed. ActiveAdmin had built this functionality
- # in to speed up applications. So in Rails >= 3.2, we can now piggy
- # back off the existing reloader. This simplifies our duties... which is
- # nice.
- class Rails32Reloader < AbstractReloader
-
- # Attach to Rails and perform the reload on each request.
- def attach!
- active_admin_app.load_paths.each do |path|
- rails_app.config.watchable_dirs[path] = [:rb]
- end
-
- reloader = self
-
- ActionDispatch::Reloader.to_prepare do
- reloader.reload!
- end
- end
-
- end
-
- # Deals with reloading Active Admin on each request in
- # development and once in production in Rails < 3.2.
- class RailsLessThan31Reloader < AbstractReloader
-
- class FileUpdateChecker < ::ActiveSupport::FileUpdateChecker
- def paths
- # hack to support both Rails 3.1 and 3.2
- @files || @paths
- end
-
- # Over-ride the default #updated_at to support the deletion of files
- def updated_at
- paths.map { |path| File.mtime(path) rescue Time.now }.max
- end
-
- def execute_if_updated
- super
- end
- end
-
- attr_reader :file_update_checker
-
- def initialize(rails_app, active_admin_app, rails_version)
- super
- @file_update_checker = FileUpdateChecker.new(watched_paths) do
- reload!
- end
- end
-
- def reload!
- super
- file_update_checker.paths.clear
- watched_paths.each{|path| file_update_checker.paths << path }
- end
-
-
- # Attach to Rails and perform the reload on each request.
- def attach!
- # Bring the checker into local scope for the ruby block
- checker = file_update_checker
-
- reloader_class.to_prepare do
- checker.execute_if_updated
- end
- end
-
- def watched_paths
- paths = active_admin_app.load_paths
- active_admin_app.load_paths.each{|path| paths += Dir[File.join(path, "**", "*.rb")]}
- paths
- end
-
- def reloader_class
- if major_rails_version == "3.1"
- ActionDispatch::Reloader
- else
- ActionDispatch::Callbacks
- end
- end
-
- end
-
- end
-end
diff --git a/lib/active_admin/resource.rb b/lib/active_admin/resource.rb
index 28ad5b22a39..96e9e31c075 100644
--- a/lib/active_admin/resource.rb
+++ b/lib/active_admin/resource.rb
@@ -1,14 +1,20 @@
-require 'active_admin/resource/action_items'
-require 'active_admin/resource/controllers'
-require 'active_admin/resource/menu'
-require 'active_admin/resource/page_presenters'
-require 'active_admin/resource/pagination'
-require 'active_admin/resource/routes'
-require 'active_admin/resource/naming'
-require 'active_admin/resource/scopes'
-require 'active_admin/resource/scope_to'
-require 'active_admin/resource/sidebars'
-require 'active_admin/resource/belongs_to'
+# frozen_string_literal: true
+require_relative "view_helpers/method_or_proc_helper"
+require_relative "resource/action_items"
+require_relative "resource/attributes"
+require_relative "resource/controllers"
+require_relative "resource/menu"
+require_relative "resource/page_presenters"
+require_relative "resource/pagination"
+require_relative "resource/routes"
+require_relative "resource/naming"
+require_relative "resource/scopes"
+require_relative "resource/includes"
+require_relative "resource/scope_to"
+require_relative "resource/sidebars"
+require_relative "resource/belongs_to"
+require_relative "resource/ordering"
+require_relative "resource/model"
module ActiveAdmin
@@ -23,7 +29,7 @@ module ActiveAdmin
class Resource
# Event dispatched when a new resource is registered
- RegisterEvent = 'active_admin.resource.register'.freeze
+ RegisterEvent = "active_admin.resource.register".freeze
# The namespace this config belongs to
attr_reader :namespace
@@ -38,13 +44,22 @@ class Resource
attr_reader :collection_actions
# The default sort order to use in the controller
- attr_accessor :sort_order
+ attr_writer :sort_order
+ def sort_order
+ @sort_order ||= (resource_class.respond_to?(:primary_key) ? resource_class.primary_key.to_s : "id") + "_desc"
+ end
# Set the configuration for the CSV
attr_writer :csv_builder
# Set breadcrumb builder
- attr_accessor :breadcrumb
+ attr_writer :breadcrumb
+
+ #Set order clause
+ attr_writer :order_clause
+ # Display create another checkbox on a new page
+ # @return [Boolean]
+ attr_writer :create_another
# Store a reference to the DSL so that we can dereference it during garbage collection.
attr_accessor :dsl
@@ -53,13 +68,19 @@ class Resource
# nil to not decorate.
attr_accessor :decorator_class_name
+ # Default `filters` list for string filters on this resource. Takes
+ # priority over the namespace-level `string_input_filters` setting when
+ # set; when nil, falls back to it, and then to the global default.
+ attr_accessor :string_input_filters
+
module Base
def initialize(namespace, resource_class, options = {})
@namespace = namespace
- @resource_class_name = "::#{resource_class.name}"
- @options = default_options.merge(options)
- @sort_order = @options[:sort_order]
- @member_actions, @collection_actions = [], []
+ @resource_class_name = resource_class.respond_to?(:name) ? "::#{resource_class.name}" : resource_class.to_s
+ @options = options
+ @sort_order = options[:sort_order]
+ @member_actions = []
+ @collection_actions = []
end
end
@@ -74,19 +95,25 @@ def initialize(namespace, resource_class, options = {})
include PagePresenters
include Pagination
include Scopes
+ include Includes
include ScopeTo
include Sidebars
- include Menu
include Routes
+ include Ordering
+ include Attributes
# The class this resource wraps. If you register the Post model, Resource#resource_class
# will point to the Post class
def resource_class
- ActiveSupport::Dependencies.constantize(resource_class_name)
+ resource_class_name.constantize
end
def decorator_class
- ActiveSupport::Dependencies.constantize(decorator_class_name) if decorator_class_name
+ decorator_class_name&.constantize
+ end
+
+ def resource_name_extension
+ @resource_name_extension ||= define_resource_name_extension(self)
end
def resource_table_name
@@ -98,7 +125,9 @@ def resource_column_names
end
def resource_quoted_column_name(column)
- resource_class.connection.quote_column_name(column)
+ resource_class.with_connection do |connection|
+ connection.quote_column_name(column)
+ end
end
# Clears all the member actions this resource knows about
@@ -117,7 +146,8 @@ def defined_actions
def belongs_to(target, options = {})
@belongs_to = Resource::BelongsTo.new(self, target, options)
- self.navigation_menu_name = target unless @belongs_to.optional?
+ self.menu_item_options = false if @belongs_to.required?
+ options[:class_name] ||= @belongs_to.resource.resource_class_name if @belongs_to.resource
controller.send :belongs_to, target, options.dup
end
@@ -125,6 +155,12 @@ def belongs_to_config
@belongs_to
end
+ def belongs_to_param
+ if belongs_to? && belongs_to_config.required?
+ belongs_to_config.to_param
+ end
+ end
+
# Do we belong to another resource?
def belongs_to?
!!belongs_to_config
@@ -135,24 +171,59 @@ def csv_builder
@csv_builder || default_csv_builder
end
- # @deprecated
- def resource
- resource_class
+ def breadcrumb
+ instance_variable_defined?(:@breadcrumb) ? @breadcrumb : namespace.breadcrumb
+ end
+
+ def order_clause
+ @order_clause || namespace.order_clause
+ end
+
+ def create_another
+ instance_variable_defined?(:@create_another) ? @create_another : namespace.create_another
+ end
+
+ def find_resource(id)
+ resource = resource_class.public_send(*method_for_find(id))
+ (decorator_class && resource) ? decorator_class.new(resource) : resource
+ end
+
+ def resource_columns
+ resource_attributes.values
+ end
+
+ def resource_attributes
+ @resource_attributes ||= default_attributes
+ end
+
+ def association_columns
+ @association_columns ||= resource_attributes.select { |key, value| key != value }.values
+ end
+
+ def content_columns
+ @content_columns ||= resource_attributes.select { |key, value| key == value }.values
end
- ActiveAdmin::Deprecation.deprecate self, :resource,
- "ActiveAdmin::Resource#resource is deprecated. Please use #resource_class instead."
private
- def default_options
- {
- :sort_order => (resource_class.respond_to?(:primary_key) ? resource_class.primary_key.to_s : 'id') + '_desc'
- }
+ def method_for_find(id)
+ if finder = resources_configuration[:self][:finder]
+ [finder, id]
+ else
+ [:find_by, { resource_class.primary_key => id }]
+ end
end
def default_csv_builder
- @default_csv_builder ||= CSVBuilder.default_for_resource(resource_class)
+ @default_csv_builder ||= CSVBuilder.default_for_resource(self)
end
+ def define_resource_name_extension(resource)
+ Module.new do
+ define_method :model_name do
+ resource.resource_name
+ end
+ end
+ end
end # class Resource
end # module ActiveAdmin
diff --git a/lib/active_admin/resource/action_items.rb b/lib/active_admin/resource/action_items.rb
index 1e08a886ed6..0dc825392e0 100644
--- a/lib/active_admin/resource/action_items.rb
+++ b/lib/active_admin/resource/action_items.rb
@@ -1,12 +1,12 @@
-require 'active_admin/helpers/optional_display'
+# frozen_string_literal: true
+require_relative "../helpers/optional_display"
module ActiveAdmin
class Resource
module ActionItems
- # Add the default action items to a resource when it's
- # initialized
+ # Adds the default action items to a resource when it's initialized
def initialize(*args)
super
add_default_action_items
@@ -19,13 +19,19 @@ def action_items
# Add a new action item to a resource
#
+ # @param [Symbol] name
# @param [Hash] options valid keys include:
# :only: A single or array of controller actions to display
# this action item on.
# :except: A single or array of controller actions not to
# display this action item on.
- def add_action_item(options = {}, &block)
- self.action_items << ActiveAdmin::ActionItem.new(options, &block)
+ # :priority: A single integer value. To control the display order. Default is 10.
+ def add_action_item(name, options = {}, &block)
+ self.action_items << ActiveAdmin::ActionItem.new(name, options, &block)
+ end
+
+ def remove_action_item(name)
+ self.action_items.delete_if { |item| item.name == name }
end
# Returns a set of action items to display for a specific controller action
@@ -34,7 +40,7 @@ def add_action_item(options = {}, &block)
#
# @return [Array] Array of ActionItems for the controller actions
def action_items_for(action, render_context = nil)
- action_items.select{|item| item.display_on?(action, render_context) }
+ action_items.select { |item| item.display_on? action, render_context }.sort_by(&:priority)
end
# Clears all the existing action items for this resource
@@ -51,27 +57,38 @@ def action_items?
# Adds the default action items to each resource
def add_default_action_items
- # New Link on all actions except :new and :show
- add_action_item :except => [:new, :show] do
- if controller.action_methods.include?('new') && authorized?(ActiveAdmin::Auth::CREATE, active_admin_config.resource_class)
- link_to(I18n.t('active_admin.new_model', :model => active_admin_config.resource_label), new_resource_path)
- end
+ add_default_new_action_item
+ add_default_edit_action_item
+ add_default_destroy_action_item
+ end
+
+ # Adds the default New link on index
+ def add_default_new_action_item
+ add_action_item :new, only: :index, if: -> { new_action_authorized?(active_admin_config.resource_class) } do
+ localizer = ActiveAdmin::Localizers.resource(active_admin_config)
+ link_to localizer.t(:new_model), new_resource_path, class: "action-item-button"
end
+ end
- # Edit link on show
- add_action_item :only => :show do
- if controller.action_methods.include?('edit') && authorized?(ActiveAdmin::Auth::UPDATE, resource)
- link_to(I18n.t('active_admin.edit_model', :model => active_admin_config.resource_label), edit_resource_path(resource))
- end
+ # Adds the default Edit link on show
+ def add_default_edit_action_item
+ add_action_item :edit, only: :show, if: -> { edit_action_authorized?(resource) } do
+ localizer = ActiveAdmin::Localizers.resource(active_admin_config)
+ link_to localizer.t(:edit_model), edit_resource_path(resource), class: "action-item-button"
end
+ end
- # Destroy link on show
- add_action_item :only => :show do
- if controller.action_methods.include?("destroy") && authorized?(ActiveAdmin::Auth::DESTROY, resource)
- link_to(I18n.t('active_admin.delete_model', :model => active_admin_config.resource_label),
- resource_path(resource),
- :method => :delete, :data => {:confirm => I18n.t('active_admin.delete_confirmation')})
- end
+ # Adds the default Destroy link on show
+ def add_default_destroy_action_item
+ add_action_item :destroy, only: :show, if: -> { destroy_action_authorized?(resource) } do
+ localizer = ActiveAdmin::Localizers.resource(active_admin_config)
+ link_to(
+ localizer.t(:delete_model),
+ resource_path(resource),
+ class: "action-item-button",
+ method: :delete,
+ data: { confirm: localizer.t(:delete_confirmation) }
+ )
end
end
@@ -82,12 +99,18 @@ def add_default_action_items
class ActionItem
include ActiveAdmin::OptionalDisplay
- attr_accessor :block
+ attr_accessor :block, :name
- def initialize(options = {}, &block)
- @options, @block = options, block
+ def initialize(name, options = {}, &block)
+ @name = name
+ @options = options
+ @block = block
normalize_display_options!
end
+
+ def priority
+ @options[:priority] || 10
+ end
end
end
diff --git a/lib/active_admin/resource/attributes.rb b/lib/active_admin/resource/attributes.rb
new file mode 100644
index 00000000000..10986f28478
--- /dev/null
+++ b/lib/active_admin/resource/attributes.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+module ActiveAdmin
+
+ class Resource
+ module Attributes
+
+ def default_attributes
+ return {} unless resource_class.respond_to?(:columns)
+
+ resource_class.columns.each_with_object({}) do |c, attrs|
+ unless reject_col?(c)
+ name = c.name.to_sym
+ attrs[name] = (method_for_column(name) || name)
+ end
+ end
+ end
+
+ def method_for_column(c)
+ resource_class.respond_to?(:reflect_on_all_associations) && foreign_methods.has_key?(c) && foreign_methods[c].name.to_sym
+ end
+
+ def foreign_methods
+ @foreign_methods ||= resource_class.reflect_on_all_associations.
+ select { |r| r.macro == :belongs_to }.
+ reject { |r| r.chain.length > 2 && !r.options[:polymorphic] }.
+ index_by { |r| r.foreign_key.to_sym }
+ end
+
+ def reject_col?(c)
+ primary_col?(c) || sti_col?(c) || counter_cache_col?(c) || filtered_col?(c)
+ end
+
+ def primary_col?(c)
+ c.name == resource_class.primary_key
+ end
+
+ def sti_col?(c)
+ c.name == resource_class.inheritance_column
+ end
+
+ def counter_cache_col?(c)
+ # This helper is called inside a loop. Let's memoize the result.
+ @counter_cache_columns ||= begin
+ resource_class.reflect_on_all_associations(:has_many)
+ .select(&:has_cached_counter?)
+ .map(&:counter_cache_column)
+ end
+
+ @counter_cache_columns.include?(c.name)
+ end
+
+ def filtered_col?(c)
+ ActiveAdmin.application.filter_attributes.include?(c.name.to_sym)
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/resource/belongs_to.rb b/lib/active_admin/resource/belongs_to.rb
index eb1cf49fa19..b65503f6ba9 100644
--- a/lib/active_admin/resource/belongs_to.rb
+++ b/lib/active_admin/resource/belongs_to.rb
@@ -1,23 +1,36 @@
-require 'active_admin/resource'
+# frozen_string_literal: true
module ActiveAdmin
class Resource
class BelongsTo
- class TargetNotFound < StandardError; end
+ class TargetNotFound < StandardError
+ def initialize(key, namespace)
+ super "Could not find #{key} in #{namespace.name} " +
+ "with #{namespace.resources.map(&:resource_name)}"
+ end
+ end
# The resource which initiated this relationship
attr_reader :owner
+ # The name of the relation
+ attr_reader :target_name
+
def initialize(owner, target_name, options = {})
- @owner, @target_name, @options = owner, target_name, options
+ @owner = owner
+ @target_name = target_name
+ @options = options
end
# Returns the target resource class or raises an exception if it doesn't exist
def target
- key = @target_name.to_s.camelize
- namespace.resources[key] or raise TargetNotFound, "Could not find #{key} in" +
- " #{namespace.name} with #{namespace.resources.map(&:resource_name)}"
+ resource or raise TargetNotFound.new (@options[:class_name] || @target_name.to_s.camelize), namespace
+ end
+
+ def resource
+ namespace.resources[@options[:class_name]] ||
+ namespace.resources[@target_name.to_s.camelize]
end
def namespace
@@ -31,6 +44,10 @@ def optional?
def required?
!optional?
end
+
+ def to_param
+ (@options[:param] || "#{@target_name}_id").to_sym
+ end
end
end
end
diff --git a/lib/active_admin/resource/controllers.rb b/lib/active_admin/resource/controllers.rb
index da17260a4d5..c63ddd09115 100644
--- a/lib/active_admin/resource/controllers.rb
+++ b/lib/active_admin/resource/controllers.rb
@@ -1,11 +1,13 @@
+# frozen_string_literal: true
module ActiveAdmin
class Resource
module Controllers
+ delegate :resources_configuration, to: :controller
# Returns a properly formatted controller name for this
# config within its namespace
def controller_name
- [namespace.module_name, resource_name.plural.camelize + "Controller"].compact.join('::')
+ [namespace.module_name, resource_name.plural.camelize + "Controller"].compact.join("::")
end
# Returns the controller for this config
diff --git a/lib/active_admin/resource/includes.rb b/lib/active_admin/resource/includes.rb
new file mode 100644
index 00000000000..0858b491b6c
--- /dev/null
+++ b/lib/active_admin/resource/includes.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ class Resource
+ module Includes
+
+ # Return an array of includes for this resource
+ def includes
+ @includes ||= []
+ end
+
+ end
+ end
+end
diff --git a/lib/active_admin/resource/menu.rb b/lib/active_admin/resource/menu.rb
index 9bfa3a1f010..9970ff60328 100644
--- a/lib/active_admin/resource/menu.rb
+++ b/lib/active_admin/resource/menu.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
class Resource
@@ -7,11 +8,12 @@ module Menu
# To disable this menu item, call `menu(false)` from the DSL
def menu_item_options=(options)
if options == false
- @include_in_menu = false
+ @include_in_menu = false
@menu_item_options = {}
else
+ @include_in_menu = true
@navigation_menu_name = options[:menu_name]
- @menu_item_options = default_menu_options.merge options
+ @menu_item_options = default_menu_options.merge options
end
end
@@ -24,19 +26,21 @@ def default_menu_options
menu_resource_class = respond_to?(:resource_class) ? resource_class : self
resource = self
{
- :id => resource_name.plural,
- :label => proc{ resource.plural_resource_label },
- :url => proc{ resource.route_collection_path(params) },
- :if => proc{ authorized?(:read, menu_resource_class) }
+ id: resource_name.plural,
+ label: proc { resource.plural_resource_label },
+ url: proc { resource.route_collection_path(params, url_options) },
+ if: proc { authorized?(Auth::READ, menu_resource_class) }
}
end
- attr_writer :navigation_menu_name
+ def navigation_menu_name=(menu_name)
+ self.menu_item_options = { menu_name: menu_name }
+ end
def navigation_menu_name
case @navigation_menu_name ||= DEFAULT_MENU
when Proc
- controller.instance_eval(&@navigation_menu_name).to_sym
+ controller.instance_exec(&@navigation_menu_name).to_sym
else
@navigation_menu_name
end
diff --git a/lib/active_admin/resource/model.rb b/lib/active_admin/resource/model.rb
new file mode 100644
index 00000000000..aeb894c3cf5
--- /dev/null
+++ b/lib/active_admin/resource/model.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ class Model
+ def initialize(resource, record)
+ @record = record
+
+ if resource
+ @record.extend(resource.resource_name_extension)
+ end
+ end
+
+ def to_model
+ @record
+ end
+ end
+end
diff --git a/lib/active_admin/resource/naming.rb b/lib/active_admin/resource/naming.rb
index 92047cf7338..5c0c8813350 100644
--- a/lib/active_admin/resource/naming.rb
+++ b/lib/active_admin/resource/naming.rb
@@ -1,77 +1,61 @@
+# frozen_string_literal: true
module ActiveAdmin
class Resource
module Naming
-
- # Returns a name used to uniquely identify this resource
- # this should be an instance of ActiveAdmin:Resource::Name, which responds to
- # #singular, #plural, #route_key, #human etc.
def resource_name
- custom_name = @options[:as] && @options[:as].gsub(/\s/,'')
- @resource_name ||= if custom_name || !resource_class.respond_to?(:model_name)
- Resource::Name.new(resource_class, custom_name)
+ @resource_name ||= begin
+ as = @options[:as].gsub(/\s/, "") if @options[:as]
+
+ if as || !resource_class.respond_to?(:model_name)
+ Name.new resource_class, as
else
- Resource::Name.new(resource_class)
+ Name.new resource_class
end
+ end
end
# Returns the name to call this resource such as "Bank Account"
def resource_label
- resource_name.translate :count => 1, :default => resource_name.gsub('::', ' ').titleize
+ resource_name.translate count: 1,
+ default: resource_name.to_s.gsub("::", " ").titleize
end
# Returns the plural version of this resource such as "Bank Accounts"
def plural_resource_label(options = {})
- resource_name.translate ({:count => 1.1, :default => resource_label.pluralize.titleize}).merge(options)
+ defaults = { count: 2.1, default: resource_label.pluralize.titleize }
+ resource_name.translate defaults.merge options
+ end
+
+ # Forms use the model's original `param_key`, so we can't use our
+ # custom `resource_name` when the model's been renamed in ActiveAdmin.
+ def param_key
+ if resource_class.respond_to? :model_name
+ resource_class.model_name.param_key
+ else
+ resource_name.param_key
+ end
end
end
- # A subclass of ActiveModel::Name which supports the different APIs presented
- # in Rails < 3.1 and > 3.1.
class Name < ActiveModel::Name
+ delegate :hash, to: :to_str
def initialize(klass, name = nil)
- if ActiveModel::Name.instance_method(:initialize).arity == 1
- super(proxy_for_initializer(klass, name))
- else
- super(klass, nil, name)
- end
+ super(klass, nil, name)
end
def translate(options = {})
- I18n.t i18n_key, {:scope => [:activerecord, :models]}.merge(options)
- end
-
- def proxy_for_initializer(klass, name)
- return klass unless name
- return StringClassProxy.new(klass, name) if klass
-
- StringProxy.new(name)
+ I18n.t i18n_key, **{ scope: [:activerecord, :models] }.merge(options)
end
def route_key
plural
end
- class StringProxy
- def initialize(name)
- @name = name
- end
-
- def name
- @name
- end
- end
-
- class StringClassProxy < StringProxy
- delegate :lookup_ancestors, :i18n_scope, :to => :"@klass"
-
- def initialize(klass, name)
- @klass = klass || name
- super(name)
- end
+ def eql?(other)
+ to_str.eql?(other.to_str)
end
-
end
end
diff --git a/lib/active_admin/resource/ordering.rb b/lib/active_admin/resource/ordering.rb
new file mode 100644
index 00000000000..5dd7383571f
--- /dev/null
+++ b/lib/active_admin/resource/ordering.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ class Resource
+ module Ordering
+
+ def ordering
+ @ordering ||= {}.with_indifferent_access
+ end
+
+ end
+ end
+end
diff --git a/lib/active_admin/resource/page_presenters.rb b/lib/active_admin/resource/page_presenters.rb
index 18ed9a3ed37..1ea736504d8 100644
--- a/lib/active_admin/resource/page_presenters.rb
+++ b/lib/active_admin/resource/page_presenters.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
class Resource
module PagePresenters
@@ -32,8 +33,8 @@ def set_page_presenter(action, page_presenter)
#
# @param [Symbol, String] action The action to get the config for
# @param [String] type The string specified in the presenters index_name method
- # @returns [PagePresenter, nil]
- def get_page_presenter(action, type=nil)
+ # @return [PagePresenter, nil]
+ def get_page_presenter(action, type = nil)
if action.to_s == "index" && type && page_presenters[:index].kind_of?(Hash)
page_presenters[:index][type.to_sym]
@@ -50,7 +51,7 @@ def get_page_presenter(action, type=nil)
# Stores a config for all index actions supplied
#
# @param [Symbol] index_as The index type to store in the configuration
- # @param [PagePresenter] page_presenter The intance of PagePresenter to store
+ # @param [PagePresenter] page_presenter The instance of PagePresenter to store
def set_index_presenter(index_as, page_presenter)
page_presenters[:index] ||= {}
@@ -63,7 +64,7 @@ def set_index_presenter(index_as, page_presenter)
page_presenters[:index][index_as] = page_presenter
end
- # Returns the actual class for renderering the main content on the index
+ # Returns the actual class for rendering the main content on the index
# page. To set this, use the :as option in the page_presenter block.
#
# @param [Symbol, Class] symbol_or_class The component symbol or class
diff --git a/lib/active_admin/resource/pagination.rb b/lib/active_admin/resource/pagination.rb
index 7033627ccec..f619fcc0888 100644
--- a/lib/active_admin/resource/pagination.rb
+++ b/lib/active_admin/resource/pagination.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
class Resource
@@ -6,6 +7,9 @@ module Pagination
# The default number of records to display per page
attr_accessor :per_page
+ # The default number of records to display per page
+ attr_accessor :max_per_page
+
# Enable / disable pagination (defaults to true)
attr_accessor :paginate
@@ -13,6 +17,7 @@ def initialize(*args)
super
@paginate = true
@per_page = namespace.default_per_page
+ @max_per_page = namespace.max_per_page
end
end
end
diff --git a/lib/active_admin/resource/routes.rb b/lib/active_admin/resource/routes.rb
index c36229f4c22..7caea753a64 100644
--- a/lib/active_admin/resource/routes.rb
+++ b/lib/active_admin/resource/routes.rb
@@ -1,76 +1,115 @@
+# frozen_string_literal: true
module ActiveAdmin
class Resource
module Routes
- # @params params [Hash] of params: {study_id: 3}
+ # @param params [Hash] of params: { study_id: 3 }
# @return [String] the path to this resource collection page
# @example "/admin/posts"
- def route_collection_path(params = {})
- RouteBuilder.new(self).collection_path(params)
+ def route_collection_path(params = {}, additional_params = {})
+ route_builder.collection_path(params, additional_params)
end
-
+
+ def route_batch_action_path(params = {}, additional_params = {})
+ route_builder.batch_action_path(params, additional_params)
+ end
+
# @param resource [ActiveRecord::Base] the instance we want the path of
# @return [String] the path to this resource collection page
# @example "/admin/posts/1"
- def route_instance_path(resource)
- RouteBuilder.new(self).instance_path(resource)
+ def route_instance_path(resource, additional_params = {})
+ route_builder.instance_path(resource, additional_params)
+ end
+
+ def route_edit_instance_path(resource, additional_params = {})
+ route_builder.member_action_path(:edit, resource, additional_params)
+ end
+
+ def route_member_action_path(action, resource, additional_params = {})
+ route_builder.member_action_path(action, resource, additional_params)
end
# Returns the routes prefix for this config
def route_prefix
- namespace.module_name.try(:underscore)
+ namespace.route_prefix
+ end
+
+ def route_builder
+ @route_builder ||= RouteBuilder.new(self)
end
def route_uncountable?
- config = controller.resources_configuration[:self]
+ config = resources_configuration[:self]
config[:route_collection_name] == config[:route_instance_name]
end
- private
-
class RouteBuilder
def initialize(resource)
@resource = resource
end
- def collection_path(params)
+ def collection_path(params, additional_params = {})
+ route_name = route_name(
+ resource.resources_configuration[:self][:route_collection_name],
+ suffix: (resource.route_uncountable? ? "index_path" : "path")
+ )
+
+ routes.public_send route_name, *route_collection_params(params), additional_params
+ end
+
+ def batch_action_path(params, additional_params = {})
route_name = route_name(
- resource.controller.resources_configuration[:self][:route_collection_name],
- (resource.route_uncountable? ? 'index_path' : 'path')
+ resource.resources_configuration[:self][:route_collection_name],
+ action: :batch_action,
+ suffix: (resource.route_uncountable? ? "index_path" : "path")
)
- routes.send(route_name, *route_collection_params(params))
+ query = params.slice(:q, :scope)
+ query = query.permit!.to_h
+ routes.public_send route_name, *route_collection_params(params), additional_params.merge(query)
end
# @return [String] the path to this resource collection page
# @param instance [ActiveRecord::Base] the instance we want the path of
# @example "/admin/posts/1"
- def instance_path(instance)
- route_name = route_name(resource.controller.resources_configuration[:self][:route_instance_name])
+ def instance_path(instance, additional_params = {})
+ route_name = route_name(resource.resources_configuration[:self][:route_instance_name])
+
+ routes.public_send route_name, *route_instance_params(instance), additional_params
+ end
+
+ # @return [String] the path to the member action of this resource
+ # @param action [Symbol]
+ # @param instance [ActiveRecord::Base] the instance we want the path of
+ # @example "/admin/posts/1/edit"
+ def member_action_path(action, instance, additional_params = {})
+ path = resource.resources_configuration[:self][:route_instance_name]
+ route_name = route_name(path, action: action)
- routes.send(route_name, *route_instance_params(instance))
+ routes.public_send route_name, *route_instance_params(instance), additional_params
end
private
attr_reader :resource
- def route_name(resource_path_name, suffix = 'path')
+ def route_name(resource_path_name, options = {})
+ suffix = options[:suffix] || "path"
route = []
- route << resource.route_prefix # "admin"
+ route << options[:action] # "batch_action", "edit" or "new"
+ route << resource.route_prefix # "admin"
route << belongs_to_name if nested? # "category"
- route << resource_path_name # "posts" or "post"
- route << suffix # "path" or "index path"
+ route << resource_path_name # "posts" or "post"
+ route << suffix # "path" or "index path"
- route.compact.join('_').to_sym # :admin_category_posts_path
+ route.compact.join("_").to_sym # :admin_category_posts_path
end
-
# @return params to pass to instance path
def route_instance_params(instance)
if nested?
- [instance.send(belongs_to_name).to_param, instance.to_param]
+ [instance.public_send(belongs_to_target_name).to_param, instance.to_param]
else
instance.to_param
end
@@ -83,15 +122,23 @@ def route_collection_params(params)
end
def nested?
- resource.belongs_to? && resource.belongs_to_config.required?
+ resource.belongs_to? && belongs_to_config.required?
+ end
+
+ def belongs_to_target_name
+ belongs_to_config.target_name
end
def belongs_to_name
- resource.belongs_to_config.target.resource_name.singular if nested?
+ belongs_to_config.target.resource_name.singular
+ end
+
+ def belongs_to_config
+ resource.belongs_to_config
end
def routes
- Rails.application.routes.url_helpers
+ Helpers::Routes
end
end
end
diff --git a/lib/active_admin/resource/scope_to.rb b/lib/active_admin/resource/scope_to.rb
index 04998243c75..e1325f8d0a2 100644
--- a/lib/active_admin/resource/scope_to.rb
+++ b/lib/active_admin/resource/scope_to.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
class Resource
module ScopeTo
@@ -20,7 +21,7 @@ module ScopeTo
# method to call as the association. If its different, you can
# pass in the association_method as an option.
#
- # scope_to :current_user, :association_method => :blog_posts
+ # scope_to :current_user, association_method: :blog_posts
#
# will result in the following
#
@@ -28,20 +29,20 @@ module ScopeTo
#
# To conditionally use this scope, you can use conditional procs
#
- # scope_to :current_user, :if => proc{ admin_user_signed_in? }
+ # scope_to :current_user, if: proc{ admin_user_signed_in? }
#
# or
#
- # scope_to :current_user, :unless => proc{ current_user.admin? }
+ # scope_to :current_user, unless: proc{ current_user.admin? }
#
def scope_to(*args, &block)
options = args.extract_options!
method = args.first
- scope_to_config[:method] = block || method
- scope_to_config[:association_method] = options[:association_method]
- scope_to_config[:if] = options[:if]
- scope_to_config[:unless] = options[:unless]
+ scope_to_config[:method] = block || method
+ scope_to_config[:association_method] = options[:association_method]
+ scope_to_config[:if] = options[:if]
+ scope_to_config[:unless] = options[:unless]
end
@@ -55,10 +56,10 @@ def scope_to_method
def scope_to_config
@scope_to_config ||= {
- :method => nil,
- :association_method => nil,
- :if => nil,
- :unless => nil
+ method: nil,
+ association_method: nil,
+ if: nil,
+ unless: nil
}
end
diff --git a/lib/active_admin/resource/scopes.rb b/lib/active_admin/resource/scopes.rb
index 5c834f64b3d..3ff15f90137 100644
--- a/lib/active_admin/resource/scopes.rb
+++ b/lib/active_admin/resource/scopes.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
class Resource
module Scopes
@@ -10,7 +11,7 @@ def scopes
# Returns a scope for this object by its identifier
def get_scope_by_id(id)
id = id.to_s
- scopes.find{|s| s.id == id }
+ scopes.find { |s| s.id == id }
end
def default_scope(context = nil)
@@ -27,14 +28,16 @@ def default_scope(context = nil)
# If you want to internationalize the scope name, you can add
# to your i18n files a key like "active_admin.scopes.scope_method".
def scope(*args, &block)
- options = args.extract_options!
+ default_options = { show_count: namespace.scopes_show_count }
+ options = default_options.merge(args.extract_options!)
title = args[0] rescue nil
method = args[1] rescue nil
+ options[:localizer] ||= ActiveAdmin::Localizers.resource(self)
scope = ActiveAdmin::Scope.new(title, method, options, &block)
# Finds and replaces a scope by the same name if it already exists
- existing_scope_index = scopes.index{|existing_scope| existing_scope.id == scope.id }
+ existing_scope_index = scopes.index { |existing_scope| existing_scope.id == scope.id }
if existing_scope_index
scopes.delete_at(existing_scope_index)
scopes.insert(existing_scope_index, scope)
diff --git a/lib/active_admin/resource/sidebars.rb b/lib/active_admin/resource/sidebars.rb
index 4a73357ad67..7d73d44456b 100644
--- a/lib/active_admin/resource/sidebars.rb
+++ b/lib/active_admin/resource/sidebars.rb
@@ -1,4 +1,5 @@
-require 'active_admin/helpers/optional_display'
+# frozen_string_literal: true
+require_relative "../helpers/optional_display"
module ActiveAdmin
@@ -14,7 +15,8 @@ def clear_sidebar_sections!
end
def sidebar_sections_for(action, render_context = nil)
- sidebar_sections.select{|section| section.display_on?(action, render_context) }
+ sidebar_sections.select { |section| section.display_on?(action, render_context) }
+ .sort_by(&:priority)
end
def sidebar_sections?
diff --git a/lib/active_admin/resource_collection.rb b/lib/active_admin/resource_collection.rb
index 1eb37d8993d..2b7dc334a30 100644
--- a/lib/active_admin/resource_collection.rb
+++ b/lib/active_admin/resource_collection.rb
@@ -1,41 +1,72 @@
+# frozen_string_literal: true
module ActiveAdmin
# This is a container for resources, which acts much like a Hash.
# It's assumed that an added resource responds to `resource_name`.
class ResourceCollection
include Enumerable
extend Forwardable
- def_delegators :@resources, :empty?, :has_key?, :keys, :values, :[]=
+ def_delegators :@collection, :empty?, :has_key?, :keys, :values, :size
def initialize
- @resources = {}
+ @collection = {}
end
def add(resource)
- self[resource.resource_name] ||= resource
+ if match = @collection[resource.resource_name]
+ raise_if_mismatched! match, resource
+ match
+ else
+ @collection[resource.resource_name] = resource
+ end
end
# Changes `each` to pass in the value, instead of both the key and value.
def each(&block)
- values.each &block
+ values.each(&block)
end
- # Finds a resource based on the resource name, the resource class, or the base class.
- def [](klass)
- if match = @resources[klass]
- match
- elsif match = real_resources.detect{ |r| r.resource_class.to_s == klass.to_s }
- match
- elsif klass.respond_to? :base_class
- real_resources.detect{ |r| r.resource_class.to_s == klass.base_class.to_s }
- end
+ def [](obj)
+ @collection[obj] || find_resource(obj)
end
private
- # REFACTOR: ResourceCollection currently stores Resource and Page objects. That doesn't
- # make sense, because by definition a ResourceCollection is a collection of resources.
- def real_resources
- select{ |r| r.respond_to? :resource_class }
+ # Finds a resource based on the resource name, resource class, or base class.
+ def find_resource(obj)
+ resources.detect do |r|
+ r.resource_name.to_s == obj.to_s
+ end || resources.detect do |r|
+ r.resource_class.to_s == obj.to_s
+ end ||
+ if obj.respond_to? :base_class
+ resources.detect { |r| r.resource_class.to_s == obj.base_class.to_s }
+ end
+ end
+
+ def resources
+ select { |r| r.class <= Resource } # can otherwise be a Page
+ end
+
+ def raise_if_mismatched!(existing, given)
+ if existing.class != given.class
+ raise IncorrectClass.new existing, given
+ elsif given.class <= Resource && existing.resource_class != given.resource_class
+ raise ConfigMismatch.new existing, given
+ end
+ end
+
+ class IncorrectClass < StandardError
+ def initialize(existing, given)
+ super "You're trying to register #{given.resource_name} which is a #{given.class}, " +
+ "but #{existing.resource_name}, a #{existing.class} has already claimed that name."
+ end
+ end
+
+ class ConfigMismatch < StandardError
+ def initialize(existing, given)
+ super "You're trying to register #{given.resource_class} as #{given.resource_name}, " +
+ "but the existing #{existing.class} config was built for #{existing.resource_class}!"
+ end
end
end
diff --git a/lib/active_admin/resource_controller.rb b/lib/active_admin/resource_controller.rb
deleted file mode 100644
index 6950ae55be1..00000000000
--- a/lib/active_admin/resource_controller.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'inherited_resources'
-require 'active_admin/resource_controller/actions'
-require 'active_admin/resource_controller/action_builder'
-require 'active_admin/resource_controller/data_access'
-require 'active_admin/resource_controller/decorators'
-require 'active_admin/resource_controller/scoping'
-require 'active_admin/resource_controller/sidebars'
-require 'active_admin/resource_controller/resource_class_methods'
-
-module ActiveAdmin
- # All Resources Controller inherits from this controller.
- # It implements actions and helpers for resources.
- class ResourceController < BaseController
- layout :determine_active_admin_layout
-
- respond_to :html, :xml, :json
- respond_to :csv, :only => :index
-
- include Actions
- include ActionBuilder
- include Decorators
- include DataAccess
- include Scoping
- include Sidebars
- extend ResourceClassMethods
-
- def self.active_admin_config=(config)
- if @active_admin_config = config
- defaults :resource_class => config.resource_class,
- :route_prefix => config.route_prefix,
- :instance_name => config.resource_name.singular
- end
- end
-
- # Inherited Resources uses the `self.inherited(base)` hook to add
- # in `self.resource_class`. To override it, we need to install
- # our resource_class method each time we're inherited from.
- def self.inherited(base)
- super(base)
- base.override_resource_class_methods!
- end
-
- private
-
- # Returns the renderer class to use for the given action.
- def renderer_for(action)
- active_admin_namespace.view_factory["#{action}_page"]
- end
- helper_method :renderer_for
-
- end
-end
diff --git a/lib/active_admin/resource_controller/actions.rb b/lib/active_admin/resource_controller/actions.rb
deleted file mode 100644
index e0e3f434153..00000000000
--- a/lib/active_admin/resource_controller/actions.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-module ActiveAdmin
- class ResourceController < BaseController
-
- # Override the InheritedResources actions to use the
- # Active Admin templates.
- #
- # We ensure that the functionality provided by Inherited
- # Resources is still available within any ResourceController
-
- def index(options={}, &block)
- super(options) do |format|
- block.call(format) if block
- format.html { render active_admin_template('index') }
- format.csv do
- headers['Content-Type'] = 'text/csv; charset=utf-8'
- headers['Content-Disposition'] = %{attachment; filename="#{csv_filename}"}
- render active_admin_template('index')
- end
- end
- end
- alias :index! :index
-
- def show(options={}, &block)
- super do |format|
- block.call(format) if block
- format.html { render active_admin_template('show') }
- end
- end
- alias :show! :show
-
- def new(options={}, &block)
- super do |format|
- block.call(format) if block
- format.html { render active_admin_template('new') }
- end
- end
- alias :new! :new
-
- def edit(options={}, &block)
- super do |format|
- block.call(format) if block
- format.html { render active_admin_template('edit') }
- end
- end
- alias :edit! :edit
-
- def create(options={}, &block)
- super(options) do |success, failure|
- block.call(success, failure) if block
- failure.html { render active_admin_template('new') }
- end
- end
- alias :create! :create
-
- def update(options={}, &block)
- super do |success, failure|
- block.call(success, failure) if block
- failure.html { render active_admin_template('edit') }
- end
- end
- alias :update! :update
-
-
- # Make aliases protected
- protected :index!, :show!, :new!, :create!, :edit!, :update!
-
- protected
-
- # Returns the full location to the Active Admin template path
- def active_admin_template(template)
- "active_admin/resource/#{template}"
- end
-
- # Returns a filename for the csv file using the collection_name
- # and current date such as 'my-articles-2011-06-24.csv'.
- def csv_filename
- "#{resource_collection_name.to_s.gsub('_', '-')}-#{Time.now.strftime("%Y-%m-%d")}.csv"
- end
-
- end
-end
diff --git a/lib/active_admin/resource_controller/data_access.rb b/lib/active_admin/resource_controller/data_access.rb
deleted file mode 100644
index bad78f3f0a5..00000000000
--- a/lib/active_admin/resource_controller/data_access.rb
+++ /dev/null
@@ -1,301 +0,0 @@
-module ActiveAdmin
- class ResourceController < BaseController
-
- # This module overrides most of the data access methods in Inherited
- # Resources to provide Active Admin with it's data.
- #
- # The module also deals with authorization and resource callbacks.
- #
- module DataAccess
- extend ActiveSupport::Concern
-
- include ActiveAdmin::Callbacks
- include ActiveAdmin::ScopeChain
-
- included do
- define_active_admin_callbacks :build, :create, :update, :save, :destroy
- end
-
- protected
-
- # Retrieve, memoize and authorize the current collection from the db. This
- # method delegates the finding of the collection to #find_collection.
- #
- # Once #collection has been called, the collection is available using
- # either the @collection instance variable or an instance variable named
- # after the resource that the collection is for. eg: Post => @post.
- #
- # @returns [ActiveRecord::Relation] The collection for the index
- def collection
- _collection = get_collection_ivar
-
- return _collection if _collection
-
- _collection = find_collection
- authorize! ActiveAdmin::Authorization::READ, active_admin_config.resource_class
-
- set_collection_ivar _collection
- end
-
-
- # Does the actual work of retrieving the current collection from the db.
- # This is a great method to override if you would like to perform
- # some additional db # work before your controller returns and
- # authorizes the collection.
- #
- # @returns [ActiveRecord::Relation] The collectin for the index
- def find_collection
- collection = scoped_collection
-
- collection = apply_authorization_scope(collection)
- collection = apply_sorting(collection)
- collection = apply_filtering(collection)
- collection = apply_scoping(collection)
- collection = apply_pagination(collection)
- collection = apply_decorator(collection)
-
- collection
- end
-
-
- # Override this method in your controllers to modify the start point
- # of our searches and index.
- #
- # This method should return an ActiveRecord::Relation object so that
- # the searching and filtering can be applied on top
- #
- # Note, unless you are doing something special, you should use the
- # scope_to method from the Scoping module instead of overriding this
- # method.
- def scoped_collection
- end_of_association_chain
- end
-
- # Retrieve, memoize and authorize a resource based on params[:id]. The
- # actual work of finding the resource is done in #find_resource.
- #
- # This method is used on all the member actions:
- #
- # * show
- # * edit
- # * update
- # * destroy
- #
- # @returns [ActiveRecord::Base] An active record object
- def resource
- _resource = get_resource_ivar
-
- return _resource if _resource
-
- _resource = find_resource
- authorize_resource! _resource
-
- if decorator?
- _resource = decorator_class.new(_resource)
- end
-
- set_resource_ivar(_resource)
- end
-
- def decorator?
- !!active_admin_config.decorator_class
- end
-
- def decorator_class
- active_admin_config.decorator_class
- end
-
-
- # Does the actual work of finding a resource in the database. This
- # method uses the finder method as defined in InheritedResources.
- #
- # @returns [ActiveRecord::Base] An active record object.
- def find_resource
- scoped_collection.send(method_for_find, params[:id])
- end
-
-
- # Builds, memoize and authorize a new instance of the resource. The
- # actual work of building the new instance is delegated to the
- # #build_new_resource method.
- #
- # This method is used to instantiate and authorize new resources in the
- # new and create controller actions.
- #
- # @returns [ActiveRecord::Base] An un-saved active record base object
- def build_resource
- return resource if resource = get_resource_ivar
-
- resource = build_new_resource
-
- run_build_callbacks resource
- authorize_resource! resource
-
- set_resource_ivar(resource)
- end
-
- # Builds a new resource. This method uses the method_for_build provided
- # by Inherited Resources.
- #
- # @returns [ActiveRecord::Base] An un-saved active record base object
- def build_new_resource
- scoped_collection.send(method_for_build, *resource_params)
- end
-
- # Calls all the appropriate callbacks and then creates the new resource.
- #
- # @param [ActiveRecord::Base] object The new resource to create
- #
- # @returns [void]
- def create_resource(object)
- run_create_callbacks object do
- save_resource(object)
- end
- end
-
- # Calls all the appropriate callbacks and then saves the new resource.
- #
- # @param [ActiveRecord::Base] object The new resource to save
- #
- # @returns [void]
- def save_resource(object)
- run_save_callbacks object do
- object.save
- end
- end
-
- # Update an object with the given attributes. Also calls the appropriate
- # callbacks for update action.
- #
- # @param [ActiveRecord::Base] object The instance to update
- #
- # @param [Array] attributes An array with the attributes in the first position
- # and the Active Record "role" in the second. The role
- # may be set to nil.
- #
- # @returns [void]
- def update_resource(object, attributes)
- if object.respond_to?(:assign_attributes)
- object.assign_attributes(*attributes)
- else
- object.attributes = attributes[0]
- end
-
- run_update_callbacks object do
- save_resource(object)
- end
- end
-
- # Destroys an object from the database and calls appropriate callbacks.
- #
- # @returns [void]
- def destroy_resource(object)
- run_destroy_callbacks object do
- object.destroy
- end
- end
-
-
- #
- # Collection Helper Methods
- #
-
-
- # Gives the authorization library a change to pre-scope the collection.
- #
- # In the case of the CanCan adapter, it calls `#accessible_by` on
- # the collection.
- #
- # @param [ActiveRecord::Relation] collection The collection to scope
- #
- # @retruns [ActiveRecord::Relation] a scoped collection of query
- def apply_authorization_scope(collection)
- action_name = action_to_permission(params[:action])
- active_admin_authorization.scope_collection(collection, action_name)
- end
-
-
- def apply_sorting(chain)
- params[:order] ||= active_admin_config.sort_order
- if params[:order] && params[:order] =~ /^([\w\_\.]+)_(desc|asc)$/
- column = $1
- order = $2
- table = active_admin_config.resource_column_names.include?(column) ? active_admin_config.resource_table_name : nil
- table_column = (column =~ /\./) ? column :
- [table, active_admin_config.resource_quoted_column_name(column)].compact.join(".")
-
- chain.reorder("#{table_column} #{order}")
- else
- chain # just return the chain
- end
- end
-
- def apply_filtering(chain)
- @search = chain.metasearch(clean_search_params(params[:q]))
- @search.relation
- end
-
- def clean_search_params(search_params)
- return {} unless search_params.is_a?(Hash)
- search_params = search_params.dup
- search_params.delete_if do |key, value|
- value == ""
- end
- search_params
- end
-
- def apply_scoping(chain)
- @collection_before_scope = chain
-
- if current_scope
- scope_chain(current_scope, chain)
- else
- chain
- end
- end
-
- def collection_before_scope
- @collection_before_scope
- end
-
- def current_scope
- @current_scope ||= if params[:scope]
- active_admin_config.get_scope_by_id(params[:scope]) if params[:scope]
- else
- active_admin_config.default_scope(self)
- end
- end
-
- def apply_pagination(chain)
- page_method_name = Kaminari.config.page_method_name
- page = params[Kaminari.config.param_name]
-
- chain.send(page_method_name, page).per(per_page)
- end
-
- def per_page
- return max_csv_records if request.format == 'text/csv'
- return max_per_page if active_admin_config.paginate == false
-
- @per_page || active_admin_config.per_page
- end
-
- def max_csv_records
- 10_000
- end
-
- def max_per_page
- 10_000
- end
-
- def apply_decorator(chain)
- if decorator?
- decorator_class.decorate_collection(chain)
- else
- chain
- end
- end
-
- end
- end
-end
diff --git a/lib/active_admin/resource_controller/decorators.rb b/lib/active_admin/resource_controller/decorators.rb
deleted file mode 100644
index cbac583d198..00000000000
--- a/lib/active_admin/resource_controller/decorators.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module ActiveAdmin
- class ResourceController < BaseController
- module Decorators
- protected
-
- def resource
- decorator = active_admin_config.decorator_class
- resource = super
- decorator ? decorator.new(resource) : resource
- end
-
- def active_admin_collection
- decorator = active_admin_config.decorator_class
- collection = super
- decorator ? decorator.decorate_collection(collection) : collection
- end
-
- end
- end
-end
diff --git a/lib/active_admin/resource_controller/sidebars.rb b/lib/active_admin/resource_controller/sidebars.rb
deleted file mode 100644
index dfbaa0e6389..00000000000
--- a/lib/active_admin/resource_controller/sidebars.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-module ActiveAdmin
- class ResourceController < BaseController
-
- module Sidebars
-
- protected
-
- def skip_sidebar!
- @skip_sidebar = true
- end
-
- def skip_sidebar?
- @skip_sidebar == true
- end
- end
-
- end
-end
diff --git a/lib/active_admin/resource_dsl.rb b/lib/active_admin/resource_dsl.rb
index 9672bc03b83..5526cddd129 100644
--- a/lib/active_admin/resource_dsl.rb
+++ b/lib/active_admin/resource_dsl.rb
@@ -1,8 +1,29 @@
+# frozen_string_literal: true
module ActiveAdmin
- # This is the class where all the register blocks are instance eval'd
+ # This is the class where all the register blocks are evaluated.
class ResourceDSL < DSL
+
private
+ # Redefine sort behaviour for column
+ #
+ # For example:
+ #
+ # # nulls last
+ # order_by(:age) do |order_clause|
+ # [order_clause.to_sql, 'NULLS LAST'].join(' ') if order_clause.order == 'desc'
+ # end
+ #
+ # # by last_name but in the case that there is no last name, by first_name.
+ # order_by(:full_name) do |order_clause|
+ # ['COALESCE(NULLIF(last_name, ''), first_name), first_name', order_clause.order].join(' ')
+ # end
+ #
+ #
+ def order_by(column, &block)
+ config.ordering[column] = block
+ end
+
def belongs_to(target, options = {})
config.belongs_to(target, options)
end
@@ -17,6 +38,49 @@ def scope(*args, &block)
config.scope(*args, &block)
end
+ # Store relations that should be included
+ def includes(*args)
+ config.includes.push(*args)
+ end
+
+ #
+ # Keys included in the `permitted_params` setting are automatically whitelisted.
+ #
+ # Either
+ #
+ # permit_params :title, :author, :body, tags: []
+ #
+ # Or
+ #
+ # permit_params do
+ # defaults = [:title, :body]
+ # if current_user.admin?
+ # defaults + [:author]
+ # else
+ # defaults
+ # end
+ # end
+ #
+ def permit_params(*args, &block)
+ param_key = config.param_key.to_sym
+
+ controller do
+ define_method :permitted_params do
+ belongs_to_param = active_admin_config.belongs_to_param
+ create_another_param = :create_another if active_admin_config.create_another
+
+ permitted_params =
+ active_admin_namespace.permitted_params +
+ Array.wrap(belongs_to_param) +
+ Array.wrap(create_another_param)
+
+ params.permit(*permitted_params, param_key => block ? instance_exec(&block) : args)
+ end
+
+ private :permitted_params
+ end
+ end
+
# Configure the index page for the resource
def index(options = {}, &block)
options[:as] ||= :table
@@ -41,11 +105,13 @@ def form(options = {}, &block)
# column("Author") { |post| post.author.full_name }
# end
#
- # csv :col_sep => ";", :force_quotes => true do
+ # csv col_sep: ";", force_quotes: true do
# column :name
# end
#
- def csv(options={}, &block)
+ def csv(options = {}, &block)
+ options[:resource] = config
+
config.csv_builder = CSVBuilder.new(options, &block)
end
@@ -57,7 +123,7 @@ def csv(options={}, &block)
#
# ActiveAdmin.register Post do
# member_action :comments do
- # @post = Post.find(params[:id]
+ # @post = Post.find(params[:id])
# @comments = @post.comments
# end
# end
@@ -69,12 +135,14 @@ def csv(options={}, &block)
# action.
#
def action(set, name, options = {}, &block)
+ warn "Warning: method `#{name}` already defined in #{controller.name}" if controller.method_defined?(name)
+
set << ControllerAction.new(name, options)
title = options.delete(:title)
controller do
- before_filter(:only => [name]) { @page_title = title } if title
- define_method(name, &block || Proc.new{})
+ before_action(only: [name]) { @page_title = title } if title
+ define_method(name, &block || Proc.new {})
end
end
@@ -86,6 +154,26 @@ def collection_action(name, options = {}, &block)
action config.collection_actions, name, options, &block
end
+ # Set the default `filters` list for this resource's string filters.
+ # Takes priority over the namespace-level `string_input_filters` setting.
+ #
+ # For example:
+ #
+ # ActiveAdmin.register Post do
+ # string_input_filters [:eq, :cont]
+ # end
+ #
+ def string_input_filters(value)
+ config.string_input_filters = value
+ end
+
+ def decorate_with(decorator_class)
+ # Force storage as a string. This will help us with reloading issues.
+ # Assuming decorator_class.to_s will return the name of the class allows
+ # us to handle a string or a class.
+ config.decorator_class_name = "::#{ decorator_class }"
+ end
+
# Defined Callbacks
#
# == After Build
@@ -110,14 +198,15 @@ def collection_action(name, options = {}, &block)
# == Before / After Destroy
# Called before and after the object is destroyed from the database.
#
- delegate :before_build, :after_build, :to => :controller
- delegate :before_create, :after_create, :to => :controller
- delegate :before_update, :after_update, :to => :controller
- delegate :before_save, :after_save, :to => :controller
- delegate :before_destroy, :after_destroy, :to => :controller
+ delegate :before_build, :after_build, to: :controller
+ delegate :before_create, :after_create, to: :controller
+ delegate :before_update, :after_update, to: :controller
+ delegate :before_save, :after_save, to: :controller
+ delegate :before_destroy, :after_destroy, to: :controller
- # Standard rails filters
- delegate :before_filter, :skip_before_filter, :after_filter, :around_filter, :to => :controller
+ standard_rails_filters =
+ AbstractController::Callbacks::ClassMethods.public_instance_methods.select { |m| m.end_with?('_action') }
+ delegate(*standard_rails_filters, to: :controller)
# Specify which actions to create in the controller
#
@@ -128,7 +217,7 @@ def collection_action(name, options = {}, &block)
# end
#
# Will only create the index and show actions (no create, update or delete)
- delegate :actions, :to => :controller
+ delegate :actions, to: :controller
end
end
diff --git a/lib/active_admin/router.rb b/lib/active_admin/router.rb
index 3e53ebf3b0a..38b8f92b101 100644
--- a/lib/active_admin/router.rb
+++ b/lib/active_admin/router.rb
@@ -1,106 +1,115 @@
+# frozen_string_literal: true
module ActiveAdmin
+ # @private
class Router
- def initialize(application)
- @application = application
+ attr_reader :namespaces, :router
+
+ def initialize(router:, namespaces:)
+ @router = router
+ @namespaces = namespaces
end
- # Creates all the necessary routes for the ActiveAdmin configurations
- #
- # Use this within the routes.rb file:
- #
- # Application.routes.draw do |map|
- # ActiveAdmin.routes(self)
- # end
- #
- def apply(router)
- define_root_routes router
- define_resource_routes router
+ def apply
+ define_root_routes
+ define_resources_routes
end
- def define_root_routes(router)
- router.instance_exec @application.namespaces.values do |namespaces|
- namespaces.each do |namespace|
- if namespace.root?
- root :to => namespace.root_to
- else
- namespace namespace.name do
- root :to => namespace.root_to
- end
+ private
+
+ def define_root_routes
+ namespaces.each do |namespace|
+ if namespace.root?
+ router.root namespace.root_to_options.merge(to: namespace.root_to)
+ else
+ router.namespace namespace.name, **namespace.route_options.dup do
+ router.root namespace.root_to_options.merge(to: namespace.root_to, as: :root)
end
end
end
end
# Defines the routes for each resource
- def define_resource_routes(router)
- router.instance_exec @application.namespaces, self do |namespaces, aa_router|
- resources = namespaces.values.map{ |n| n.resources.values }.flatten
- resources.each do |config|
- routes = aa_router.resource_routes(config)
-
- # Add in the parent if it exists
- if config.belongs_to?
- belongs_to = routes
- routes = Proc.new do
- # If it's optional, make the normal resource routes
- instance_exec &belongs_to if config.belongs_to_config.optional?
-
- # Make the nested belongs_to routes
- # :only is set to nothing so that we don't clobber any existing routes on the resource
- resources config.belongs_to_config.target.resource_name.plural, :only => [] do
- instance_exec &belongs_to
- end
- end
- end
+ def define_resources_routes
+ resources = namespaces.flat_map { |n| n.resources.values }
+ resources.each do |config|
+ define_resource_routes(config)
+ end
+ end
- # Add on the namespace if required
- unless config.namespace.root?
- nested = routes
- routes = Proc.new do
- namespace config.namespace.name do
- instance_exec &nested
- end
- end
- end
+ def define_resource_routes(config)
+ if config.namespace.root?
+ define_routes(config)
+ else
+ # Add on the namespace if required
+ define_namespace(config)
+ end
+ end
- instance_exec &routes
- end
+ def define_routes(config)
+ if config.belongs_to?
+ define_belongs_to_routes(config)
+ else
+ page_or_resource_routes(config)
end
end
+ def page_or_resource_routes(config)
+ config.is_a?(Page) ? page_routes(config) : resource_routes(config)
+ end
+
def resource_routes(config)
- Proc.new do
- # Builds one route for each HTTP verb passed in
- build_route = proc{ |verbs, *args|
- [*verbs].each{ |verb| send verb, *args }
- }
- # Deals with +ControllerAction+ instances
- build_action = proc{ |action|
- build_route.call(action.http_verb, action.name)
- }
- case config
- when ::ActiveAdmin::Resource
- resources config.resource_name.route_key, :only => config.defined_actions do
- member do
- config.member_actions.each &build_action
- end
-
- collection do
- config.collection_actions.each &build_action
- post :batch_action
- end
- end
- when ::ActiveAdmin::Page
- page = config.underscored_resource_name
- get "/#{page}" => "#{page}#index"
- config.page_actions.each do |action|
- build_route.call action.http_verb, "/#{page}/#{action.name}" => "#{page}##{action.name}"
- end
- else
- raise "Unsupported config class: #{config.class}"
+ router.resources config.resource_name.route_key, only: config.defined_actions do
+ define_actions(config)
+ end
+ end
+
+ def page_routes(config)
+ page = config.underscored_resource_name
+ router.get "/#{page}", to: "#{page}#index"
+ config.page_actions.each do |action|
+ Array.wrap(action.http_verb).each do |verb|
+ build_route(verb, "/#{page}/#{action.name}", to: "#{page}##{action.name}")
end
end
+ end
+
+ # Defines member and collection actions
+ def define_actions(config)
+ router.member do
+ config.member_actions.each { |action| build_action(action) }
+ end
+
+ router.collection do
+ config.collection_actions.each { |action| build_action(action) }
+ router.post :batch_action if config.batch_actions_enabled?
+ end
+ end
+
+ # Deals with +ControllerAction+ instances
+ # Builds one route for each HTTP verb passed in
+ def build_action(action)
+ build_route(action.http_verb, action.name)
+ end
+
+ def build_route(verbs, ...)
+ Array.wrap(verbs).each { |verb| router.send(verb, ...) }
+ end
+
+ def define_belongs_to_routes(config)
+ # If it's optional, make the normal resource routes
+ page_or_resource_routes(config) if config.belongs_to_config.optional?
+
+ # Make the nested belongs_to routes
+ # :only is set to nothing so that we don't clobber any existing routes on the resource
+ router.resources config.belongs_to_config.target.resource_name.plural, only: [] do
+ page_or_resource_routes(config)
+ end
+ end
+ def define_namespace(config)
+ router.namespace config.namespace.name, **config.namespace.route_options.dup do
+ define_routes(config)
+ end
end
end
end
diff --git a/lib/active_admin/sass/active_admin.scss b/lib/active_admin/sass/active_admin.scss
deleted file mode 100644
index 72958351118..00000000000
--- a/lib/active_admin/sass/active_admin.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// This file is included when using Rails 3.0
-@import "active_admin/mixins";
-@import "active_admin/base";
diff --git a/lib/active_admin/sass/css_loader.rb b/lib/active_admin/sass/css_loader.rb
deleted file mode 100644
index ac78be3d9cc..00000000000
--- a/lib/active_admin/sass/css_loader.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'sass/importers'
-
-# This monkey patches the SASS filesystem importer to work with files
-# that are named *.css.scss. This allows us to be compatible with both
-# Rails 3.0.* and Rails 3.1
-#
-# This should only be loaded in Rails 3.0 apps.
-class Sass::Importers::Filesystem
-
- # We want to ensure that all *.css.scss files are loaded as scss files
- def extensions_with_css
- extensions_without_css.merge('{css.,}scss' => :scss)
- end
- alias_method_chain :extensions, :css
-
-end
diff --git a/lib/active_admin/sass/helpers.rb b/lib/active_admin/sass/helpers.rb
deleted file mode 100644
index edceec770d9..00000000000
--- a/lib/active_admin/sass/helpers.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'sass'
-require 'sass/script/functions'
-require 'sass-rails' if ActiveAdmin::DependencyChecker.rails_3_1?
-
-module ActiveAdmin
- module Sass
- module Helpers
-
- if ActiveAdmin::DependencyChecker.rails_3_1?
- include ::Sass::Rails::Helpers
- end
-
- # Provides a helper in SASS to ensure that the paths to image
- # assets are always correct across Rails versions.
- #
- # Example:
- #
- # background: url(active_admin_image_path('some_image.png')) 0 0 repeat-x;
- #
- # Will result in:
- #
- # background: url("/images/active_admin/some_image.png") 0 0 repeat-x;
- #
- # Or in Rails 3.1 with asset pipeline enebaled:
- #
- # background: url("/assets/active_admin/some_image.png") 0 0 repeat-x;
- #
- # @param [Sass::Script::String] asset the path to the image after */active_admin/
- #
- # @return [Sass::Script::String] path to the image
- #
- def active_admin_image_path(asset)
- if ActiveAdmin.use_asset_pipeline?
- asset_path(::Sass::Script::String.new("active_admin/#{asset.value}"), ::Sass::Script::String.new('image'))
- else
- ::Sass::Script::String.new("/images/active_admin/#{asset.value}", true)
- end
- end
-
- end
- end
-end
-
-# Install for use in Sass
-Sass::Script::Functions.send :include, ActiveAdmin::Sass::Helpers
diff --git a/lib/active_admin/scope.rb b/lib/active_admin/scope.rb
index 3e0090757e1..6eece2052ea 100644
--- a/lib/active_admin/scope.rb
+++ b/lib/active_admin/scope.rb
@@ -1,7 +1,8 @@
+# frozen_string_literal: true
module ActiveAdmin
class Scope
- attr_reader :scope_method, :id, :scope_block, :display_if_block, :show_count, :default_block
+ attr_reader :scope_method, :id, :scope_block, :display_if_block, :show_count, :default_block, :group
# Create a Scope
#
@@ -13,45 +14,62 @@ class Scope
# Scope.new('Published', :public)
# # => Scope with name 'Published' and scope method :public
#
- # Scope.new 'Published', :public, :if => proc { current_admin_user.can? :manage, resource_class } do |articles|
- # articles.where :published => true
+ # Scope.new(:published, show_count: :async)
+ # # => Scope with name 'Published' that queries its count asynchronously
+ #
+ # Scope.new(:published, show_count: false)
+ # # => Scope with name 'Published' that does not display a count
+ #
+ # Scope.new 'Published', :public, if: proc { current_admin_user.can? :manage, resource_class } do |articles|
+ # articles.where published: true
# end
# # => Scope with name 'Published' and scope method :public, optionally displaying the scope per the :if block
#
- # Scope.new('Published') { |articles| articles.where(:published => true) }
+ # Scope.new('Published') { |articles| articles.where(published: true) }
# # => Scope with name 'Published' using a block to scope
#
# Scope.new ->{Date.today.strftime '%A'}, :published_today
# # => Scope with dynamic title using the :published_today scope method
#
+ # Scope.new :published, nil, group: :status
+ # # => Scope with the group :status
+ #
def initialize(name, method = nil, options = {}, &block)
- @name, @scope_method = name, method.try(:to_sym)
+ @name = name
+ @scope_method = method.try(:to_sym)
if name.is_a? Proc
raise "A string/symbol is required as the second argument if your label is a proc." unless method
- @id = method.to_s.gsub(' ', '').underscore
+ @id = method.to_s.parameterize(separator: "_")
else
@scope_method ||= name.to_sym
- @id = name.to_s.gsub(' ', '').underscore
+ @id = name.to_s.parameterize(separator: "_")
end
- @scope_method = nil if @scope_method == :all
- @scope_method, @scope_block = nil, block if block_given?
-
- @show_count = options[:show_count].nil? ? true : options[:show_count]
- @display_if_block = options[:if] || proc{ true }
- @default_block = options[:default] || proc{ false }
+ @scope_method = nil if @scope_method == :all
+ if block
+ @scope_method = nil
+ @scope_block = block
+ end
+ @localizer = options[:localizer]
+ @show_count = options.fetch(:show_count, true)
+ @display_if_block = options[:if] || proc { true }
+ @default_block = options[:default] || proc { false }
+ @group = options[:group].try(:to_sym)
end
def name
case @name
- when Proc then @name.call.to_s
- when String then @name
- when Symbol then @name.to_s.titleize
- else @name.to_s
+ when String then @name
+ when Symbol then @localizer ? @localizer.t(@name, scope: "scopes") : @name.to_s.titleize
+ else @name
end
end
+ def async_count?
+ @show_count == :async
+ end
+
end
end
diff --git a/lib/active_admin/settings_node.rb b/lib/active_admin/settings_node.rb
new file mode 100644
index 00000000000..aae29e8953f
--- /dev/null
+++ b/lib/active_admin/settings_node.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+module ActiveAdmin
+
+ class SettingsNode
+ class << self
+ # Never instantiated. Variables are stored in the singleton_class.
+ private_class_method :new
+
+ # @return anonymous class with same accessors as the superclass.
+ def build(superclass = self)
+ Class.new(superclass)
+ end
+
+ def register(name, value)
+ class_attribute name
+ send :"#{name}=", value
+ end
+ end
+ end
+end
diff --git a/lib/active_admin/sidebar_section.rb b/lib/active_admin/sidebar_section.rb
index 49397e4a5c9..a920f5a432f 100644
--- a/lib/active_admin/sidebar_section.rb
+++ b/lib/active_admin/sidebar_section.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
class SidebarSection
@@ -6,31 +7,28 @@ class SidebarSection
attr_accessor :name, :options, :block
def initialize(name, options = {}, &block)
- @name, @options, @block = name, options, block
+ @name = name.to_s
+ @options = options
+ @block = block
normalize_display_options!
end
# The id gets used for the div in the view
def id
- "#{name.to_s.downcase.underscore}_sidebar_section".parameterize
+ "#{name.downcase.underscore}_sidebar_section".parameterize
end
- def icon?
- !!options[:icon]
- end
-
- def icon
- options[:icon] if icon?
+ # If a block is not passed in, the name of the partial to render
+ def partial_name
+ options[:partial] || "#{name.downcase.tr(' ', '_')}_sidebar"
end
- # The title gets displayed within the section in the view
- def title
- I18n.t("active_admin.sidebars.#{name.to_s}", :default => name.to_s.titlecase)
+ def custom_class
+ options[:class]
end
- # If a block is not passed in, the name of the partial to render
- def partial_name
- options[:partial] || "#{name.to_s.downcase.gsub(' ', '_')}_sidebar"
+ def priority
+ options[:priority] || 10
end
end
diff --git a/lib/active_admin/version.rb b/lib/active_admin/version.rb
index 605ec4b7d95..be8415a6c12 100644
--- a/lib/active_admin/version.rb
+++ b/lib/active_admin/version.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveAdmin
- VERSION = '0.6.0'
+ VERSION = "4.0.0.beta23"
end
diff --git a/lib/active_admin/view_factory.rb b/lib/active_admin/view_factory.rb
deleted file mode 100644
index b0689721f84..00000000000
--- a/lib/active_admin/view_factory.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'active_admin/abstract_view_factory'
-
-module ActiveAdmin
- class ViewFactory < AbstractViewFactory
-
- # Register Helper Renderers
- register :global_navigation => ActiveAdmin::Views::TabbedNavigation,
- :utility_navigation => ActiveAdmin::Views::TabbedNavigation,
- :site_title => ActiveAdmin::Views::SiteTitle,
- :action_items => ActiveAdmin::Views::ActionItems,
- :title_bar => ActiveAdmin::Views::TitleBar,
- :header => ActiveAdmin::Views::Header,
- :footer => ActiveAdmin::Views::Footer,
- :index_scopes => ActiveAdmin::Views::Scopes,
- :blank_slate => ActiveAdmin::Views::BlankSlate,
- :action_list_popover => ActiveAdmin::Views::ActionListPopover
-
- # Register All The Pages
- register :dashboard_page => ActiveAdmin::Views::Pages::Dashboard,
- :index_page => ActiveAdmin::Views::Pages::Index,
- :show_page => ActiveAdmin::Views::Pages::Show,
- :new_page => ActiveAdmin::Views::Pages::Form,
- :edit_page => ActiveAdmin::Views::Pages::Form,
- :layout => ActiveAdmin::Views::Pages::Layout,
- :page => ActiveAdmin::Views::Pages::Page
-
- end
-end
diff --git a/lib/active_admin/view_helpers.rb b/lib/active_admin/view_helpers.rb
index d5dfdbff9c0..d4c84d9ba50 100644
--- a/lib/active_admin/view_helpers.rb
+++ b/lib/active_admin/view_helpers.rb
@@ -1,21 +1,9 @@
+# frozen_string_literal: true
module ActiveAdmin
module ViewHelpers
-
# Require all ruby files in the view helpers dir
- Dir[File.expand_path('../view_helpers', __FILE__) + "/*.rb"].each{|f| require f }
+ Dir[File.expand_path("view_helpers", __dir__) + "/*.rb"].each { |f| require f }
- include AssignsWithIndifferentAccessHelper
- include ActiveAdminApplicationHelper
- include AutoLinkHelper
- include BreadcrumbHelper
- include DisplayHelper
- include IconHelper
include MethodOrProcHelper
- include SidebarHelper
- include FormHelper
- include TitleHelper
- include ViewFactoryHelper
- include FlashHelper
-
end
end
diff --git a/lib/active_admin/view_helpers/active_admin_application_helper.rb b/lib/active_admin/view_helpers/active_admin_application_helper.rb
deleted file mode 100644
index d475c919c56..00000000000
--- a/lib/active_admin/view_helpers/active_admin_application_helper.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-module ActiveAdmin
- module ViewHelpers
- module ActiveAdminApplicationHelper
-
- # Returns the current Active Admin application instance
- def active_admin_application
- ActiveAdmin.application
- end
-
- end
- end
-end
diff --git a/lib/active_admin/view_helpers/assigns_with_indifferent_access_helper.rb b/lib/active_admin/view_helpers/assigns_with_indifferent_access_helper.rb
deleted file mode 100644
index c866e235391..00000000000
--- a/lib/active_admin/view_helpers/assigns_with_indifferent_access_helper.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module AssignsWithIndifferentAccessHelper
-
- def assigns
- @assigns_with_indifferent_access_helper ||= HashWithIndifferentAccess.new(super)
- end
-
-end
diff --git a/lib/active_admin/view_helpers/auto_link_helper.rb b/lib/active_admin/view_helpers/auto_link_helper.rb
deleted file mode 100644
index 4b92f24b97d..00000000000
--- a/lib/active_admin/view_helpers/auto_link_helper.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module ActiveAdmin
- module ViewHelpers
- module AutoLinkHelper
-
- # Automatically links objects to their resource controllers. If
- # the resource has not been registered, a string representation of
- # the object is returned.
- #
- # The default content in the link is returned from ActiveAdmin::ViewHelpers::DisplayHelper#display_name
- #
- # You can pass in the content to display
- # eg: auto_link(@post, "My Link Content")
- #
- def auto_link(resource, link_content = nil)
- content = link_content || display_name(resource)
- if (registration = active_admin_resource_for(resource.class))
- begin
- content = link_to(content, registration.route_instance_path(resource))
- rescue
- # ignored
- end
- end
- content
- end
-
- # Returns the ActiveAdmin::Resource instance for a class
- def active_admin_resource_for(klass)
- return nil unless respond_to?(:active_admin_namespace)
- active_admin_namespace.resource_for(klass)
- end
-
- end
- end
-end
diff --git a/lib/active_admin/view_helpers/breadcrumb_helper.rb b/lib/active_admin/view_helpers/breadcrumb_helper.rb
deleted file mode 100644
index bf3f1c91759..00000000000
--- a/lib/active_admin/view_helpers/breadcrumb_helper.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-module ActiveAdmin
- module ViewHelpers
- module BreadcrumbHelper
-
- # Returns an array of links to use in a breadcrumb
- def breadcrumb_links(path = request.path)
- parts = path[1..-1].split('/') # remove leading "/" and split up the URL
- parts.pop # remove last since it's used as the page title
-
- parts.each_with_index.map do |part, index|
- # 1. try using `display_name` if we can locate a DB object
- # 2. try using the model name translation
- # 3. default to calling `titlecase` on the URL fragment
- if part =~ /\A(\d+|[a-f0-9]{24})\z/ && parts[index-1]
- config = active_admin_config.belongs_to_config.try(:target) || active_admin_config
- obj = config.resource_class.where( config.resource_class.primary_key => part ).first
- name = display_name obj
- end
- name ||= I18n.t "activerecord.models.#{part.singularize}", :count => 1.1, :default => part.titlecase
-
- link_to name, '/' + parts[0..index].join('/')
- end
- end
-
- end
- end
-end
diff --git a/lib/active_admin/view_helpers/display_helper.rb b/lib/active_admin/view_helpers/display_helper.rb
deleted file mode 100644
index 4390ffac2f0..00000000000
--- a/lib/active_admin/view_helpers/display_helper.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-module ActiveAdmin
- module ViewHelpers
- module DisplayHelper
-
- # Attempts to call any known display name methods on the resource.
- # See the setting in `application.rb` for the list of methods and their priority.
- def display_name(resource)
- resource.send display_name_method_for resource if resource
- end
-
- # Looks up and caches the first available display name method.
- def display_name_method_for(resource)
- @@display_name_methods_cache ||= {}
- @@display_name_methods_cache[resource.class] ||= begin
- methods = active_admin_application.display_name_methods - association_methods_for(resource)
- methods.detect{ |method| resource.respond_to? method }
- end
- end
-
- # To prevent conflicts, we exclude any methods that happen to be associations.
- def association_methods_for(resource)
- return [] unless resource.class.respond_to? :reflect_on_all_associations
- resource.class.reflect_on_all_associations.map(&:name)
- end
-
- # Return a pretty string for any object
- # Date Time are formatted via #localize with :format => :long
- # ActiveRecord objects are formatted via #auto_link
- # We attempt to #display_name of any other objects
- def pretty_format(object)
- case object
- when String
- object
- when Arbre::Element
- object
- when Date, Time
- localize(object, :format => :long)
- when ActiveRecord::Base
- auto_link(object)
- else
- display_name(object)
- end
- end
-
- end
- end
-end
diff --git a/lib/active_admin/view_helpers/download_format_links_helper.rb b/lib/active_admin/view_helpers/download_format_links_helper.rb
deleted file mode 100644
index 17785f54e47..00000000000
--- a/lib/active_admin/view_helpers/download_format_links_helper.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-module ActiveAdmin
- module ViewHelpers
- module DownloadFormatLinksHelper
-
- module ClassMethods
-
- # A ready only of formats to make available in index/paginated
- # collection view.
- # @return [Array]
- # @see add_format for information on adding custom download link
- # formats
- def formats
- @formats ||= [:csv, :xml, :json]
- @formats.clone
- end
-
- # Adds a mime type extension to the list of available formats.
- # You must register the extension prior to adding it to the list
- # of avilable formats. This should be used by plugins that want
- # to add additional formats to the download format links.
- # @param [Symbol] extension the mime extension to add
- # @return [Array] A copy of the updated formats array.
- def add_format extension
- unless formats.include?(extension)
- if Mime::Type.lookup_by_extension(extension).nil?
- raise ArgumentError, "The mime extension you defined: #{extension} is not registered. Please register it via Mime::Type.register before adding it to the available formats."
- end
- @formats << extension
- end
- formats
- end
- end
-
- # TODO: Refactor to new HTML DSL
- def build_download_format_links(formats = self.class.formats)
- links = formats.collect do |format|
- link_to format.to_s.upcase, { :format => format}.merge(request.query_parameters.except(:commit, :format))
- end
- div :class => "download_links" do
- text_node [I18n.t('active_admin.download'), links].flatten.join(" ").html_safe
- end
- end
-
- def self.included base
- base.extend ClassMethods
- end
- end
- end
-end
-
diff --git a/lib/active_admin/view_helpers/fields_for.rb b/lib/active_admin/view_helpers/fields_for.rb
deleted file mode 100644
index 09ea6f3da9c..00000000000
--- a/lib/active_admin/view_helpers/fields_for.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-module ActiveAdmin
- module ViewHelpers
- module FormHelper
-
- # Flatten a params Hash to an array of fields.
- #
- # @param params [Hash]
- # @param options [Hash] :namespace and :except
- #
- # @return [Array] of [Hash] with one element.
- #
- # @example
- # fields_for_params(scope: "all", users: ["greg"])
- # => [ {"scope" => "all"} , {"users[]" => "greg"} ]
- #
- def fields_for_params(params, options = {})
- namespace = options[:namespace]
- except = options[:except].is_a?(Array) ? options[:except] : [options[:except]]
-
- params.map do |k, v|
- next if namespace.nil? && %w(controller action commit utf8).include?(k.to_s)
- next if except.map(&:to_s).include?(k.to_s)
-
- if namespace
- k = "#{namespace}[#{k}]"
- end
-
- case v
- when String
- { k => v }
- when Symbol
- { k => v.to_s }
- when Hash
- fields_for_params(v, :namespace => k)
- when Array
- v.map do |v|
- { "#{k}[]" => v }
- end
- when nil
- { k => '' }
- when TrueClass,FalseClass
- { k => v }
- else
- raise "I don't know what to do with #{v.class} params: #{v.inspect}"
- end
- end.flatten.compact
- end
- end
- end
-end
diff --git a/lib/active_admin/view_helpers/flash_helper.rb b/lib/active_admin/view_helpers/flash_helper.rb
deleted file mode 100644
index 868888e9af3..00000000000
--- a/lib/active_admin/view_helpers/flash_helper.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module ActiveAdmin
- module ViewHelpers
- module FlashHelper
-
- # Returns all the flash keys to display in any Active Admin view.
- # This method removes the :timedout key that Devise uses by default
- def active_admin_flash_messages
- @active_admin_flash_messages ||= flash.to_hash.except(:timedout)
- end
-
- end
- end
-end
diff --git a/lib/active_admin/view_helpers/form_helper.rb b/lib/active_admin/view_helpers/form_helper.rb
deleted file mode 100644
index 38c6f65f033..00000000000
--- a/lib/active_admin/view_helpers/form_helper.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-module ActiveAdmin
- module ViewHelpers
- module FormHelper
-
- def active_admin_form_for(resource, options = {}, &block)
- options = Marshal.load( Marshal.dump(options) )
- options[:builder] ||= ActiveAdmin::FormBuilder
-
- if ! options.fetch(:decorate, true)
- resource = resource.model
- end
-
- semantic_form_for resource, options, &block
- end
-
- def hidden_field_tags_for(params, options={})
- fields_for_params(params, options).map do |kv|
- k, v = kv.first
- hidden_field_tag k, v, :id => sanitize_to_id("hidden_active_admin_#{k}")
- end.join("\n").html_safe
- end
- end
- end
-end
diff --git a/lib/active_admin/view_helpers/icon_helper.rb b/lib/active_admin/view_helpers/icon_helper.rb
deleted file mode 100644
index da73f140f0d..00000000000
--- a/lib/active_admin/view_helpers/icon_helper.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-module ActiveAdmin
- module ViewHelpers
- module IconHelper
-
- # Render an icon from the Iconic icon set
- def icon(*args)
- ActiveAdmin::Iconic.icon(*args)
- end
-
- end
- end
-end
diff --git a/lib/active_admin/view_helpers/method_or_proc_helper.rb b/lib/active_admin/view_helpers/method_or_proc_helper.rb
index 75c47ce7634..6dc9a0156bd 100644
--- a/lib/active_admin/view_helpers/method_or_proc_helper.rb
+++ b/lib/active_admin/view_helpers/method_or_proc_helper.rb
@@ -1,4 +1,8 @@
+# frozen_string_literal: true
+# Utility methods for internal use.
+# @private
module MethodOrProcHelper
+ extend self
# This method will either call the symbol on self or instance_exec the Proc
# within self. Any args will be passed along to the method dispatch.
@@ -18,6 +22,8 @@ def call_method_or_exec_proc(symbol_or_proc, *args)
send(symbol_or_proc, *args)
when Proc
instance_exec(*args, &symbol_or_proc)
+ else
+ symbol_or_proc
end
end
@@ -25,7 +31,7 @@ def call_method_or_exec_proc(symbol_or_proc, *args)
# or instance_exec a proc passing in the object as the first parameter. This
# method wraps that pattern.
#
- # Calling with a Symbol:
+ # Calling with a String or Symbol:
#
# call_method_or_proc_on(@my_obj, :size) same as @my_obj.size
#
@@ -35,11 +41,11 @@ def call_method_or_exec_proc(symbol_or_proc, *args)
# call_method_or_proc_on(@my_obj, proc)
#
# By default, the Proc will be instance_exec'd within self. If you would rather
- # not instance exec, but just call the Proc, then pass along `:exec => false` in
+ # not instance exec, but just call the Proc, then pass along `exec: false` in
# the options hash.
#
# proc = Proc.new{|s| s.size }
- # call_method_or_proc_on(@my_obj, proc, :exec => false)
+ # call_method_or_proc_on(@my_obj, proc, exec: false)
#
# You can pass along any necessary arguments to the method / Proc as arguments. For
# example:
@@ -47,19 +53,21 @@ def call_method_or_exec_proc(symbol_or_proc, *args)
# call_method_or_proc_on(@my_obj, :find, 1) #=> @my_obj.find(1)
#
def call_method_or_proc_on(receiver, *args)
- options = { :exec => true }.merge(args.extract_options!)
+ options = { exec: true }.merge(args.extract_options!)
symbol_or_proc = args.shift
case symbol_or_proc
when Symbol, String
- receiver.send(symbol_or_proc.to_sym, *args)
+ receiver.public_send symbol_or_proc.to_sym, *args
when Proc
if options[:exec]
instance_exec(receiver, *args, &symbol_or_proc)
else
symbol_or_proc.call(receiver, *args)
end
+ else
+ symbol_or_proc
end
end
@@ -71,21 +79,21 @@ def render_or_call_method_or_proc_on(obj, string_symbol_or_proc, options = {})
case string_symbol_or_proc
when Symbol, Proc
call_method_or_proc_on(obj, string_symbol_or_proc, options)
- when String
+ else
string_symbol_or_proc
end
end
- # This method is different from the others in that it calls `instance_exec` on the reciever,
- # passing it the proc. This evaluates the proc in the context of the reciever, thus changing
+ # This method is different from the others in that it calls `instance_exec` on the receiver,
+ # passing it the proc. This evaluates the proc in the context of the receiver, thus changing
# what `self` means inside the proc.
def render_in_context(context, obj, *args)
- context ||= self # default to `self`
+ context = self if context.nil? # default to `self` only when nil
case obj
when Proc
- context.instance_exec *args, &obj
+ context.instance_exec(*args, &obj)
when Symbol
- context.send obj, *args
+ context.public_send obj, *args
else
obj
end
diff --git a/lib/active_admin/view_helpers/sidebar_helper.rb b/lib/active_admin/view_helpers/sidebar_helper.rb
deleted file mode 100644
index 866a3a6a4fb..00000000000
--- a/lib/active_admin/view_helpers/sidebar_helper.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module ActiveAdmin
- module ViewHelpers
- module SidebarHelper
-
- def skip_sidebar!
- @skip_sidebar = true
- end
-
- def skip_sidebar?
- @skip_sidebar == true
- end
-
- end
- end
-end
diff --git a/lib/active_admin/view_helpers/title_helper.rb b/lib/active_admin/view_helpers/title_helper.rb
deleted file mode 100644
index 1cc737d9845..00000000000
--- a/lib/active_admin/view_helpers/title_helper.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActiveAdmin
- module ViewHelpers
- module TitleHelper
-
- def title(_title)
- @page_title = _title
- end
-
- end
- end
-end
diff --git a/lib/active_admin/view_helpers/view_factory_helper.rb b/lib/active_admin/view_helpers/view_factory_helper.rb
deleted file mode 100644
index 06b85ed9b88..00000000000
--- a/lib/active_admin/view_helpers/view_factory_helper.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActiveAdmin
- module ViewHelpers
- module ViewFactoryHelper
-
- def view_factory
- active_admin_namespace.view_factory
- end
-
- end
- end
-end
diff --git a/lib/active_admin/views.rb b/lib/active_admin/views.rb
index daae1a56611..aa3851a8831 100644
--- a/lib/active_admin/views.rb
+++ b/lib/active_admin/views.rb
@@ -1,8 +1,9 @@
+# frozen_string_literal: true
module ActiveAdmin
module Views
# Loads all the classes in views/*.rb
- Dir[File.expand_path('../views', __FILE__) + "/**/*.rb"].sort.each{ |f| require f }
+ Dir[File.expand_path("views", __dir__) + "/**/*.rb"].sort.each { |f| require f }
end
end
diff --git a/lib/active_admin/views/action_items.rb b/lib/active_admin/views/action_items.rb
deleted file mode 100644
index 2f87a25b011..00000000000
--- a/lib/active_admin/views/action_items.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module ActiveAdmin
- module Views
-
- class ActionItems < ActiveAdmin::Component
-
- def build(action_items)
- action_items.each do |action_item|
- span :class => "action_item" do
- instance_eval(&action_item.block)
- end
- end
- end
-
- end
-
- end
-end
diff --git a/lib/active_admin/views/components/action_list_popover.rb b/lib/active_admin/views/components/action_list_popover.rb
deleted file mode 100644
index 449c5e472fb..00000000000
--- a/lib/active_admin/views/components/action_list_popover.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'active_admin/views/components/popover'
-
-module ActiveAdmin
- module Views
- # Build an ActionListPopover
- class ActionListPopover < ActiveAdmin::Views::Popover
- builder_method :action_list_popover
-
-
- def build(*args, &block)
- @contents = ul :class => "popover_contents"
-
- options = args.extract_options!
-
- super(options)
- end
-
- def action(title, url, *args)
- options = args.extract_options!
- within @contents do
- li do
- text_node link_to( title, url, options )
- end
- end
- end
-
- end
- end
-end
diff --git a/lib/active_admin/views/components/active_admin_form.rb b/lib/active_admin/views/components/active_admin_form.rb
new file mode 100644
index 00000000000..27c91e951ff
--- /dev/null
+++ b/lib/active_admin/views/components/active_admin_form.rb
@@ -0,0 +1,156 @@
+# frozen_string_literal: true
+module ActiveAdmin
+ module Views
+ class FormtasticProxy < ::Arbre::Rails::Forms::FormBuilderProxy
+ def split_string_on(string, match)
+ return "" unless string && match
+ part_1 = string.split(Regexp.new("#{match}\\z")).first
+ [part_1, match]
+ end
+
+ def opening_tag
+ @opening_tag || ""
+ end
+
+ def closing_tag
+ @closing_tag || ""
+ end
+
+ def to_s
+ opening_tag + children.to_s + closing_tag
+ end
+ end
+
+ class ActiveAdminForm < FormtasticProxy
+ builder_method :active_admin_form_for
+
+ def build(resource, options = {}, &block)
+ @resource = resource
+ options = options.deep_dup
+ options[:builder] ||= ActiveAdmin::FormBuilder
+ form_string = helpers.semantic_form_for(resource, options) do |f|
+ @form_builder = f
+ end
+
+ @opening_tag, @closing_tag = split_string_on(form_string, "")
+ instance_eval(&block) if block
+
+ # Rails sets multipart automatically if a file field is present,
+ # but the form tag has already been rendered before the block eval.
+ if multipart? && !@opening_tag.include?('multipart')
+ @opening_tag.sub!(/