├── .github ├── FUNDING.yml ├── SECURITY.md └── workflows │ ├── codeql.yml │ ├── cronjob.yml │ ├── deploy.yml │ ├── rubocop.yml │ ├── test-deploy.yml │ └── test.yml ├── .gitignore ├── .nojekyll ├── .rubocop.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bug_report_templates ├── test-ransack-scope-and-column-same-name.rb └── test-ransacker-arel-present-predicate.rb ├── docs ├── .gitignore ├── .nojekyll ├── babel.config.js ├── blog │ └── 2022-03-27-ransack-3.0.0.md ├── docs │ ├── getting-started │ │ ├── _category_.json │ │ ├── advanced-mode.md │ │ ├── configuration.md │ │ ├── search-matches.md │ │ ├── simple-mode.md │ │ ├── sorting.md │ │ └── using-predicates.md │ ├── going-further │ │ ├── _category_.json │ │ ├── acts-as-taggable-on.md │ │ ├── associations.md │ │ ├── custom-predicates.md │ │ ├── documentation.md │ │ ├── exporting-to-csv.md │ │ ├── external-guides.md │ │ ├── form-customisation.md │ │ ├── i18n.md │ │ ├── img │ │ │ └── create_release.png │ │ ├── merging-searches.md │ │ ├── other-notes.md │ │ ├── polymorphic-search.md │ │ ├── ransackers.md │ │ ├── release_process.md │ │ ├── saving-queries.md │ │ ├── searching-postgres.md │ │ └── wiki-contributors.md │ └── intro.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.module.css │ │ └── markdown-page.md ├── static │ ├── .nojekyll │ ├── img │ │ ├── docusaurus.png │ │ ├── favicon.ico │ │ ├── logo.svg │ │ ├── tutorial │ │ │ ├── docsVersionDropdown.png │ │ │ └── localeDropdown.png │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ └── undraw_docusaurus_tree.svg │ └── logo │ │ ├── ransack-h.png │ │ ├── ransack-h.svg │ │ ├── ransack-v.png │ │ ├── ransack-v.svg │ │ ├── ransack.png │ │ └── ransack.svg └── yarn.lock ├── lib ├── polyamorous │ ├── activerecord │ │ ├── join_association.rb │ │ ├── join_association_7_2.rb │ │ ├── join_dependency.rb │ │ └── reflection.rb │ ├── join.rb │ ├── polyamorous.rb │ ├── swapping_reflection_class.rb │ └── tree_node.rb ├── ransack.rb └── ransack │ ├── active_record.rb │ ├── adapters │ └── active_record │ │ ├── base.rb │ │ └── context.rb │ ├── configuration.rb │ ├── constants.rb │ ├── context.rb │ ├── helpers.rb │ ├── helpers │ ├── form_builder.rb │ └── form_helper.rb │ ├── invalid_search_error.rb │ ├── locale │ ├── ar.yml │ ├── az.yml │ ├── bg.yml │ ├── ca.yml │ ├── cs.yml │ ├── da.yml │ ├── de.yml │ ├── el.yml │ ├── en.yml │ ├── es.yml │ ├── fa.yml │ ├── fi.yml │ ├── fr.yml │ ├── hu.yml │ ├── id.yml │ ├── it.yml │ ├── ja.yml │ ├── ko.yml │ ├── nl.yml │ ├── pt-BR.yml │ ├── ro.yml │ ├── ru.yml │ ├── sk.yml │ ├── sv.yml │ ├── tr.yml │ ├── zh-CN.yml │ └── zh-TW.yml │ ├── naming.rb │ ├── nodes │ ├── attribute.rb │ ├── bindable.rb │ ├── condition.rb │ ├── grouping.rb │ ├── node.rb │ ├── sort.rb │ └── value.rb │ ├── predicate.rb │ ├── ransacker.rb │ ├── search.rb │ ├── translate.rb │ ├── version.rb │ └── visitor.rb ├── ransack.gemspec └── spec ├── blueprints ├── articles.rb ├── comments.rb ├── notes.rb ├── people.rb └── tags.rb ├── console.rb ├── helpers ├── polyamorous_helper.rb └── ransack_helper.rb ├── polyamorous ├── activerecord_compatibility_spec.rb ├── join_association_spec.rb ├── join_dependency_spec.rb └── join_spec.rb ├── ransack ├── adapters │ └── active_record │ │ ├── base_spec.rb │ │ └── context_spec.rb ├── configuration_spec.rb ├── helpers │ ├── form_builder_spec.rb │ └── form_helper_spec.rb ├── nodes │ ├── condition_spec.rb │ ├── grouping_spec.rb │ └── value_spec.rb ├── predicate_spec.rb ├── search_spec.rb └── translate_spec.rb ├── spec_helper.rb └── support ├── en.yml └── schema.rb /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | tidelift: rubygems/ransack 4 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | At the moment, only the latest major.minor release stream is supported with 6 | security updates. 7 | 8 | ## Reporting a Vulnerability 9 | 10 | Please use the Tidelift security contact to [report a security 11 | vulnerability](https://tidelift.com/security). Tidelift will coordinate the fix 12 | and disclosure. 13 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '43 11 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'ruby' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.github/workflows/cronjob.yml: -------------------------------------------------------------------------------- 1 | name: cronjob 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | jobs: 8 | sqlite3: 9 | runs-on: ubuntu-22.04 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | ruby: 14 | - 3.2.2 15 | env: 16 | DB: sqlite3 17 | RAILS: main 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up Ruby 21 | uses: ruby/setup-ruby@v1 22 | with: 23 | ruby-version: ${{ matrix.ruby }} 24 | - name: Install dependencies 25 | run: bundle install 26 | - name: Run tests 27 | run: bundle exec rspec 28 | 29 | mysql: 30 | runs-on: ubuntu-22.04 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | ruby: 35 | - 3.2.2 36 | env: 37 | DB: mysql 38 | RAILS: main 39 | MYSQL_USERNAME: root 40 | MYSQL_PASSWORD: root 41 | steps: 42 | - uses: actions/checkout@v2 43 | - name: Set up Ruby 44 | uses: ruby/setup-ruby@v1 45 | with: 46 | ruby-version: ${{ matrix.ruby }} 47 | - name: Startup MySQL 48 | run: | 49 | sudo systemctl start mysql.service 50 | - name: Setup databases 51 | run: | 52 | mysql --user=root --password=root --host=127.0.0.1 -e 'create database ransack collate utf8_general_ci;'; 53 | mysql --user=root --password=root --host=127.0.0.1 -e 'use ransack;show variables like "%character%";show variables like "%collation%";'; 54 | - name: Install dependencies 55 | run: bundle install 56 | - name: Run tests 57 | run: bundle exec rspec 58 | 59 | postgis: 60 | runs-on: ubuntu-22.04 61 | strategy: 62 | fail-fast: false 63 | matrix: 64 | ruby: 65 | - 3.2.2 66 | env: 67 | DB: postgis 68 | RAILS: main 69 | DATABASE_USERNAME: postgres 70 | DATABASE_PASSWORD: postgres 71 | DATABASE_HOST: 127.0.0.1 72 | services: 73 | postgres: 74 | image: postgres 75 | ports: 76 | - 5432:5432 77 | env: 78 | POSTGRES_PASSWORD: postgres 79 | POSTGRES_HOST_AUTH_METHOD: trust 80 | # Set health checks to wait until postgres has started 81 | options: >- 82 | --health-cmd pg_isready 83 | --health-interval 10s 84 | --health-timeout 5s 85 | --health-retries 5 86 | 87 | steps: 88 | - uses: actions/checkout@v2 89 | - name: Set up Ruby 90 | uses: ruby/setup-ruby@v1 91 | with: 92 | ruby-version: ${{ matrix.ruby }} 93 | - name: Setup databases 94 | run: | 95 | psql -h localhost -p 5432 -W postgres -c 'create database ransack;' -U postgres; 96 | - name: Install dependencies 97 | run: bundle install 98 | - name: Run tests 99 | run: bundle exec rspec 100 | 101 | postgres: 102 | runs-on: ubuntu-22.04 103 | strategy: 104 | fail-fast: false 105 | matrix: 106 | ruby: 107 | - 3.2.2 108 | env: 109 | DB: postgres 110 | RAILS: main 111 | DATABASE_USERNAME: postgres 112 | DATABASE_PASSWORD: postgres 113 | DATABASE_HOST: 127.0.0.1 114 | services: 115 | postgres: 116 | image: postgres 117 | ports: 118 | - 5432:5432 119 | env: 120 | POSTGRES_PASSWORD: postgres 121 | POSTGRES_HOST_AUTH_METHOD: trust 122 | # Set health checks to wait until postgres has started 123 | options: >- 124 | --health-cmd pg_isready 125 | --health-interval 10s 126 | --health-timeout 5s 127 | --health-retries 5 128 | 129 | steps: 130 | - uses: actions/checkout@v2 131 | - name: Set up Ruby 132 | uses: ruby/setup-ruby@v1 133 | with: 134 | ruby-version: ${{ matrix.ruby }} 135 | - name: Setup databases 136 | run: | 137 | psql -h localhost -p 5432 -W postgres -c 'create database ransack;' -U postgres; 138 | - name: Install dependencies 139 | run: bundle install 140 | - name: Run tests 141 | run: bundle exec rspec 142 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | name: Deploy to GitHub Pages 11 | 12 | runs-on: ubuntu-22.04 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: 16 20 | cache: yarn 21 | cache-dependency-path: docs/yarn.lock 22 | 23 | - name: Install dependencies 24 | run: yarn install --frozen-lockfile 25 | working-directory: docs 26 | 27 | - name: Build website 28 | run: yarn build 29 | working-directory: docs 30 | 31 | - name: Deploy to GitHub Pages 32 | uses: peaceiris/actions-gh-pages@v3 33 | with: 34 | github_token: ${{ secrets.GITHUB_TOKEN }} 35 | publish_dir: docs/build 36 | -------------------------------------------------------------------------------- /.github/workflows/rubocop.yml: -------------------------------------------------------------------------------- 1 | name: rubocop 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-22.04 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Ruby 14 | uses: ruby/setup-ruby@v1 15 | with: 16 | ruby-version: 3.2.2 17 | - name: Install gems 18 | run: bundle install --jobs 4 --retry 3 19 | - name: Run RuboCop 20 | run: bundle exec rubocop --parallel 21 | -------------------------------------------------------------------------------- /.github/workflows/test-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Test deploy to GitHub Pages 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test-deploy: 10 | name: Test deploy to GitHub Pages 11 | 12 | runs-on: ubuntu-22.04 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: 16 20 | cache: yarn 21 | cache-dependency-path: docs/yarn.lock 22 | 23 | - name: Install dependencies 24 | run: yarn install --frozen-lockfile 25 | working-directory: docs 26 | 27 | - name: Test build website 28 | run: yarn build 29 | working-directory: docs 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | sqlite3: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | rails: 16 | - 8-0-stable 17 | - v7.2.2 18 | - v7.1.0 19 | ruby: 20 | - 3.2.2 21 | - 3.1.4 22 | exclude: 23 | - rails: 8-0-stable 24 | ruby: 3.1.4 25 | env: 26 | DB: sqlite3 27 | RAILS: ${{ matrix.rails }} 28 | steps: 29 | - uses: actions/checkout@v2 30 | - name: Set up Ruby 31 | uses: ruby/setup-ruby@v1 32 | with: 33 | ruby-version: ${{ matrix.ruby }} 34 | bundler-cache: true 35 | - name: Run tests 36 | run: bundle exec rspec 37 | 38 | mysql: 39 | runs-on: ubuntu-latest 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | rails: 44 | - 8-0-stable 45 | - 7-2-stable 46 | - v7.1.0 47 | ruby: 48 | - 3.2.2 49 | - 3.1.4 50 | exclude: 51 | - rails: 8-0-stable 52 | ruby: 3.1.4 53 | 54 | env: 55 | DB: mysql 56 | RAILS: ${{ matrix.rails }} 57 | MYSQL_USERNAME: root 58 | MYSQL_PASSWORD: root 59 | steps: 60 | - uses: actions/checkout@v2 61 | - name: Set up Ruby 62 | uses: ruby/setup-ruby@v1 63 | with: 64 | ruby-version: ${{ matrix.ruby }} 65 | bundler-cache: true 66 | - name: Startup MySQL 67 | run: | 68 | sudo systemctl start mysql.service 69 | - name: Setup databases 70 | run: | 71 | mysql --user=root --password=root --host=127.0.0.1 -e 'create database ransack collate utf8_general_ci;'; 72 | mysql --user=root --password=root --host=127.0.0.1 -e 'use ransack;show variables like "%character%";show variables like "%collation%";'; 73 | - name: Run tests 74 | run: bundle exec rspec 75 | 76 | postgis: 77 | runs-on: ubuntu-22.04 78 | strategy: 79 | fail-fast: false 80 | matrix: 81 | rails: 82 | - 7-2-stable 83 | - v7.1.0 84 | ruby: 85 | - 3.2.2 86 | - 3.1.4 87 | env: 88 | DB: postgis 89 | RAILS: ${{ matrix.rails }} 90 | DATABASE_USERNAME: postgres 91 | DATABASE_PASSWORD: postgres 92 | DATABASE_HOST: 127.0.0.1 93 | services: 94 | postgres: 95 | image: postgres 96 | ports: 97 | - 5432:5432 98 | env: 99 | POSTGRES_PASSWORD: postgres 100 | POSTGRES_HOST_AUTH_METHOD: trust 101 | # Set health checks to wait until postgres has started 102 | options: >- 103 | --health-cmd pg_isready 104 | --health-interval 10s 105 | --health-timeout 5s 106 | --health-retries 5 107 | 108 | steps: 109 | - uses: actions/checkout@v2 110 | - name: Set up Ruby 111 | uses: ruby/setup-ruby@v1 112 | with: 113 | ruby-version: ${{ matrix.ruby }} 114 | bundler-cache: true 115 | - name: Setup databases 116 | run: | 117 | psql -h localhost -p 5432 -W postgres -c 'create database ransack;' -U postgres; 118 | - name: Run tests 119 | run: bundle exec rspec 120 | 121 | postgres: 122 | runs-on: ubuntu-latest 123 | strategy: 124 | fail-fast: false 125 | matrix: 126 | rails: 127 | - 8-0-stable 128 | - 7-2-stable 129 | - v7.1.0 130 | ruby: 131 | - 3.2.2 132 | - 3.1.4 133 | exclude: 134 | - rails: 8-0-stable 135 | ruby: 3.1.4 136 | env: 137 | DB: postgres 138 | RAILS: ${{ matrix.rails }} 139 | DATABASE_USERNAME: postgres 140 | DATABASE_PASSWORD: postgres 141 | DATABASE_HOST: 127.0.0.1 142 | services: 143 | postgres: 144 | image: postgres 145 | ports: 146 | - 5432:5432 147 | env: 148 | POSTGRES_PASSWORD: postgres 149 | POSTGRES_HOST_AUTH_METHOD: trust 150 | # Set health checks to wait until postgres has started 151 | options: >- 152 | --health-cmd pg_isready 153 | --health-interval 10s 154 | --health-timeout 5s 155 | --health-retries 5 156 | 157 | steps: 158 | - uses: actions/checkout@v2 159 | - name: Set up Ruby 160 | uses: ruby/setup-ruby@v1 161 | with: 162 | ruby-version: ${{ matrix.ruby }} 163 | bundler-cache: true 164 | - name: Setup databases 165 | run: | 166 | psql -h localhost -p 5432 -W postgres -c 'create database ransack;' -U postgres; 167 | - name: Run tests 168 | run: bundle exec rspec 169 | 170 | bug-report-templates: 171 | runs-on: ubuntu-latest 172 | steps: 173 | - uses: actions/checkout@v2 174 | - name: Set up Ruby 175 | uses: ruby/setup-ruby@v1 176 | with: 177 | ruby-version: 3.2.2 178 | bundler-cache: true 179 | - name: Run bug report templates 180 | run: | 181 | ruby bug_report_templates/test-ransacker-arel-present-predicate.rb 182 | ruby bug_report_templates/test-ransack-scope-and-column-same-name.rb 183 | rm Gemfile Gemfile.lock 184 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | coverage/* 6 | .DS_Store 7 | .byebug_history 8 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activerecord-hackery/ransack/e06a27ded233166362fdd9e6ca3996cecbef8265/.nojekyll -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - rubocop-rspec 3 | 4 | AllCops: 5 | TargetRubyVersion: 3.0 6 | 7 | DisabledByDefault: true 8 | 9 | Exclude: 10 | - "docs/**/*" 11 | 12 | Layout/EmptyLineAfterMagicComment: 13 | Enabled: true 14 | 15 | Layout/EmptyLineBetweenDefs: 16 | Enabled: true 17 | 18 | Layout/EmptyLines: 19 | Enabled: true 20 | 21 | Layout/EmptyLinesAroundBlockBody: 22 | Enabled: true 23 | 24 | Layout/FirstArrayElementIndentation: 25 | EnforcedStyle: consistent 26 | 27 | Layout/SpaceAfterComma: 28 | Enabled: true 29 | 30 | Layout/SpaceInsideBlockBraces: 31 | Enabled: true 32 | 33 | Layout/SpaceInsideHashLiteralBraces: 34 | Enabled: true 35 | 36 | Layout/SpaceInsideParens: 37 | Enabled: true 38 | 39 | Layout/TrailingEmptyLines: 40 | Enabled: true 41 | 42 | RSpec/EmptyLineAfterFinalLet: 43 | Enabled: true 44 | 45 | Style/HashSyntax: 46 | Enabled: true 47 | 48 | Style/RedundantFileExtensionInRequire: 49 | Enabled: true 50 | 51 | Style/RedundantReturn: 52 | Enabled: true 53 | 54 | Style/SelfAssignment: 55 | Enabled: true 56 | 57 | Style/Semicolon: 58 | Enabled: true 59 | 60 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | 4 | gem 'rake' 5 | 6 | rails = ENV['RAILS'] || '7-2-stable' 7 | 8 | rails_version = case rails 9 | when /\// # A path 10 | File.read(File.join(rails, "RAILS_VERSION")) 11 | when /^v/ # A tagged version 12 | rails.gsub(/^v/, '') 13 | else 14 | rails 15 | end 16 | 17 | gem 'faker' 18 | if ::Gem::Version.new(rails_version) > ::Gem::Version.new('7.3') 19 | gem 'sqlite3', '>= 2.1' 20 | else 21 | gem 'sqlite3', '~> 1.4' 22 | end 23 | gem 'pg' 24 | gem 'activerecord-postgis-adapter' 25 | gem 'pry' 26 | gem 'byebug' 27 | 28 | case rails 29 | when /\// # A path 30 | gem 'activesupport', path: "#{rails}/activesupport" 31 | gem 'activemodel', path: "#{rails}/activemodel" 32 | gem 'activerecord', path: "#{rails}/activerecord", require: false 33 | gem 'actionpack', path: "#{rails}/actionpack" 34 | gem 'actionview', path: "#{rails}/actionview" 35 | when /^v/ # A tagged version 36 | git 'https://github.com/rails/rails.git', tag: rails do 37 | gem 'activesupport' 38 | gem 'activemodel' 39 | gem 'activerecord', require: false 40 | gem 'actionpack' 41 | end 42 | else 43 | git 'https://github.com/rails/rails.git', branch: rails do 44 | gem 'activesupport' 45 | gem 'activemodel' 46 | gem 'activerecord', require: false 47 | gem 'actionpack' 48 | end 49 | end 50 | gem 'mysql2' 51 | 52 | group :test do 53 | gem 'machinist', '~> 1.0.6' 54 | gem 'rspec' 55 | gem 'simplecov', require: false 56 | end 57 | 58 | gem 'rubocop', require: false 59 | gem 'rubocop-rspec', require: false 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Ernie Miller 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require 'rspec/core/rake_task' 3 | 4 | Bundler::GemHelper.install_tasks 5 | 6 | RSpec::Core::RakeTask.new(:spec) do |rspec| 7 | ENV['SPEC'] = 'spec/ransack/**/*_spec.rb' 8 | # With Rails 3, using `--backtrace` raises 'invalid option' when testing. 9 | # With Rails 4 and 5 it can be uncommented to see the backtrace: 10 | # 11 | # rspec.rspec_opts = ['--backtrace'] 12 | end 13 | 14 | task :default do 15 | Rake::Task["spec"].invoke 16 | end 17 | 18 | desc "Open an irb session with Ransack and the sample data used in specs" 19 | task :console do 20 | require 'pry' 21 | require File.expand_path('../spec/console.rb', __FILE__) 22 | ARGV.clear 23 | Pry.start 24 | end 25 | -------------------------------------------------------------------------------- /bug_report_templates/test-ransack-scope-and-column-same-name.rb: -------------------------------------------------------------------------------- 1 | # test-ransack-scope-and-column-same-name.rb 2 | 3 | # This is a stand-alone test case. 4 | 5 | # Run it in your console with: `ruby test-ransack-scope-and-column-same-name.rb` 6 | 7 | # If you change the gem dependencies, run it with: 8 | # `rm gemfile* && ruby test-ransack-scope-and-column-same-name.rb` 9 | 10 | unless File.exist?('Gemfile') 11 | File.write('Gemfile', <<-GEMFILE) 12 | source 'https://rubygems.org' 13 | 14 | # Rails master 15 | gem 'rails', github: 'rails/rails', branch: '7-1-stable' 16 | 17 | # Rails last release 18 | # gem 'rails' 19 | 20 | gem 'sqlite3' 21 | gem 'ransack', github: 'activerecord-hackery/ransack' 22 | GEMFILE 23 | 24 | system 'bundle install' 25 | end 26 | 27 | require 'bundler' 28 | Bundler.setup(:default) 29 | 30 | require 'active_record' 31 | require 'minitest/autorun' 32 | require 'logger' 33 | require 'ransack' 34 | 35 | # This connection will do for database-independent bug reports. 36 | ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') 37 | ActiveRecord::Base.logger = Logger.new(STDOUT) 38 | 39 | # Display versions. 40 | message = "Running test case with Ruby #{RUBY_VERSION}, Active Record #{ 41 | ::ActiveRecord::VERSION::STRING}, Arel #{Arel::VERSION} and #{ 42 | ::ActiveRecord::Base.connection.adapter_name}" 43 | line = '=' * message.length 44 | puts line, message, line 45 | 46 | ActiveRecord::Schema.define do 47 | create_table :users, force: true do |t| 48 | t.boolean :active, null: false, default: true 49 | end 50 | end 51 | 52 | class User < ActiveRecord::Base 53 | scope :activated, -> (boolean = true) { where(active: boolean) } 54 | 55 | private 56 | 57 | def self.ransackable_scopes(auth_object = nil) 58 | %i(activated) 59 | end 60 | end 61 | 62 | class BugTest < Minitest::Test 63 | def test_activated_scope_equals_true 64 | sql = User.ransack({ activated: true }).result.to_sql 65 | puts sql 66 | assert_equal( 67 | "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"active\" = 1", sql 68 | ) 69 | end 70 | 71 | def test_activated_scope_equals_false 72 | sql = User.ransack({ activated: false }).result.to_sql 73 | puts sql 74 | assert_equal( 75 | "SELECT \"users\".* FROM \"users\"", sql 76 | ) 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /bug_report_templates/test-ransacker-arel-present-predicate.rb: -------------------------------------------------------------------------------- 1 | # test-ransacker-arel-present-predicate.rb 2 | 3 | # Run it in your console with: `ruby test-ransacker-arel-present-predicate.rb` 4 | 5 | # If you change the gem dependencies, run it with: 6 | # `rm gemfile* && ruby test-ransacker-arel-present-predicate.rb` 7 | 8 | unless File.exist?('Gemfile') 9 | File.write('Gemfile', <<-GEMFILE) 10 | source 'https://rubygems.org' 11 | 12 | # Rails master 13 | gem 'rails', github: 'rails/rails', branch: '7-1-stable' 14 | 15 | # Rails last release 16 | # gem 'rails' 17 | 18 | gem 'sqlite3' 19 | gem 'ransack', github: 'activerecord-hackery/ransack' 20 | GEMFILE 21 | 22 | system 'bundle install' 23 | end 24 | 25 | require 'bundler' 26 | Bundler.setup(:default) 27 | 28 | require 'active_record' 29 | require 'minitest/autorun' 30 | require 'logger' 31 | require 'ransack' 32 | 33 | # This connection will do for database-independent bug reports. 34 | ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') 35 | ActiveRecord::Base.logger = Logger.new(STDOUT) 36 | 37 | # Display versions. 38 | message = "Running test case with Ruby #{RUBY_VERSION}, Active Record #{ 39 | ::ActiveRecord::VERSION::STRING}, Arel #{Arel::VERSION} and #{ 40 | ::ActiveRecord::Base.connection.adapter_name}" 41 | line = '=' * message.length 42 | puts line, message, line 43 | 44 | ActiveRecord::Schema.define do 45 | create_table :projects, force: true do |t| 46 | t.string :name 47 | t.string :number 48 | end 49 | end 50 | 51 | class Project < ActiveRecord::Base 52 | ransacker :name do 53 | Arel.sql('projects.name') 54 | end 55 | 56 | ransacker :number do |parent| 57 | parent.table[:number] 58 | end 59 | 60 | def self.ransackable_attributes(_auth_object = nil) 61 | ["name", "number"] 62 | end 63 | end 64 | 65 | class BugTest < Minitest::Test 66 | def test_ransackers 67 | sql = Project.ransack({ number_present: 1 }).result.to_sql 68 | puts sql 69 | assert_equal "SELECT \"projects\".* FROM \"projects\" WHERE (\"projects\".\"number\" IS NOT NULL AND \"projects\".\"number\" != '')", sql 70 | 71 | sql = Project.ransack({ name_present: 1 }).result.to_sql 72 | puts sql 73 | assert_equal "SELECT \"projects\".* FROM \"projects\" WHERE (projects.name IS NOT NULL AND projects.name != '')", sql 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | yarn-debug.log* 19 | yarn-error.log* 20 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activerecord-hackery/ransack/e06a27ded233166362fdd9e6ca3996cecbef8265/docs/.nojekyll -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/blog/2022-03-27-ransack-3.0.0.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: ransack-3-0-0 3 | title: Ransack 3.0.0 4 | authors: 5 | name: Sean Carroll 6 | title: Ransack Core Team 7 | tags: [ransack, release] 8 | --- 9 | 10 | Ransack has been a part of many Rubyists toolboxes for years and 3.0.0 is a major release. We have a number of new features and one breaking change. As part of 3.0.0, we decided to launch this documentation website, merging in the Wiki and the content from the README. 11 | 12 | With 3.0.0 we are hoping to re-energise the community, we need help on closing out old issues, refactoring the codebase and even some design work. 13 | 14 | I also wanted to let you know that Ernie Miller (creator of Ransack) has decided to leave the project completely, he has this message for the community: 15 | 16 | > While my own personal development efforts have been spent elsewhere as of late, I'm keenly aware of how many people still depend on some of the software I originally wrote all those years ago. 17 | 18 | > That's why I'm grateful to be able to step away from the ActiveRecord Hackery organization (and, specifically, maintenance of Ransack) without impacting those users. I'm thankful that Sean, David, Greg, and others will continue to support users, and wish them the best as they move forward without me! 19 | 20 | Please join me in thanking Ernie for bringing Ransack to life, I personally think it is one of the most amazing Rails libraries out there. 21 | -------------------------------------------------------------------------------- /docs/docs/getting-started/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Getting started", 3 | "position": 2 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/getting-started/advanced-mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | title: Advanced Mode 4 | --- 5 | 6 | 7 | "Advanced" searches Rails's nested attributes functionality in order to 8 | generate complex queries with nested AND/OR groupings, etc. This takes a bit 9 | more work but can generate some pretty cool search interfaces that put a lot of 10 | power in the hands of your users. 11 | 12 | A notable drawback with these searches is 13 | that the increased size of the parameter string will typically force you to use 14 | the HTTP POST method instead of GET. 15 | 16 | 17 | ## Tweak your routes 18 | 19 | ```ruby 20 | resources :people do 21 | collection do 22 | match 'search' => 'people#search', via: [:get, :post], as: :search 23 | end 24 | end 25 | ``` 26 | 27 | ## Add a controller action 28 | 29 | ```ruby 30 | def search 31 | index 32 | render :index 33 | end 34 | ``` 35 | 36 | ## Update your form 37 | 38 | ```erb 39 | <%= search_form_for @q, url: search_people_path, 40 | html: { method: :post } do |f| %> 41 | ``` 42 | 43 | Once you've done so, you can make use of the helpers in [Ransack::Helpers::FormBuilder](https://github.com/activerecord-hackery/ransack/blob/main/lib/ransack/helpers/form_builder.rb) to 44 | construct much more complex search forms, such as the one on the 45 | [demo app](http://ransack-demo.herokuapp.com/users/advanced_search) 46 | (source code [here](https://github.com/activerecord-hackery/ransack_demo)). 47 | -------------------------------------------------------------------------------- /docs/docs/getting-started/configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: Configuration 4 | --- 5 | 6 | 7 | 8 | Ransack may be easily configured. The best place to put configuration is in an initializer file at `config/initializers/ransack.rb`, containing code such as: 9 | 10 | ```ruby 11 | Ransack.configure do |config| 12 | 13 | # Change default search parameter key name. 14 | # Default key name is :q 15 | config.search_key = :query 16 | 17 | # Raise errors if a query contains an unknown predicate or attribute. 18 | # Default is true (do not raise error on unknown conditions). 19 | config.ignore_unknown_conditions = false 20 | 21 | # Globally display sort links without the order indicator arrow. 22 | # Default is false (sort order indicators are displayed). 23 | # This can also be configured individually in each sort link (see the README). 24 | config.hide_sort_order_indicators = true 25 | 26 | end 27 | ``` 28 | 29 | ## Custom search parameter key name 30 | 31 | Sometimes there are situations when the default search parameter name cannot be used, for instance, 32 | if there are two searches on one page. Another name may be set using the `search_key` option in the `ransack` or `search` methods in the controller, and in the `@search_form_for` method in the view. 33 | 34 | ### In the controller 35 | 36 | ```ruby 37 | @search = Log.ransack(params[:log_search], search_key: :log_search) 38 | # or 39 | @search = Log.search(params[:log_search], search_key: :log_search) 40 | ``` 41 | 42 | ### In the view 43 | 44 | ```erb 45 | <%= f.search_form_for @search, as: :log_search %> 46 | <%= sort_link(@search) %> 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/docs/getting-started/search-matches.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Search Matchers 3 | --- 4 | 5 | ### Search Matchers 6 | 7 | List of all possible predicates 8 | 9 | 10 | | Predicate | Description | Notes | 11 | | ------------- | ------------- |-------- | 12 | | `*_eq` | equal | | 13 | | `*_not_eq` | not equal | | 14 | | `*_matches` | matches with `LIKE` | e.g. `q[email_matches]=%@gmail.com`| 15 | | `*_does_not_match` | does not match with `LIKE` | | 16 | | `*_matches_any` | Matches any | | 17 | | `*_matches_all` | Matches all | | 18 | | `*_does_not_match_any` | Does not match any | | 19 | | `*_does_not_match_all` | Does not match all | | 20 | | `*_lt` | less than | | 21 | | `*_lteq` | less than or equal | | 22 | | `*_gt` | greater than | | 23 | | `*_gteq` | greater than or equal | | 24 | | `*_present` | not null and not empty | Only compatible with string columns. Example: `q[name_present]=1` (SQL: `col is not null AND col != ''`) | 25 | | `*_blank` | is null or empty. | (SQL: `col is null OR col = ''`) | 26 | | `*_null` | is null | | 27 | | `*_not_null` | is not null | | 28 | | `*_in` | match any values in array | e.g. `q[name_in][]=Alice&q[name_in][]=Bob` | 29 | | `*_not_in` | match none of values in array | | 30 | | `*_lt_any` | Less than any | SQL: `col < value1 OR col < value2` | 31 | | `*_lteq_any` | Less than or equal to any | | 32 | | `*_gt_any` | Greater than any | | 33 | | `*_gteq_any` | Greater than or equal to any | | 34 | | `*_lt_all` | Less than all | SQL: `col < value1 AND col < value2` | 35 | | `*_lteq_all` | Less than or equal to all | | 36 | | `*_gt_all` | Greater than all | | 37 | | `*_gteq_all` | Greater than or equal to all | | 38 | | `*_not_eq_all` | none of values in a set | | 39 | | `*_start` | Starts with | SQL: `col LIKE 'value%'` | 40 | | `*_not_start` | Does not start with | | 41 | | `*_start_any` | Starts with any of | | 42 | | `*_start_all` | Starts with all of | | 43 | | `*_not_start_any` | Does not start with any of | | 44 | | `*_not_start_all` | Does not start with all of | | 45 | | `*_end` | Ends with | SQL: `col LIKE '%value'` | 46 | | `*_not_end` | Does not end with | | 47 | | `*_end_any` | Ends with any of | | 48 | | `*_end_all` | Ends with all of | | 49 | | `*_not_end_any` | | | 50 | | `*_not_end_all` | | | 51 | | `*_cont` | Contains value | uses `LIKE` | 52 | | `*_cont_any` | Contains any of | | 53 | | `*_cont_all` | Contains all of | | 54 | | `*_not_cont` | Does not contain | 55 | | `*_not_cont_any` | Does not contain any of | | 56 | | `*_not_cont_all` | Does not contain all of | | 57 | | `*_i_cont` | Contains value with case insensitive | uses `ILIKE` | 58 | | `*_i_cont_any` | Contains any of values with case insensitive | | 59 | | `*_i_cont_all` | Contains all of values with case insensitive | | 60 | | `*_not_i_cont` | Does not contain with case insensitive | 61 | | `*_not_i_cont_any` | Does not contain any of values with case insensitive | | 62 | | `*_not_i_cont_all` | Does not contain all of values with case insensitive | | 63 | | `*_true` | is true | | 64 | | `*_false` | is false | | 65 | 66 | 67 | See full list: https://github.com/activerecord-hackery/ransack/blob/main/lib/ransack/locale/en.yml#L16 68 | -------------------------------------------------------------------------------- /docs/docs/getting-started/sorting.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sorting 3 | --- 4 | 5 | 6 | # Sorting 7 | 8 | ## Sorting in the View 9 | 10 | You can add a form to capture sorting and filtering options together. 11 | 12 | ```erb 13 | # app/views/posts/index.html.erb 14 | 15 | <%= search_form_for @q do |f| %> 16 | <%= f.label :title_cont %> 17 | <%= f.search_field :title_cont %> 18 | 19 | <%= f.submit "Search" %> 20 | <% end %> 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | <% @posts.each do |post| %> 33 | 34 | 35 | 36 | 37 | 38 | <% end %> 39 | 40 |
<%= sort_link(@q, :title, "Title") %><%= sort_link(@q, :category, "Category") %><%= sort_link(@q, :created_at, "Created at") %>
<%= post.title %><%= post.category %><%= post.created_at.to_s(:long) %>
41 | ``` 42 | 43 | ## Sorting in the Controller 44 | 45 | To specify a default search sort field + order in the controller `index`: 46 | 47 | ```ruby 48 | # app/controllers/posts_controller.rb 49 | class PostsController < ActionController::Base 50 | def index 51 | @q = Post.ransack(params[:q]) 52 | @q.sorts = 'title asc' if @q.sorts.empty? 53 | 54 | @posts = @q.result(distinct: true) 55 | end 56 | end 57 | ``` 58 | 59 | Multiple sorts can be set by: 60 | 61 | ```ruby 62 | # app/controllers/posts_controller.rb 63 | class PostsController < ActionController::Base 64 | def index 65 | @q = Post.ransack(params[:q]) 66 | @q.sorts = ['title asc', 'created_at desc'] if @q.sorts.empty? 67 | 68 | @posts = @q.result(distinct: true) 69 | end 70 | end 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/docs/going-further/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Going further", 3 | "position": 2 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/going-further/acts-as-taggable-on.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Acts-as-taggable-on 3 | sidebar_position: 13 4 | --- 5 | 6 | ## Using Acts As Taggable On 7 | 8 | If you have an `ActiveRecord` model and you're using [acts-as-taggable-on](https://github.com/mbleigh/acts-as-taggable-on), 9 | chances are you might want to search on tagged fields. Follow the instructions to install the gem and then set up your project files. 10 | 11 | ### Configure the model 12 | 13 | `app/models/tasks.rb` 14 | 15 | You can call the tagging field anything you like, it just needs to be plural. No migration is needed as this is stored in the internal ActsAsTaggable tables (`tags` and `taggings`). 16 | 17 | ```ruby 18 | class Task < ApplicationRecord 19 | acts_as_taggable_on :projects 20 | end 21 | ``` 22 | 23 | ### Controller 24 | 25 | Add a field to strong params in the controller. Use the singular name with `_list`. 26 | 27 | `app/controllers/tasks_controller.rb` 28 | 29 | ```ruby 30 | def strong_params 31 | params 32 | .require(:tasks) 33 | .permit(:task, :example_field, :project_list) 34 | ``` 35 | 36 | ### Form 37 | 38 | We need to `send` the tag fieldname to our model, also using the singular naming. 39 | 40 | ```erb 41 |
42 | <%= f.label :project_list %> 43 | <%= f.text_field :project_list, value: @task.send(:project_list).to_s %> 44 |
45 | ``` 46 | 47 | Now we can collect our data via the form, with tags separated by commas. 48 | 49 | ## Ransack Search 50 | 51 | Imagine you have the following two instances of `Task`: 52 | 53 | ```ruby 54 | { id: 1, name: 'Clean up my room', projects: [ 'Home', 'Personal' ] } 55 | { id: 2, name: 'Complete math exercises', projects: [ 'Homework', 'Study' ] } 56 | ``` 57 | 58 | When you're writing a `Ransack` search form, you can choose any of the following options: 59 | 60 | ```erb 61 | <%= search_form_for @search do |f| %> 62 | <%= f.text_field :projects_name_in %> 63 | <%= f.text_field :projects_name_eq %> 64 | <%= f.text_field :projects_name_cont %> 65 | <% end %> 66 | ``` 67 | 68 | ### Option A - Match keys exactly 69 | 70 | Option `A` will match keys exactly. This is the solution to choose if you want to distinguish 'Home' from 'Homework': searching for 'Home' will return just the `Task` with id 1. It also allows searching for more than one tag at once (comma separated): 71 | - `Home, Personal` will return task 1 72 | - `Home, Homework` will return task 1 and 2 73 | 74 | ### Option B - match key combinations 75 | 76 | Option `B` will match all keys exactly. This is the solution if you wanna search for specific combinations of tags: 77 | - `Home` will return nothing, as there is no Task with just the `Home` tag 78 | - `Home, Personal` will return task 1 79 | 80 | ### Option C - match substrings 81 | 82 | Option `C` is used to match substrings. This is useful when you don't care for the exact tag, but only for part of it: 83 | - `Home` will return task 1 and 2 (`/Home/` matches both `"Home"` and `"Homework"`) 84 | 85 | ### Option D - select from a list of tags 86 | 87 | In Option `D` we allow the user to select a list of valid tags and then search against them. We use the plural name here. 88 | 89 | ```erb 90 |
91 | <%= f.label :projects_name, 'Project' %> 92 | <%= f.select :projects_name_in, ActsAsTaggableOn::Tag.distinct.order(:name).pluck(:name) %> 93 |
94 | ``` 95 | 96 | ## Multitenancy 97 | 98 | ActsAsTaggableOn allows scoping of tags based on another field on the model. Suppose we have a `language` field on the model, as an effective second level key. We would adjust our model to look like this: 99 | 100 | ```ruby 101 | class Task < ApplicationRecord 102 | acts_as_taggable_on :projects 103 | acts_as_taggable_tenant :language 104 | end 105 | ``` 106 | 107 | The Ransack search is then filtered using the `for_tenant` method 108 | 109 | ```erb 110 |
111 | <%= f.label :projects_name, 'Project' %> 112 | <%= f.select :projects_name_in, ActsAsTaggableOn::Tag.for_tenant('fr').distinct.order(:name).pluck(:name) %> 113 |
114 | 115 | -------------------------------------------------------------------------------- /docs/docs/going-further/associations.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | title: Associations 4 | --- 5 | 6 | ### Associations 7 | 8 | You can easily use Ransack to search for objects in `has_many` and `belongs_to` 9 | associations. 10 | 11 | Given these associations... 12 | 13 | ```ruby 14 | class Employee < ActiveRecord::Base 15 | belongs_to :supervisor 16 | 17 | # has attributes first_name:string and last_name:string 18 | end 19 | 20 | class Department < ActiveRecord::Base 21 | has_many :supervisors 22 | 23 | # has attribute title:string 24 | end 25 | 26 | class Supervisor < ActiveRecord::Base 27 | belongs_to :department 28 | has_many :employees 29 | 30 | # has attribute last_name:string 31 | end 32 | ``` 33 | 34 | ... and a controller... 35 | 36 | ```ruby 37 | class SupervisorsController < ApplicationController 38 | def index 39 | @q = Supervisor.ransack(params[:q]) 40 | @supervisors = @q.result.includes(:department, :employees) 41 | end 42 | end 43 | ``` 44 | 45 | ... you might set up your form like this... 46 | 47 | ```erb 48 | <%= search_form_for @q do |f| %> 49 | <%= f.label :last_name_cont %> 50 | <%= f.search_field :last_name_cont %> 51 | 52 | <%= f.label :department_title_cont %> 53 | <%= f.search_field :department_title_cont %> 54 | 55 | <%= f.label :employees_first_name_or_employees_last_name_cont %> 56 | <%= f.search_field :employees_first_name_or_employees_last_name_cont %> 57 | 58 | <%= f.submit "search" %> 59 | <% end %> 60 | ... 61 | <%= content_tag :table do %> 62 | <%= content_tag :th, sort_link(@q, :last_name) %> 63 | <%= content_tag :th, sort_link(@q, :department_title) %> 64 | <%= content_tag :th, sort_link(@q, :employees_last_name) %> 65 | <% end %> 66 | ``` 67 | 68 | If you have trouble sorting on associations, try using an SQL string with the 69 | pluralized table (`'departments.title'`,`'employees.last_name'`) instead of the 70 | symbolized association (`:department_title)`, `:employees_last_name`). 71 | -------------------------------------------------------------------------------- /docs/docs/going-further/custom-predicates.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | title: Custom predicates 4 | --- 5 | 6 | If you'd like to add your own custom Ransack predicates: 7 | 8 | ```ruby 9 | # config/initializers/ransack.rb 10 | 11 | Ransack.configure do |config| 12 | config.add_predicate 'equals_diddly', # Name your predicate 13 | # What non-compound ARel predicate will it use? (eq, matches, etc) 14 | arel_predicate: 'eq', 15 | # Format incoming values as you see fit. (Default: Don't do formatting) 16 | formatter: proc { |v| "#{v}-diddly" }, 17 | # Validate a value. An "invalid" value won't be used in a search. 18 | # Below is default. 19 | validator: proc { |v| v.present? }, 20 | # Should compounds be created? Will use the compound (any/all) version 21 | # of the arel_predicate to create a corresponding any/all version of 22 | # your predicate. (Default: true) 23 | compounds: true, 24 | # Force a specific column type for type-casting of supplied values. 25 | # (Default: use type from DB column) 26 | type: :string, 27 | # Use LOWER(column on database). 28 | # (Default: false) 29 | case_insensitive: true 30 | end 31 | ``` 32 | You can check all Arel predicates [here](https://github.com/rails/rails/blob/main/activerecord/lib/arel/predications.rb). 33 | 34 | If Arel does not have the predicate you are looking for, consider monkey patching it: 35 | 36 | ```ruby 37 | # config/initializers/ransack.rb 38 | 39 | module Arel 40 | module Predications 41 | def gteq_or_null(other) 42 | left = gteq(other) 43 | right = eq(nil) 44 | left.or(right) 45 | end 46 | end 47 | end 48 | 49 | Ransack.configure do |config| 50 | config.add_predicate 'gteq_or_null', arel_predicate: 'gteq_or_null' 51 | end 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/docs/going-further/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 11 3 | title: Documentation 4 | --- 5 | 6 | Ransack uses [Docusaurus](https://docusaurus.io/) for documentation. To contribute to the docs simply use the "Edit this page" link from any page to directly edit, or else pull the repo and edit locally. 7 | 8 | ### Local Development 9 | 10 | Switch to docs folder 11 | 12 | ``` 13 | cd docs 14 | ``` 15 | 16 | Install docusaurus and other dependencies 17 | 18 | ``` 19 | yarn install 20 | ``` 21 | 22 | 23 | Start a local development server and open up a browser window. Most changes are reflected live without having to restart the server. 24 | 25 | ``` 26 | yarn start 27 | ``` 28 | 29 | ### Build 30 | 31 | ``` 32 | yarn build 33 | ``` 34 | 35 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 36 | 37 | ### Deployment 38 | 39 | Using SSH: 40 | 41 | ``` 42 | USE_SSH=true yarn deploy 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/docs/going-further/exporting-to-csv.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | title: CSV Export 4 | --- 5 | 6 | Exporting to CSV 7 | 8 | Example downloading a csv file preserving ransack search, based on [this gist](https://gist.github.com/pama/adff25ed1f4b796ce088ea362a08e1c5) 9 | 10 | ```ruby title='index.html.erb' 11 |

Users

12 | 13 | <%= search_form_for @q, url: dashboard_index_path do |f| %> 14 | <%= f.label :name_cont %> 15 | <%= f.search_field :name_cont %> 16 | 17 | <%= f.submit %> 18 | <% end %> 19 | 20 | 25 | 26 | <% if params[:q] %> 27 | <%= link_to 'Export 1', dashboard_index_path({name: params[:q][:name_cont]}.merge({format: :csv})) %> 28 | <% else %> 29 | <%= link_to 'Export 2', dashboard_index_path(format: 'csv') %> 30 | <% end %> 31 | ``` 32 | 33 | ```ruby title='user.rb' 34 | require 'csv' 35 | 36 | class User < ApplicationRecord 37 | has_many :devices 38 | 39 | def self.get_csv(users) 40 | CSV.generate do |csv| 41 | csv << ["Name", "Devices"] 42 | 43 | users.each do |user| 44 | csv << [user.name, user.devices.map{|device| device.name}.join(', ')] 45 | end 46 | end 47 | end 48 | end 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/docs/going-further/external-guides.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 9 3 | title: External resources 4 | --- 5 | 6 | There is a plethora of material on Ransack around the internet. We've collected some here for your convenience. 7 | 8 | Do you want to have a blog post or other content on Ransack highlighted? Please just edit the page, add your content and a Pull Request will be sent to Ransack maintainers for approval. 9 | 10 | # Screencasts 11 | 12 | - [DriftingRuby: Ransack Search and Hotwire](https://www.driftingruby.com/episodes/ransack-search-and-hotwire) 13 | - [GoRails: Forum Series Part 6: Search with Ransack](https://gorails.com/episodes/forum-search-with-ransack) 14 | - [Railscast 370 - Ransack](http://railscasts.com/episodes/370-ransack) 15 | - [Search And Sort Ransack Associations With The Rails Ransack Gem | Ruby On Rails 6 Ransack Tutorial](https://www.youtube.com/watch?v=rtg-5EXwpbg) 16 | 17 | 18 | # Gems 19 | 20 | - [ActiveAdmin](https://activeadmin.info/) The Administration Framework for Rails **_uses Ransack internally_** 21 | - [Ransack Memory](https://github.com/richardrails/ransack_memory) Automatically save and load Ransack's filtered params into the Rail's session 22 | - [Mobility Ransack](https://github.com/shioyama/mobility-ransack) Search attributes translated by Mobility with Ransack. 23 | - [Ransack UI](https://github.com/ndbroadbent/ransack_ui) Framework for building a search UI with Ransack **_seems abandoned_** 24 | 25 | # Blogs 26 | 27 | - [Search And Sort In Ruby On Rails 6 With The Ransack Gem](https://deanin.com/blog/ransack/) 28 | - [Implement Ransack Gem in Ruby on Rails](https://www.botreetechnologies.com/blog/implementing-advanced-search-in-ruby-on-rails-with-ransack/) 29 | - [Searching and Sorting with Ransack](https://jaspercurry.medium.com/searching-and-sorting-on-rails-with-ransack-560e862e650a) 30 | - [How to Build Your Own ActiveAdmin Filters with Ransack](https://www.viget.com/articles/how-to-build-your-own-filters-with-ransack/) 31 | - [Avoid Ransack's N+1 Pitfall!](https://dev.to/husteadrobert/avoid-ransacks-n1-pitfall-33of) 32 | - [Filter and paging with Kaminari](https://gist.github.com/MyklClason/e4dc96fd0e009b7b3a9c84ddbb1e11d2) 33 | - [Pagination for Ransack Forms](https://nicholaide.github.io/ransack/2016/11/26/ransack-pagination.html) 34 | - [AJAX Search, Sort, Paginate with Ransack and Kaminari](https://techbrownbags.wordpress.com/2014/01/17/rails-ajax-search-sort-paginate-with-ransack-kaminari/) 35 | - [Searching with Ransack in Ruby on Rails](http://blog.magmalabs.io/2019/03/12/searching-with-ransack-in-ruby-on-rails.html) 36 | - [Role scopes with gem Ransack](https://blog.corsego.com/rolify-scopes) 37 | - [Searching and Sorting with Ransack](https://www.mintbit.com/blog/searching-and-sorting-with-ransack) 38 | - [Using custom scopes with Ransack gem in Rails](https://profilehunt.net/blog/using-custom-scopes-with-ransack-in-rails) 39 | - [Query Date Range With Ransack](https://lingceng.github.io/blog/2015/12/28/query-date-range-with-ransack/) 40 | - [ransack vs searchkick: Building a search feature in Rails](https://www.cookieshq.co.uk/posts/ransack-vs-searchkick-building-a-search-feature-in-rails) 41 | - [Using ransack and delegate in Rails](https://huangwenwei.com/blogs/using-ransack-and-delegate-in-rails) 42 | - [Using Ransack as a Search Engine](https://medium.com/@jelaniwoods/using-ransack-as-a-search-engine-92e002a68da) 43 | - [Advanced Search with Ransack](https://www.sitepoint.com/advanced-search-ransack/) 44 | - [Sort a table of records in Rails with Ransack](https://alankydd.wordpress.com/2012/03/12/sort-a-table-of-records-in-rails-with-ransack/) 45 | - [Ransack: Search with Multiple Checkboxes (Rails)](https://iamjosh.wordpress.com/2014/03/07/ransack-search-with-multiple-checkboxes/) 46 | - [Rails : Ransack : Sorting data by ratings](https://cbabhusal.wordpress.com/2017/01/03/rails-ransack-sorting-data-by-ratings/) 47 | - [Setting Up Rails 5 API Only App with ActiveAdmin enabled](https://rrott.com/blog/ror/rails-5-api-with-activeadmin-integration/) 48 | - [Ransack, the library formerly known as MetaSearch 2.0](https://ernie.io/2011/04/01/ransack-the-library-formerly-known-as-metasearch-2-0/) **_some Ransack history_** 49 | 50 | ## In French 51 | 52 | - [Faciliter les recherches avec Ransack](https://www.synbioz.com/blog/tech/faciliter-les-recherches-avec-ransack) 53 | 54 | ## In Vietnamese 55 | 56 | - [Ransack - công cụ tuyệt vời giúp tìm kiếm và sắp xếp dữ liệu đơn giản hơn 57 | ](https://nddblog.com/posts/ransack-cong-cu-tuyet-voi-giup-tim-kiem-va-sap-xep-du-lieu-don-gian-hon) 58 | -------------------------------------------------------------------------------- /docs/docs/going-further/form-customisation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | title: Form customisation 4 | --- 5 | 6 | Predicate and attribute labels in forms may be specified with I18n in a translation file (see the locale files in [Ransack::Locale](https://github.com/activerecord-hackery/ransack/tree/main/lib/ransack/locale) for more examples): 7 | 8 | ```yml 9 | # locales/en.yml 10 | en: 11 | ransack: 12 | asc: ascending 13 | desc: descending 14 | predicates: 15 | cont: contains 16 | not_cont: not contains 17 | start: starts with 18 | end: ends with 19 | gt: greater than 20 | lt: less than 21 | attributes: 22 | person: 23 | name: Full Name 24 | article: 25 | title: Article Title 26 | body: Main Content 27 | ``` 28 | The names of attribute fields may also be changed globally or under activerecord: 29 | 30 | ```yml 31 | # locales/en.yml 32 | en: 33 | attributes: 34 | model_name: 35 | model_field1: field name1 36 | model_field2: field name2 37 | activerecord: 38 | attributes: 39 | namespace/article: 40 | title: AR Namespaced Title 41 | namespace_article: 42 | title: Old Ransack Namespaced Title 43 | ``` 44 | 45 | To limit the predicates in the `predicate_select` form helper in a view template, pass an array of permitted predicates with `only`: 46 | 47 | ```erb 48 | <%= f.predicate_select only: %i(cont not_cont eq not_eq blank null) %> 49 | ``` 50 | 51 | Compound predicates (`_any` & `_all`) may be removed by passing the option `compounds: false`. 52 | 53 | ```erb 54 | <%= f.predicate_select compounds: false %> 55 | ``` 56 | 57 | Searchable attributes versus non-searchable ones may be specified as follows: 58 | 59 | ```ruby 60 | def self.ransackable_attributes(auth_object = nil) 61 | %w(searchable_attribute_1 searchable_attribute_2 ...) + _ransackers.keys 62 | end 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/docs/going-further/i18n.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: i18n 4 | --- 5 | 6 | # i18n and Ransack 7 | 8 | Ransack translation files are available in 9 | [Ransack::Locale](https://github.com/activerecord-hackery/ransack/tree/main/lib/ransack/locale). You may also be interested in one of the 10 | many translations for Ransack available at 11 | http://www.localeapp.com/projects/2999. 12 | 13 | Predicate and attribute translations in forms may be specified as follows (see 14 | the translation files in [Ransack::Locale](https://github.com/activerecord-hackery/ransack/tree/main/lib/ransack/locale) for more examples): 15 | 16 | locales/en.yml: 17 | ```yml 18 | en: 19 | ransack: 20 | asc: ascending 21 | desc: descending 22 | predicates: 23 | cont: contains 24 | not_cont: not contains 25 | start: starts with 26 | end: ends with 27 | gt: greater than 28 | lt: less than 29 | models: 30 | person: Passenger 31 | attributes: 32 | person: 33 | name: Full Name 34 | article: 35 | title: Article Title 36 | body: Main Content 37 | ``` 38 | 39 | Attribute names may also be changed globally, or under `activerecord`: 40 | 41 | ```yml 42 | en: 43 | attributes: 44 | model_name: 45 | model_field1: field name1 46 | model_field2: field name2 47 | activerecord: 48 | attributes: 49 | namespace/article: 50 | title: AR Namespaced Title 51 | namespace_article: 52 | title: Old Ransack Namespaced Title 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/docs/going-further/img/create_release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activerecord-hackery/ransack/e06a27ded233166362fdd9e6ca3996cecbef8265/docs/docs/going-further/img/create_release.png -------------------------------------------------------------------------------- /docs/docs/going-further/merging-searches.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | title: Merging searches 4 | --- 5 | 6 | To find records that match multiple searches, it's possible to merge all the ransack search conditions into an ActiveRecord relation to perform a single query. In order to avoid conflicts between joined table names it's necessary to set up a shared context to track table aliases used across all the conditions before initializing the searches: 7 | 8 | ```ruby 9 | shared_context = Ransack::Context.for(Person) 10 | 11 | search_parents = Person.ransack( 12 | { parent_name_eq: "A" }, context: shared_context 13 | ) 14 | 15 | search_children = Person.ransack( 16 | { children_name_eq: "B" }, context: shared_context 17 | ) 18 | 19 | shared_conditions = [search_parents, search_children].map { |search| 20 | Ransack::Visitor.new.accept(search.base) 21 | } 22 | 23 | Person.joins(shared_context.join_sources) 24 | .where(shared_conditions.reduce(&:or)) 25 | .to_sql 26 | ``` 27 | Produces: 28 | ```sql 29 | SELECT "people".* 30 | FROM "people" 31 | LEFT OUTER JOIN "people" "parents_people" 32 | ON "parents_people"."id" = "people"."parent_id" 33 | LEFT OUTER JOIN "people" "children_people" 34 | ON "children_people"."parent_id" = "people"."id" 35 | WHERE ( 36 | ("parents_people"."name" = 'A' OR "children_people"."name" = 'B') 37 | ) 38 | ORDER BY "people"."id" DESC 39 | ``` 40 | 41 | Admittedly this is not as simple as it should be, but it's workable for now. (Implementing [issue 417](https://github.com/activerecord-hackery/ransack/issues/417) could make this more straightforward.) 42 | -------------------------------------------------------------------------------- /docs/docs/going-further/polymorphic-search.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Polymorphic Searches 3 | sidebar_position: 14 4 | --- 5 | 6 | When making searches from polymorphic models it is necessary to specify the type of model you are searching. 7 | 8 | For example: 9 | 10 | Given two models 11 | 12 | ```ruby 13 | class House < ActiveRecord::Base 14 | has_one :location, as: :locatable 15 | end 16 | 17 | class Location < ActiveRecord::Base 18 | belongs_to :locatable, polymorphic: true 19 | end 20 | ``` 21 | 22 | Normally (without polymorphic relationship) you would be able to search as per below: 23 | 24 | ```ruby 25 | Location.ransack(locatable_number_eq: 100).result 26 | ``` 27 | 28 | However when this is searched you will get the following error 29 | 30 | ```ruby 31 | ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :locatable 32 | ``` 33 | 34 | In order to search for locations by house number when the relationship is polymorphic you have to specify the type of records you will be searching and construct your search as below: 35 | 36 | ```ruby 37 | Location.ransack(locatable_of_House_type_number_eq: 100).result 38 | ``` 39 | 40 | note the `_of_House_type_` added to the search key. This allows Ransack to correctly specify the table names in SQL join queries. 41 | 42 | For namespaced models you should use a quoted string containing the standard Ruby module notation 43 | 44 | ```ruby 45 | Location.ransack('locatable_of_Residences::House_type_number_eq' => 100).result 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/docs/going-further/release_process.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Versions and Releases 3 | sidebar_position: 11 4 | --- 5 | 6 | 7 | ## Semantic Versioning 8 | 9 | Ransack attempts to follow semantic versioning in the format of `x.y.z`, where: 10 | 11 | `x` stands for a major version (new features that are not backward-compatible). 12 | 13 | `y` stands for a minor version (new features that are backward-compatible). 14 | 15 | `z` stands for a patch (bug fixes). 16 | 17 | In other words: `Major.Minor.Patch`. 18 | 19 | 20 | ## Release Process 21 | 22 | *For the maintainers of Ransack.* 23 | 24 | To release a new version of Ransack and publish it to RubyGems, take the following steps: 25 | 26 | - Create a new release, marked `Prerelease`. 27 | - Update the versions file to the new release, commit and push to `master`. 28 | - Update the [`version.rb`](https://github.com/activerecord-hackery/ransack/lib/ransack/version.rb) file to the new release, commit and push to `master`. 29 | - From the terminal, run the following commands: 30 | 31 | ```bash 32 | rake build 33 | rake release 34 | ``` 35 | 36 | ![Create a Release](img/create_release.png) 37 | -------------------------------------------------------------------------------- /docs/docs/going-further/saving-queries.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | title: Saving queries 4 | --- 5 | 6 | ## Ransack Memory Gem 7 | 8 | The [Ransack Memory](https://github.com/richardrails/ransack_memory) gem accomplishes this. 9 | 10 | ## Custom solution 11 | 12 | If you want a custom solution, you can build it yourself. My ransack AJAX searching doesn’t save your search parameters across transactions. In this post I’ll show you how to easily add this capability in a generic way. 13 | 14 | In this example I added AJAX search ability to index pages. 15 | 16 | ```ruby 17 | def index 18 | @search = ComponentDefinition.search(search_params) 19 | # make name the default sort column 20 | @search.sorts = 'name' if @search.sorts.empty? 21 | @component_definitions = @search.result().page(params[:page]) 22 | end 23 | ``` 24 | 25 | I added methods(search_params, clear_search_index) in the ApplicationController to add a level of abstraction from the search gem I was using. Turns out this made things super easy, especially considering I won’t have to update my code generation tools for index pages. 26 | 27 | ```ruby 28 | class ApplicationController < ActionController::Base 29 | def search_params 30 | params[:q] 31 | end 32 | def clear_search_index 33 | if params[:search_cancel] 34 | params.delete(:search_cancel) 35 | if(!search_params.nil?) 36 | search_params.each do |key, param| 37 | search_params[key] = nil 38 | end 39 | end 40 | end 41 | end 42 | end 43 | ``` 44 | 45 | I decided to store the ransack search parameters, params[:q], in the session. To make the session parameter unique I used a key creed from the controllers name and “_search”. 46 | 47 | ```ruby 48 | class ApplicationController < ActionController::Base 49 | 50 | # CHECK THE SESSION FOR SEARCH PARAMETERS IS THEY AREN'T IN THE REQUEST 51 | def search_params 52 | if params[:q] == nil 53 | params[:q] = session[search_key] 54 | end 55 | if params[:q] 56 | session[search_key] = params[:q] 57 | end 58 | params[:q] 59 | end 60 | # DELETE SEARCH PARAMETERS FROM THE SESSION 61 | def clear_search_index 62 | if params[:search_cancel] 63 | params.delete(:search_cancel) 64 | if(!search_params.nil?) 65 | search_params.each do |key, param| 66 | search_params[key] = nil 67 | end 68 | end 69 | # REMOVE FROM SESSION 70 | session.delete(search_key) 71 | end 72 | end 73 | 74 | protected 75 | # GENERATE A GENERIC SESSION KEY BASED ON THE CONTROLLER NAME 76 | def search_key 77 | "#{controller_name}_search".to_sym 78 | end 79 | end 80 | ``` 81 | 82 | Based on [Saving queries](https://techbrownbags.wordpress.com/2015/02/18/rails-save-ransack-search-queries/) 83 | -------------------------------------------------------------------------------- /docs/docs/going-further/searching-postgres.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 8 3 | title: Postgres searches 4 | --- 5 | 6 | Searching on Postgres-specific column types. 7 | 8 | ## Postgres Array searches 9 | 10 | See [this issue](https://github.com/activerecord-hackery/ransack/issues/321) for details. 11 | 12 | ## PostgreSQL JSONB searches 13 | 14 | ### Using a fixed key 15 | 16 | See here for searching on a fixed key in a JSONB column: https://activerecord-hackery.github.io/ransack/going-further/ransackers/#postgres-columns 17 | 18 | ### Using the JSONB contains operator 19 | 20 | To fully use the power of the JSONB column you may want to filter on any key though: 21 | 22 | Install the [ActiveRecordExtended](https://github.com/GeorgeKaraszi/ActiveRecordExtended) gem to add the `contains` arel predicate to your project. It let's you use the [Postgres contains operator @>](https://www.postgresql.org/docs/12/functions-json.html#FUNCTIONS-JSONB-OP-TABLE). 23 | 24 | Add a custom predicate in the `config/initializers/ransack.rb` file: 25 | ```ruby 26 | Ransack.configure do |config| 27 | config.add_predicate 'jcont', arel_predicate: 'contains', formatter: proc { |v| JSON.parse(v) } 28 | end 29 | ``` 30 | 31 | Now you can ransack the JSONB columns using the _jcont predicate. For example the Person model has a `data` JSONB column, find entries where the column contains the {"group": "experts"} key-value pair: 32 | 33 | Person.ransack(data_jcont: '{"group": "experts"}').result.to_sql 34 | 35 | SELECT "persons".* FROM "persons" WHERE "persons"."data" @> '"{\"group\": \"experts\"}"' 36 | 37 | If you have a GIN index on that column, the database will quickly be able to find that result. 38 | 39 | ### Treating the column as a string 40 | 41 | Warning: This method converts the column to a string and matches the given string to the result. This will be slow on large data_sets and does not make good use of the JSONB capabilities of Postgres, such as indexes. 42 | 43 | ```ruby 44 | class Contact < ApplicationRecord 45 | ransacker :within_json do |parent| 46 | Arel.sql("table.jsonb_data::text") 47 | end 48 | end 49 | 50 | Contact.all.ransack("within_json_cont" => "my") 51 | ``` 52 | 53 | Will generate 54 | 55 | `SELECT "contacts".* FROM "contacts" WHERE contacts.json_data ILIKE '%my%'` 56 | 57 | Note that this search treats the entire JSON as string, including parens, etc. i.e. you can search for e.g.: `Contact.all.ransack("within_json_cont" => '{"key": "value"}')` 58 | -------------------------------------------------------------------------------- /docs/docs/going-further/wiki-contributors.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Wiki Contributors 3 | sidebar_position: 20 4 | --- 5 | 6 | Ransack previously had documentation contained in a GitHub Wiki, and this content has been merged into this documentation website. The following long list of _amazing_ people all made contributions to the Wiki: 7 | 8 | * Abinoam P. Marques Jr 9 | * Alex Stophel 10 | * Andrea Schiavini 11 | * Andrew Vit 12 | * Ben Koshy 13 | * Brainkurv 14 | * Brandan Lennox 15 | * Brendon Muir 16 | * Chris Salzberg 17 | * Colleen McGuckin 18 | * David Aldridge 19 | * Davidson Mohanty 20 | * Denis Tataurov 21 | * Drew Moore 22 | * Eike Send 23 | * Feodor Cherashev 24 | * Glauco Custódio 25 | * Grey Baker 26 | * Harold.Luo 27 | * Herman Singh 28 | * Ian Smith 29 | * Jake Haber 30 | * Jan Klimo 31 | * Jared Beck 32 | * Jon Atack 33 | * Juanito Fatas 34 | * JungaJk 35 | * Leo Chen 36 | * Leon Miller-Out 37 | * Luca F 38 | * Marc Poris 39 | * Matt Oakley 40 | * Michael Kopchick 41 | * Nathan Colgate 42 | * Nguyen Phi Viet(Sun*) 43 | * Nguyễn Đức Long 44 | * NielsKSchjoedt 45 | * Patrick Copeland 46 | * Pedro Chambino 47 | * Rene Hopf 48 | * Richa Arora 49 | * Rob Jones 50 | * Roman Sokhan 51 | * Ryan Bates 52 | * Ryan Bigg 53 | * Sean 54 | * Sean Linsley 55 | * Sergey 56 | * Sunny Ripert 57 | * Tanbir Hasan 58 | * ThuyNguyen97 59 | * Vanda 60 | * Yana Agun Siswanto 61 | * bonyiii 62 | * charly 63 | * chifung7 64 | * colorfulberry 65 | * ddonahue99 66 | * ernie 67 | * gaaady 68 | * gingerlime 69 | * grumpit 70 | * itsalongstory 71 | * jonatack 72 | * kogre 73 | * nguyentrungson97 74 | * nslocum 75 | * omitter 76 | * radar 77 | * rilian 78 | * terraplane 79 | * tyronewilson 80 | * vansy61 81 | * willnet 82 | * wzcolon 83 | -------------------------------------------------------------------------------- /docs/docs/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | slug: '/' 4 | --- 5 | 6 | # Introduction 7 | 8 | Ransack will help you easily add **searching to your Rails application**, without any additional dependencies. 9 | 10 | There are advanced searching solutions around, like ElasticSearch or Algolia. **Ransack** will do the job for many Rails websites, without the need to run additional infrastructure or work in a different language. With Ransack you do it all with standard Ruby and ERB. 11 | 12 | Ready to move beyond the basics? Use **advanced features** like i18n and extensive configuration options. 13 | 14 | Ransack is supported for Rails 7.0, 6.x on Ruby 2.6.6 and later. 15 | 16 | ## Installation 17 | 18 | To install `ransack` and add it to your Gemfile, run 19 | 20 | ```ruby title='Gemfile' 21 | gem 'ransack' 22 | ``` 23 | 24 | ### Bleeding edge 25 | 26 | If you would like to use the latest updates not yet published to RubyGems, use the `main` branch: 27 | 28 | ```ruby title='Gemfile' 29 | gem 'ransack', :github => 'activerecord-hackery/ransack', :branch => 'main' 30 | ``` 31 | 32 | ### Demo application 33 | 34 | The [Ransack Demo application](https://github.com/activerecord-hackery/ransack_demo) shows how to create [simple](http://ransack-demo.herokuapp.com) and 35 | [advanced](http://ransack-demo.herokuapp.com/users/advanced_search) search forms for your Ruby on Rails application. 36 | 37 | 38 | ## Issues tracker 39 | 40 | * Before filing an issue, please read the [Contributing Guide](https://github.com/activerecord-hackery/ransack/CONTRIBUTING.md). 41 | * File an issue if a bug is caused by Ransack, is new (has not already been reported), and _can be reproduced from the information you provide_. 42 | * Please consider adding a branch with a failing spec describing the problem. 43 | * Contributions are welcome. :smiley: 44 | * Please do not use the issue tracker for personal support requests. Stack Overflow is a better place for that where a wider community can help you! 45 | 46 | 47 | ## Contributions 48 | 49 | To support the project: 50 | 51 | * Consider supporting us via [Open Collective](https://opencollective.com/ransack/backers/badge.svg) 52 | * Use Ransack in your apps, and let us know if you encounter anything that's 53 | broken or missing. A failing spec to demonstrate the issue is awesome. A pull 54 | request with passing tests is even better! 55 | * Before filing an issue or pull request, be sure to read and follow the 56 | [Contributing Guide](https://github.com/activerecord-hackery/ransack/CONTRIBUTING.md). 57 | * Please use Stack Overflow or other sites for questions or discussion not 58 | directly related to bug reports, pull requests, or documentation improvements. 59 | * Spread the word on Twitter, Facebook, and elsewhere if Ransack's been useful 60 | to you. The more people who are using the project, the quicker we can find and 61 | fix bugs! 62 | 63 | ## Contributors 64 | 65 | Ransack was created by [Ernie Miller](http://twitter.com/erniemiller) and is developed and maintained by: 66 | * [Sean Carroll](https://github.com/scarroll32) 67 | * [Deivid Rodriguez](https://github.com/deivid-rodriguez) 68 | * [Greg Molnar](https://github.com/gregmolnar) 69 | * [A great group of contributors](https://github.com/activerecord-hackery/ransack/graphs/contributors). 70 | - Ransack's logo is designed by [Anıl Kılıç](https://github.com/anilkilic). 71 | 72 | Alumni Maintainers 73 | - [Jon Atack](http://twitter.com/jonatack) 74 | - [Ryan Bigg](http://twitter.com/ryanbigg) 75 | 76 | This project exists thanks to all the people who contribute. 77 | 78 | 79 | ## Backers 80 | 81 | Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/ransack#backer)] 82 | 83 | 84 | 85 | 86 | ## Sponsors 87 | 88 | Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/ransack#sponsor)] 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Note: type annotations allow type checking and IDEs autocompletion 3 | 4 | const lightCodeTheme = require('prism-react-renderer/themes/github'); 5 | const darkCodeTheme = require('prism-react-renderer/themes/dracula'); 6 | 7 | /** @type {import('@docusaurus/types').Config} */ 8 | const config = { 9 | title: 'Ransack documentation', 10 | tagline: 'Object-based searching', 11 | url: 'https://activerecord-hackery.github.io', 12 | baseUrl: '/ransack/', 13 | //onBrokenLinks: 'throw', 14 | onBrokenMarkdownLinks: 'warn', 15 | favicon: 'img/favicon.ico', 16 | organizationName: 'activerecord-hackery', 17 | projectName: 'ransack', 18 | trailingSlash: true, 19 | 20 | presets: [ 21 | [ 22 | 'classic', 23 | /** @type {import('@docusaurus/preset-classic').Options} */ 24 | ({ 25 | docs: { 26 | routeBasePath: '/', 27 | sidebarPath: require.resolve('./sidebars.js'), 28 | editUrl: 'https://github.com/activerecord-hackery/ransack/edit/main/docs/', 29 | }, 30 | blog: { 31 | showReadingTime: true, 32 | editUrl: 'https://github.com/activerecord-hackery/ransack/edit/main/blog/', 33 | }, 34 | theme: { 35 | customCss: require.resolve('./src/css/custom.css'), 36 | }, 37 | }), 38 | ], 39 | ], 40 | 41 | themeConfig: 42 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 43 | ({ 44 | navbar: { 45 | logo: { 46 | alt: 'Ransack Logo', 47 | src: './logo/ransack-h.png', 48 | }, 49 | items: [ 50 | { 51 | type: 'doc', 52 | docId: 'intro', 53 | position: 'left', 54 | label: 'Documentation', 55 | }, 56 | {to: '/blog', label: 'Blog', position: 'left'}, 57 | { 58 | href: 'https://github.com/activerecord-hackery/ransack', 59 | label: 'GitHub', 60 | position: 'right', 61 | }, 62 | ], 63 | }, 64 | footer: { 65 | style: 'dark', 66 | links: [ 67 | { 68 | title: 'Docs', 69 | items: [ 70 | { 71 | label: 'Documentation', 72 | to: '/', 73 | }, 74 | ], 75 | }, 76 | { 77 | title: 'Community', 78 | items: [ 79 | { 80 | label: 'Stack Overflow', 81 | href: 'https://stackoverflow.com/questions/tagged/ransack', 82 | }, 83 | ], 84 | }, 85 | { 86 | title: 'More', 87 | items: [ 88 | { 89 | label: 'Blog', 90 | to: '/blog', 91 | }, 92 | { 93 | label: 'GitHub', 94 | href: 'https://github.com/activerecord-hackery/ransack', 95 | }, 96 | ], 97 | }, 98 | ], 99 | }, 100 | prism: { 101 | theme: lightCodeTheme, 102 | darkTheme: darkCodeTheme, 103 | additionalLanguages: ['ruby', 'erb'], 104 | }, 105 | }), 106 | 107 | themes: [ 108 | [ 109 | require.resolve("@easyops-cn/docusaurus-search-local"), 110 | { 111 | // `hashed` is recommended as long-term-cache of index file is possible. 112 | hashed: true, 113 | // needs to be the same as routeBasePath in @docusaurus/preset-classic config 114 | docsRouteBasePath: '/' 115 | }, 116 | ] 117 | ] 118 | }; 119 | 120 | module.exports = config; 121 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs-website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "^2.2.0", 18 | "@docusaurus/preset-classic": "^2.2.0", 19 | "@easyops-cn/docusaurus-search-local": "^0.33.5", 20 | "@mdx-js/react": "^1.6.22", 21 | "clsx": "^1.1.1", 22 | "prism-react-renderer": "^1.3.1", 23 | "react": "^17.0.2", 24 | "react-dom": "^17.0.2" 25 | }, 26 | "resolutions": { 27 | "trim": "^0.0.3", 28 | "got": "^11.8.5" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.5%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['hello'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | module.exports = sidebars; 32 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './styles.module.css'; 4 | 5 | const FeatureList = [ 6 | { 7 | title: 'Easy to Use', 8 | Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, 9 | description: ( 10 | <> 11 | Docusaurus was designed from the ground up to be easily installed and 12 | used to get your website up and running quickly. 13 | 14 | ), 15 | }, 16 | { 17 | title: 'Focus on What Matters', 18 | Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, 19 | description: ( 20 | <> 21 | Docusaurus lets you focus on your docs, and we'll do the chores. Go 22 | ahead and move your docs into the docs directory. 23 | 24 | ), 25 | }, 26 | { 27 | title: 'Powered by React', 28 | Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, 29 | description: ( 30 | <> 31 | Extend or customize your website layout by reusing React. Docusaurus can 32 | be extended while reusing the same header and footer. 33 | 34 | ), 35 | }, 36 | ]; 37 | 38 | function Feature({Svg, title, description}) { 39 | return ( 40 |
41 |
42 | 43 |
44 |
45 |

{title}

46 |

{description}

47 |
48 |
49 | ); 50 | } 51 | 52 | export default function HomepageFeatures() { 53 | return ( 54 |
55 |
56 |
57 | {FeatureList.map((props, idx) => ( 58 | 59 | ))} 60 |
61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | } 18 | 19 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 20 | [data-theme='dark'] { 21 | --ifm-color-primary: #25c2a0; 22 | --ifm-color-primary-dark: #21af90; 23 | --ifm-color-primary-darker: #1fa588; 24 | --ifm-color-primary-darkest: #1a8870; 25 | --ifm-color-primary-light: #29d5b0; 26 | --ifm-color-primary-lighter: #32d8b4; 27 | --ifm-color-primary-lightest: #4fddbf; 28 | } 29 | 30 | .docusaurus-highlight-code-line { 31 | background-color: rgba(0, 0, 0, 0.1); 32 | display: block; 33 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 34 | padding: 0 var(--ifm-pre-padding); 35 | } 36 | 37 | [data-theme='dark'] .docusaurus-highlight-code-line { 38 | background-color: rgba(0, 0, 0, 0.3); 39 | } 40 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /docs/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activerecord-hackery/ransack/e06a27ded233166362fdd9e6ca3996cecbef8265/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activerecord-hackery/ransack/e06a27ded233166362fdd9e6ca3996cecbef8265/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activerecord-hackery/ransack/e06a27ded233166362fdd9e6ca3996cecbef8265/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/tutorial/docsVersionDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activerecord-hackery/ransack/e06a27ded233166362fdd9e6ca3996cecbef8265/docs/static/img/tutorial/docsVersionDropdown.png -------------------------------------------------------------------------------- /docs/static/img/tutorial/localeDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activerecord-hackery/ransack/e06a27ded233166362fdd9e6ca3996cecbef8265/docs/static/img/tutorial/localeDropdown.png -------------------------------------------------------------------------------- /docs/static/logo/ransack-h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activerecord-hackery/ransack/e06a27ded233166362fdd9e6ca3996cecbef8265/docs/static/logo/ransack-h.png -------------------------------------------------------------------------------- /docs/static/logo/ransack-v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activerecord-hackery/ransack/e06a27ded233166362fdd9e6ca3996cecbef8265/docs/static/logo/ransack-v.png -------------------------------------------------------------------------------- /docs/static/logo/ransack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activerecord-hackery/ransack/e06a27ded233166362fdd9e6ca3996cecbef8265/docs/static/logo/ransack.png -------------------------------------------------------------------------------- /docs/static/logo/ransack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /lib/polyamorous/activerecord/join_association.rb: -------------------------------------------------------------------------------- 1 | module Polyamorous 2 | module JoinAssociationExtensions 3 | include SwappingReflectionClass 4 | def self.prepended(base) 5 | base.class_eval { attr_reader :join_type } 6 | end 7 | 8 | def initialize(reflection, children, polymorphic_class = nil, join_type = Arel::Nodes::InnerJoin) 9 | @join_type = join_type 10 | if polymorphic_class && ::ActiveRecord::Base > polymorphic_class 11 | swapping_reflection_klass(reflection, polymorphic_class) do |reflection| 12 | super(reflection, children) 13 | self.reflection.options[:polymorphic] = true 14 | end 15 | else 16 | super(reflection, children) 17 | end 18 | end 19 | 20 | # Same as #join_constraints, but instead of constructing tables from the 21 | # given block, uses the ones passed 22 | def join_constraints_with_tables(foreign_table, foreign_klass, join_type, alias_tracker, tables) 23 | joins = [] 24 | chain = [] 25 | 26 | reflection.chain.each.with_index do |reflection, i| 27 | table = tables[i] 28 | 29 | @table ||= table 30 | chain << [reflection, table] 31 | end 32 | 33 | # The chain starts with the target table, but we want to end with it here (makes 34 | # more sense in this context), so we reverse 35 | chain.reverse_each do |reflection, table| 36 | klass = reflection.klass 37 | 38 | join_scope = reflection.join_scope(table, foreign_table, foreign_klass) 39 | 40 | unless join_scope.references_values.empty? 41 | join_dependency = join_scope.construct_join_dependency( 42 | join_scope.eager_load_values | join_scope.includes_values, Arel::Nodes::OuterJoin 43 | ) 44 | join_scope.joins!(join_dependency) 45 | end 46 | 47 | arel = join_scope.arel(alias_tracker.aliases) 48 | nodes = arel.constraints.first 49 | 50 | if nodes.is_a?(Arel::Nodes::And) 51 | others = nodes.children.extract! do |node| 52 | !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name } 53 | end 54 | end 55 | 56 | joins << table.create_join(table, table.create_on(nodes), join_type) 57 | 58 | if others && !others.empty? 59 | joins.concat arel.join_sources 60 | append_constraints(joins.last, others) 61 | end 62 | 63 | # The current table in this iteration becomes the foreign table in the next 64 | foreign_table, foreign_klass = table, klass 65 | end 66 | 67 | joins 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/polyamorous/activerecord/join_association_7_2.rb: -------------------------------------------------------------------------------- 1 | module Polyamorous 2 | module JoinAssociationExtensions 3 | # Same as #join_constraints, but instead of constructing tables from the 4 | # given block, uses the ones passed 5 | def join_constraints_with_tables(foreign_table, foreign_klass, join_type, alias_tracker, tables) 6 | joins = [] 7 | chain = [] 8 | 9 | reflection.chain.each.with_index do |reflection, i| 10 | table = tables[i] 11 | 12 | @table ||= table 13 | chain << [reflection, table] 14 | end 15 | 16 | base_klass.with_connection do |connection| 17 | # The chain starts with the target table, but we want to end with it here (makes 18 | # more sense in this context), so we reverse 19 | chain.reverse_each do |reflection, table| 20 | klass = reflection.klass 21 | 22 | join_scope = reflection.join_scope(table, foreign_table, foreign_klass) 23 | 24 | unless join_scope.references_values.empty? 25 | join_dependency = join_scope.construct_join_dependency( 26 | join_scope.eager_load_values | join_scope.includes_values, Arel::Nodes::OuterJoin 27 | ) 28 | join_scope.joins!(join_dependency) 29 | end 30 | 31 | arel = join_scope.arel(alias_tracker.aliases) 32 | nodes = arel.constraints.first 33 | 34 | if nodes.is_a?(Arel::Nodes::And) 35 | others = nodes.children.extract! do |node| 36 | !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name } 37 | end 38 | end 39 | 40 | joins << table.create_join(table, table.create_on(nodes), join_type) 41 | 42 | if others && !others.empty? 43 | joins.concat arel.join_sources 44 | append_constraints(connection, joins.last, others) 45 | end 46 | 47 | # The current table in this iteration becomes the foreign table in the next 48 | foreign_table, foreign_klass = table, klass 49 | end 50 | 51 | joins 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/polyamorous/activerecord/join_dependency.rb: -------------------------------------------------------------------------------- 1 | module Polyamorous 2 | module JoinDependencyExtensions 3 | # Replaces ActiveRecord::Associations::JoinDependency#build 4 | def build(associations, base_klass) 5 | associations.map do |name, right| 6 | if name.is_a? Join 7 | reflection = find_reflection base_klass, name.name 8 | reflection.check_validity! 9 | reflection.check_eager_loadable! 10 | 11 | klass = if reflection.polymorphic? 12 | name.klass || base_klass 13 | else 14 | reflection.klass 15 | end 16 | JoinAssociation.new(reflection, build(right, klass), name.klass, name.type) 17 | else 18 | reflection = find_reflection base_klass, name 19 | reflection.check_validity! 20 | reflection.check_eager_loadable! 21 | 22 | if reflection.polymorphic? 23 | raise ActiveRecord::EagerLoadPolymorphicError.new(reflection) 24 | end 25 | JoinAssociation.new(reflection, build(right, reflection.klass)) 26 | end 27 | end 28 | end 29 | 30 | def join_constraints(joins_to_add, alias_tracker, references) 31 | @alias_tracker = alias_tracker 32 | @joined_tables = {} 33 | @references = {} 34 | 35 | references.each do |table_name| 36 | @references[table_name.to_sym] = table_name if table_name.is_a?(String) 37 | end 38 | 39 | joins = make_join_constraints(join_root, join_type) 40 | 41 | joins.concat joins_to_add.flat_map { |oj| 42 | if join_root.match?(oj.join_root) && join_root.table.name == oj.join_root.table.name 43 | walk join_root, oj.join_root, oj.join_type 44 | else 45 | make_join_constraints(oj.join_root, oj.join_type) 46 | end 47 | } 48 | end 49 | 50 | def construct_tables_for_association!(join_root, association) 51 | tables = table_aliases_for(join_root, association) 52 | association.table = tables.first 53 | tables 54 | end 55 | 56 | private 57 | 58 | def table_aliases_for(parent, node) 59 | @joined_tables ||= {} 60 | node.reflection.chain.map { |reflection| 61 | table, terminated = @joined_tables[reflection] 62 | root = reflection == node.reflection 63 | 64 | if table && (!root || !terminated) 65 | @joined_tables[reflection] = [table, true] if root 66 | table 67 | else 68 | table = alias_tracker.aliased_table_for(reflection.klass.arel_table) do 69 | name = reflection.alias_candidate(parent.table_name) 70 | root ? name : "#{name}_join" 71 | end 72 | @joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin 73 | table 74 | end 75 | } 76 | end 77 | 78 | module ClassMethods 79 | # Prepended before ActiveRecord::Associations::JoinDependency#walk_tree 80 | # 81 | def walk_tree(associations, hash) 82 | case associations 83 | when TreeNode 84 | associations.add_to_tree(hash) 85 | when Hash 86 | associations.each do |k, v| 87 | cache = 88 | if TreeNode === k 89 | k.add_to_tree(hash) 90 | else 91 | hash[k] ||= {} 92 | end 93 | walk_tree(v, cache) 94 | end 95 | else 96 | super(associations, hash) 97 | end 98 | end 99 | end 100 | 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/polyamorous/activerecord/reflection.rb: -------------------------------------------------------------------------------- 1 | module Polyamorous 2 | module ReflectionExtensions 3 | def join_scope(table, foreign_table, foreign_klass) 4 | if respond_to?(:polymorphic?) && polymorphic? 5 | super.where!(foreign_table[foreign_type].eq(klass.name)) 6 | else 7 | super 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/polyamorous/join.rb: -------------------------------------------------------------------------------- 1 | module Polyamorous 2 | class Join 3 | include TreeNode 4 | 5 | attr_accessor :name 6 | attr_reader :type, :klass 7 | 8 | def initialize(name, type = InnerJoin, klass = nil) 9 | @name = name 10 | @type = convert_to_arel_join_type(type) 11 | @klass = convert_to_class(klass) if klass 12 | end 13 | 14 | def klass=(klass) 15 | @klass = convert_to_class(klass) if klass 16 | end 17 | 18 | def type=(type) 19 | @type = convert_to_arel_join_type(type) if type 20 | end 21 | 22 | def hash 23 | [@name, @type, @klass].hash 24 | end 25 | 26 | def eql?(other) 27 | self.class == other.class && 28 | self.name == other.name && 29 | self.type == other.type && 30 | self.klass == other.klass 31 | end 32 | 33 | alias :== :eql? 34 | 35 | def add_to_tree(hash) 36 | hash[self] ||= {} 37 | end 38 | 39 | private 40 | 41 | def convert_to_arel_join_type(type) 42 | case type 43 | when 'inner', :inner 44 | InnerJoin 45 | when 'outer', :outer 46 | OuterJoin 47 | when Class 48 | if [InnerJoin, OuterJoin].include? type 49 | type 50 | else 51 | raise ArgumentError, "#{type} cannot be converted to an ARel join type" 52 | end 53 | else 54 | raise ArgumentError, "#{type} cannot be converted to an ARel join type" 55 | end 56 | end 57 | 58 | def convert_to_class(value) 59 | case value 60 | when String, Symbol 61 | Kernel.const_get(value) 62 | when Class 63 | value 64 | else 65 | raise ArgumentError, "#{value} cannot be converted to a Class" 66 | end 67 | end 68 | 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/polyamorous/polyamorous.rb: -------------------------------------------------------------------------------- 1 | ActiveSupport.on_load(:active_record) do 2 | module Polyamorous 3 | InnerJoin = Arel::Nodes::InnerJoin 4 | OuterJoin = Arel::Nodes::OuterJoin 5 | 6 | JoinDependency = ::ActiveRecord::Associations::JoinDependency 7 | JoinAssociation = ::ActiveRecord::Associations::JoinDependency::JoinAssociation 8 | end 9 | 10 | require 'polyamorous/tree_node' 11 | require 'polyamorous/join' 12 | require 'polyamorous/swapping_reflection_class' 13 | 14 | require 'polyamorous/activerecord/join_association' 15 | require 'polyamorous/activerecord/join_dependency' 16 | require 'polyamorous/activerecord/reflection' 17 | 18 | if ::ActiveRecord.version >= ::Gem::Version.new("7.2") && ::ActiveRecord.version < ::Gem::Version.new("7.2.2.1") 19 | require "polyamorous/activerecord/join_association_7_2" 20 | end 21 | 22 | ActiveRecord::Reflection::AbstractReflection.send(:prepend, Polyamorous::ReflectionExtensions) 23 | 24 | Polyamorous::JoinDependency.send(:prepend, Polyamorous::JoinDependencyExtensions) 25 | Polyamorous::JoinDependency.singleton_class.send(:prepend, Polyamorous::JoinDependencyExtensions::ClassMethods) 26 | Polyamorous::JoinAssociation.send(:prepend, Polyamorous::JoinAssociationExtensions) 27 | end 28 | -------------------------------------------------------------------------------- /lib/polyamorous/swapping_reflection_class.rb: -------------------------------------------------------------------------------- 1 | module Polyamorous 2 | module SwappingReflectionClass 3 | def swapping_reflection_klass(reflection, klass) 4 | new_reflection = reflection.clone 5 | new_reflection.instance_variable_set(:@options, reflection.options.clone) 6 | new_reflection.options.delete(:polymorphic) 7 | new_reflection.instance_variable_set(:@klass, klass) 8 | yield new_reflection 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/polyamorous/tree_node.rb: -------------------------------------------------------------------------------- 1 | module Polyamorous 2 | module TreeNode 3 | def add_to_tree(hash) 4 | raise NotImplementedError 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/ransack.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/dependencies/autoload' 2 | require 'active_support/deprecation' 3 | require 'active_support/version' 4 | 5 | if ::ActiveSupport.version >= ::Gem::Version.new("7.1") 6 | require 'active_support/deprecator' 7 | end 8 | 9 | require 'active_support/core_ext' 10 | require 'ransack/configuration' 11 | require 'polyamorous/polyamorous' 12 | 13 | module Ransack 14 | extend Configuration 15 | class UntraversableAssociationError < StandardError; end 16 | end 17 | 18 | Ransack.configure do |config| 19 | Ransack::Constants::AREL_PREDICATES.each do |name| 20 | config.add_predicate name, arel_predicate: name 21 | end 22 | Ransack::Constants::DERIVED_PREDICATES.each do |args| 23 | config.add_predicate(*args) 24 | end 25 | end 26 | 27 | require 'ransack/search' 28 | require 'ransack/ransacker' 29 | require 'ransack/translate' 30 | require 'ransack/active_record' 31 | require 'ransack/context' 32 | require 'ransack/version' 33 | 34 | ActiveSupport.on_load(:action_controller) do 35 | require 'ransack/helpers' 36 | ActionController::Base.helper Ransack::Helpers::FormHelper 37 | end 38 | -------------------------------------------------------------------------------- /lib/ransack/active_record.rb: -------------------------------------------------------------------------------- 1 | require 'ransack/adapters/active_record/base' 2 | 3 | ActiveSupport.on_load(:active_record) do 4 | extend Ransack::Adapters::ActiveRecord::Base 5 | 6 | Ransack::SUPPORTS_ATTRIBUTE_ALIAS = 7 | begin 8 | ActiveRecord::Base.respond_to?(:attribute_aliases) 9 | rescue NameError 10 | false 11 | end 12 | end 13 | 14 | require 'ransack/adapters/active_record/context' 15 | -------------------------------------------------------------------------------- /lib/ransack/helpers.rb: -------------------------------------------------------------------------------- 1 | require 'ransack/helpers/form_builder' 2 | require 'ransack/helpers/form_helper' 3 | -------------------------------------------------------------------------------- /lib/ransack/invalid_search_error.rb: -------------------------------------------------------------------------------- 1 | module Ransack 2 | class InvalidSearchError < ArgumentError; end 3 | end 4 | -------------------------------------------------------------------------------- /lib/ransack/locale/ar.yml: -------------------------------------------------------------------------------- 1 | ar: 2 | ransack: 3 | search: "بحث" 4 | predicate: "فاعل" 5 | and: "و" 6 | or: "أو" 7 | any: "أيُّ" 8 | all: "كل" 9 | combinator: "دالة توافقية" 10 | attribute: "خاصية" 11 | value: "قيمة" 12 | condition: "شرط" 13 | sort: "ترتيب" 14 | asc: "تصاعدي" 15 | desc: "تنازلي" 16 | predicates: 17 | eq: "معادل" 18 | eq_any: "معادل على اﻷقل لواحد" 19 | eq_all: "معادل للجميع" 20 | not_eq: "ليس معادلا لـ" 21 | not_eq_any: "ليس معادلا على اﻷقل لواحد" 22 | not_eq_all: "ليس معادلا للجميع" 23 | matches: "موائم" 24 | matches_any: "موائم لواحد على اﻷقل" 25 | matches_all: "موائم للجميع" 26 | does_not_match: "لا يتواءم" 27 | does_not_match_any: "لا يتواءم مع واحد على اﻷقل" 28 | does_not_match_all: "لا يتواءم مع الجميع" 29 | lt: "أصغر من" 30 | lt_any: "أصغر لواحد على اﻷقل" 31 | lt_all: "أصغر من الجميع" 32 | lteq: "أصغر أو مساو لـ" 33 | lteq_any: "أصغر أو مساو لواحد على اﻷقل" 34 | lteq_all: "أصغر أو مساو للجميع" 35 | gt: "أكبر من" 36 | gt_any: "أكبر من واحد على اﻷقل" 37 | gt_all: "أكبر من الجميع" 38 | gteq: "أكبر أو مساو لـ" 39 | gteq_any: "أكبر أو مساو لواحد على اﻷقل" 40 | gteq_all: "أكبر أو مساو للجميع" 41 | in: "متضمن لـ" 42 | in_any: "متضمن لواحد على اﻷقل" 43 | in_all: "متضمن للجميع" 44 | not_in: "غير متضمن" 45 | not_in_any: "غير متضمن لواحد على اﻷقل" 46 | not_in_all: "غير متضمن للجميع" 47 | cont: "محتو" 48 | cont_any: "محتو لواحد على اﻷقل" 49 | cont_all: "محتو لجميع" 50 | not_cont: "غير محتو" 51 | not_cont_any: "غير محتو لواحد على اﻷقل" 52 | not_cont_all: "غير محتو للجميع" 53 | start: "يبدأ بـ" 54 | start_any: "يبدأ بواحد على اﻷقل" 55 | start_all: "يبدأ بالجميع" 56 | not_start: "لا يبدأ بـ" 57 | not_start_any: "لا يبدأ بواحد على اﻷقل" 58 | not_start_all: "لا يبدأ بالجميع" 59 | end: "ينتهي بـ" 60 | end_any: "ينتهي بواحد على اﻷقل" 61 | end_all: "ينتهي بالجميع" 62 | not_end: "لا ينتهي بـ" 63 | not_end_any: "لا ينتهي بواحد على اﻷقل" 64 | not_end_all: "لا ينتهي بالجميع" 65 | 'true': "صحيح" 66 | 'false': "خطأ" 67 | present: "مستقبل" 68 | blank: "فراغ" 69 | 'null': "عدم" 70 | not_null: "غير مساو لقيمة عدم" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/az.yml: -------------------------------------------------------------------------------- 1 | az: 2 | ransack: 3 | search: "axtar" 4 | predicate: "təsdiqlə" 5 | and: "və" 6 | or: "və ya" 7 | any: "hər hansı" 8 | all: "hamısı" 9 | combinator: "birləşdirici" 10 | attribute: "xüsusiyyət" 11 | value: "dəyər" 12 | condition: "şərt" 13 | sort: "sırala" 14 | asc: "artan" 15 | desc: "azalan" 16 | predicates: 17 | eq: "bərabər" 18 | eq_any: "hər hansı birinə bərabər" 19 | eq_all: "hamısına bərabər" 20 | not_eq: "bərabər deyil" 21 | not_eq_any: "hər hansı birinə bərabər deyil" 22 | not_eq_all: "heç birinə bərabər deyil" 23 | matches: "uyğunlaşan" 24 | matches_any: "hər hansı biri ilə uyğunlaşan" 25 | matches_all: "hamısı ilə uyğunlaşan" 26 | does_not_match: "uyğunlaşmayan" 27 | does_not_match_any: "hər hansı biri ilə uyğunlaşmayan" 28 | does_not_match_all: "heç biri ilə uyğunlaşmayan" 29 | lt: "daha kiçik" 30 | lt_any: "hər hansı birindən kiçik" 31 | lt_all: "hamısından kiçik" 32 | lteq: "daha kiçik və ya bərabər" 33 | lteq_any: "daha kiçik və ya hər hansı birinə bərabər" 34 | lteq_all: "daha kiçik və ya hamısına bərabər" 35 | gt: "daha böyük " 36 | gt_any: "hər hansı birindən daha böyük" 37 | gt_all: "hamısından daha böyük" 38 | gteq: "daha böyük və ya bərabər" 39 | gteq_any: "daha böyük və ya hər hansı birine bərabər" 40 | gteq_all: "daha böyük və ya hamısıne bərabər" 41 | in: "içində" 42 | in_any: "hər hansı birində" 43 | in_all: "hamısında" 44 | not_in: "içində deyil" 45 | not_in_any: "hər hansı birində deyil" 46 | not_in_all: "heç birində deyil" 47 | cont: "ehtiva edən" 48 | cont_any: "hər hansı birini ehtiva edən" 49 | cont_all: "hamısını ehtiva edən" 50 | not_cont: "ehtiva etməyən" 51 | not_cont_any: "hər hansı birini ehtiva etməyən" 52 | not_cont_all: "heç birini birini ehtiva etməyən" 53 | start: "ilə başlayan" 54 | start_any: "hər hansı biri ilə başlayan" 55 | start_all: "hamısı ilə başlayan" 56 | not_start: "ilə başlamayan" 57 | not_start_any: "hər hansı biri ilə başlamayan" 58 | not_start_all: "hiçbiri ilə başlamayan" 59 | end: "ilə bitən" 60 | end_any: "hər hansı biri ilə bitən" 61 | end_all: "hamısı ilə bitən" 62 | not_end: "ilə bitməyən" 63 | not_end_any: "hər hansı biri ilə bitməyən" 64 | not_end_all: "heç biri ilə bitməyən" 65 | 'true': "doğru" 66 | 'false': "yanlış" 67 | present: "mövcüd" 68 | blank: "boş" 69 | 'null': "keçərsiz" 70 | not_null: "keçərli" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/bg.yml: -------------------------------------------------------------------------------- 1 | bg: 2 | ransack: 3 | search: "търсене" 4 | predicate: "предикат" 5 | and: "и" 6 | or: "или" 7 | any: "всякакво" 8 | all: "всички" 9 | combinator: "комбинатор" 10 | attribute: "атрибут" 11 | value: "стойност" 12 | condition: "условие" 13 | sort: "сортиране" 14 | asc: "възходящ" 15 | desc: "низходящ" 16 | predicates: 17 | eq: "се равнява на" 18 | eq_any: "се равнява на всяко" 19 | eq_all: "се равнява на всички" 20 | not_eq: "не е равно на" 21 | not_eq_any: "не е равно на никое от" 22 | not_eq_all: "не е равно на всички" 23 | matches: "съвпада" 24 | matches_any: "съвпада с някое" 25 | matches_all: "съвпада с всички" 26 | does_not_match: "не съвпада" 27 | does_not_match_any: "не съвпада с никое" 28 | does_not_match_all: "не съвпада с всички" 29 | lt: "по-малко от" 30 | lt_any: "по-малко от някое" 31 | lt_all: "по-малко от всички" 32 | lteq: "по-малко или равно на" 33 | lteq_any: "по-малко или равно на някое" 34 | lteq_all: "по-малко или равно на всички" 35 | gt: "по-голямо от" 36 | gt_any: "по-голямо от всякое" 37 | gt_all: "по-голямо от всички" 38 | gteq: "по-голямо или равно на" 39 | gteq_any: "по-голямо или равно на всякое" 40 | gteq_all: "по-голямо или равно на всички" 41 | in: "в" 42 | in_any: "във всякое" 43 | in_all: "във всички" 44 | not_in: "не в" 45 | not_in_any: "не в никое" 46 | not_in_all: "не във всички" 47 | cont: "съдържа" 48 | cont_any: "съдържа всякое" 49 | cont_all: "съдържа всички" 50 | not_cont: "не съдържа" 51 | not_cont_any: "не съдържа никое" 52 | not_cont_all: "не съдържа всички" 53 | start: "започва с" 54 | start_any: "започва с всякое" 55 | start_all: "започва с всички" 56 | not_start: "не започва с" 57 | not_start_any: "не започва с никое" 58 | not_start_all: "не започва с всички" 59 | end: "завършва с" 60 | end_any: "завършва с всякое" 61 | end_all: "завършва с всички" 62 | not_end: "не завършва с" 63 | not_end_any: "не завършва с никое" 64 | not_end_all: "не завършва с всички" 65 | 'true': "истина" 66 | 'false': "неистина" 67 | present: "присъства" 68 | blank: "е празно" 69 | 'null': "е нула" 70 | not_null: "не е нула" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/ca.yml: -------------------------------------------------------------------------------- 1 | ca: 2 | ransack: 3 | search: "cercar" 4 | predicate: "predicat" 5 | and: "i" 6 | or: "o" 7 | any: "qualsevol" 8 | all: "tots" 9 | combinator: "combinador" 10 | attribute: "atribut" 11 | value: "valor" 12 | condition: "condició" 13 | sort: "ordenar" 14 | asc: "ascendent" 15 | desc: "descendent" 16 | predicates: 17 | eq: "és igual a" 18 | eq_any: "és igual a qualsevol" 19 | eq_all: "és igual a tots" 20 | not_eq: "no és igual a" 21 | not_eq_any: "no és igual a qualsevol" 22 | not_eq_all: "no és igual a tots" 23 | matches: "coincideix" 24 | matches_any: "coincideix a qualsevol" 25 | matches_all: "coincideix a tots" 26 | does_not_match: "no coincideix" 27 | does_not_match_any: "no coincideix amb cap" 28 | does_not_match_all: "no coincideix amb tots" 29 | lt: "menor que" 30 | lt_any: "menor que qualsevol" 31 | lt_all: "menor o igual a" 32 | lteq: "menor que o igual a" 33 | lteq_any: "menor o igual a qualsevol" 34 | lteq_all: "menor o igual a tots" 35 | gt: "major que" 36 | gt_any: "major que qualsevol" 37 | gt_all: "major que tots" 38 | gteq: "major que o igual a" 39 | gteq_any: "major que o igual a qualsevol" 40 | gteq_all: "major que o igual a tots" 41 | in: "en" 42 | in_any: "en qualsevol" 43 | in_all: "en tots" 44 | not_in: "no en" 45 | not_in_any: "no en qualsevol" 46 | not_in_all: "no en tots" 47 | cont: "conté" 48 | cont_any: "conté qualsevol" 49 | cont_all: "conté tots" 50 | not_cont: "no conté" 51 | not_cont_any: "no conté cap" 52 | not_cont_all: "no conté tota" 53 | start: "comença per" 54 | start_any: "comença per qualsevol" 55 | start_all: "comença per tot" 56 | not_start: "no comença per" 57 | not_start_any: "no comença per qualsevol" 58 | not_start_all: "no comença per tot" 59 | end: "acaba en" 60 | end_any: "acaba en qualsevol" 61 | end_all: "acaba en tot" 62 | not_end: "no acaba en" 63 | not_end_any: "no acaba en qualsevol" 64 | not_end_all: "no acaba en tot" 65 | 'true': "és verdader" 66 | 'false': "és fals" 67 | present: "és present" 68 | blank: "està en blanc" 69 | 'null': "és nul" 70 | not_null: "no és nul" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/cs.yml: -------------------------------------------------------------------------------- 1 | cs: 2 | ransack: 3 | search: "vyhledávání" 4 | predicate: "predikát" 5 | and: "a" 6 | or: "nebo" 7 | any: "kteroukoliv" 8 | all: "každou" 9 | combinator: "kombinátor" 10 | attribute: "atribut" 11 | value: "hodnota" 12 | condition: "podmínka" 13 | sort: "řazení" 14 | asc: "vzestupné" 15 | desc: "sestupné" 16 | predicates: 17 | eq: "rovno" 18 | eq_any: "rovno kterékoliv" 19 | eq_all: "rovno všem" 20 | not_eq: "nerovno" 21 | not_eq_any: "nerovno kterékoliv" 22 | not_eq_all: "nerovno všem" 23 | matches: "odpovídá" 24 | matches_any: "odpovídá kterékoliv" 25 | matches_all: "odpovídá všem" 26 | does_not_match: "neodpovídá" 27 | does_not_match_any: "neodpovídá kterékoliv" 28 | does_not_match_all: "neodpovídá všem" 29 | lt: "menší než" 30 | lt_any: "menší než kterákoliv" 31 | lt_all: "menší než všechny" 32 | lteq: "menší nebo rovno než" 33 | lteq_any: "menší nebo rovno než kterákoliv" 34 | lteq_all: "menší nebo rovno než všechny" 35 | gt: "větší než" 36 | gt_any: "větší než kterákoliv" 37 | gt_all: "větší než všechny" 38 | gteq: "větší nebo rovno než" 39 | gteq_any: "větší nebo rovno než kterákoliv" 40 | gteq_all: "větší nebo rovno než všechny" 41 | in: "v" 42 | in_any: "v kterékoliv" 43 | in_all: "ve všech" 44 | not_in: "není v" 45 | not_in_any: "není v kterékoliv" 46 | not_in_all: "není ve všech" 47 | cont: "obsahuje" 48 | cont_any: "obsahuje kterékoliv" 49 | cont_all: "obsahuje všechny" 50 | not_cont: "neobsahuje" 51 | not_cont_any: "neobsahuje kteroukoliv" 52 | not_cont_all: "neobsahuje všechny" 53 | start: "začíná s" 54 | start_any: "začíná s kteroukoliv" 55 | start_all: "začíná se všemi" 56 | not_start: "nezačíná s" 57 | not_start_any: "nezačíná s kteroukoliv" 58 | not_start_all: "nezačíná se všemi" 59 | end: "končí s" 60 | end_any: "končí s kteroukoliv" 61 | end_all: "končí se všemi" 62 | not_end: "nekončí s" 63 | not_end_any: "nekončí s kteroukoliv" 64 | not_end_all: "nekončí se všemi" 65 | 'true': "je pravdivé" 66 | 'false': "není pravdivé" 67 | present: "je vyplněné" 68 | blank: "je prázdné" 69 | 'null': "je null" 70 | not_null: "není null" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/da.yml: -------------------------------------------------------------------------------- 1 | da: 2 | ransack: 3 | search: "søg" 4 | predicate: "predicate" 5 | and: "og" 6 | or: "eller" 7 | any: "anhver" 8 | all: "alle" 9 | combinator: "kombinering" 10 | attribute: "attribut" 11 | value: "værdi" 12 | condition: "betingelse" 13 | sort: "sorter" 14 | asc: "opstigende" 15 | desc: "faldende" 16 | predicates: 17 | eq: "lig med" 18 | eq_any: "lig med enhver" 19 | eq_all: "lig med alle" 20 | not_eq: "ikke lig med" 21 | not_eq_any: "ikke lig med nogen" 22 | not_eq_all: "ikke lig med alle" 23 | matches: "matcher" 24 | matches_any: "matcher enhver" 25 | matches_all: "matcher alle" 26 | does_not_match: "matcher ikke" 27 | does_not_match_any: "matcher ikke nogen" 28 | does_not_match_all: "matcher ikke alle" 29 | lt: "mindre end" 30 | lt_any: "mindre end nogen" 31 | lt_all: "mindre end alle" 32 | lteq: "mindre end eller lig med" 33 | lteq_any: "mindre end eller lig med nogen" 34 | lteq_all: "mindre end eller lig med alle" 35 | gt: "større end" 36 | gt_any: "større end nogen" 37 | gt_all: "større end alle" 38 | gteq: "større end eller lig med" 39 | gteq_any: "større end eller lig med nogen" 40 | gteq_all: "større end eller lig med alle" 41 | in: "i" 42 | in_any: "i nogen" 43 | in_all: "i alle" 44 | not_in: "ikke i" 45 | not_in_any: "ikke i nogen" 46 | not_in_all: "ikke i alle" 47 | cont: "indeholder" 48 | cont_any: "indeholder nogen" 49 | cont_all: "indeholder alle" 50 | not_cont: "indeholder ikke" 51 | not_cont_any: "indeholder ikke nogen" 52 | not_cont_all: "indeholder ikke alle" 53 | start: "starter med" 54 | start_any: "starter med nogen" 55 | start_all: "starter med alle" 56 | not_start: "starter ikke med" 57 | not_start_any: "starter ikke med nogen" 58 | not_start_all: "starter ikke med alle" 59 | end: "slutter med" 60 | end_any: "slutter med nogen" 61 | end_all: "slutter med alle" 62 | not_end: "slutter ikke med" 63 | not_end_any: "slutter ikke med nogen" 64 | not_end_all: "slutter ikke med alle" 65 | 'true': "er sand" 66 | 'false': "er falsk" 67 | present: "er til stede" 68 | blank: "er blank" 69 | 'null': "er nul" 70 | not_null: "er ikke nul" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | ransack: 3 | search: "suchen" 4 | predicate: "Eigenschaft" 5 | and: "und" 6 | or: "oder" 7 | any: "beliebige" 8 | all: "alle" 9 | combinator: "Kombinator" 10 | attribute: "Attribut" 11 | value: "Wert" 12 | condition: "Bedingung" 13 | sort: "sortieren" 14 | asc: "aufsteigend" 15 | desc: "absteigend" 16 | predicates: 17 | eq: "gleicht" 18 | eq_any: "gleicht beliebigen" 19 | eq_all: "gleicht allen" 20 | not_eq: "ungleich" 21 | not_eq_any: "ungleich beliebigen" 22 | not_eq_all: "ungleich allen" 23 | matches: "entspricht" 24 | matches_any: "stimmt überein mit einem beliebigen" 25 | matches_all: "stimmt mit allen überein" 26 | does_not_match: "stimmt nicht überein" 27 | does_not_match_any: "erfüllt ein beliebiger/s nicht" 28 | does_not_match_all: "stimmt nicht mit allen überein" 29 | lt: "kleiner als" 30 | lt_any: "kleiner als ein beliebiger/s" 31 | lt_all: "kleiner als alle als alle" 32 | lteq: "kleiner oder gleich" 33 | lteq_any: "kleiner oder gleich beliebige" 34 | lteq_all: "kleiner oder gleich allen" 35 | gt: "größer als" 36 | gt_any: "größer als ein beliebiger/s" 37 | gt_all: "größer als alle" 38 | gteq: "größer oder gleich" 39 | gteq_any: "größer oder gleich als ein beliebiger/s" 40 | gteq_all: "größer oder gleich alle" 41 | in: "in" 42 | in_any: "ist nicht in einem beliebigen" 43 | in_all: "in allen" 44 | not_in: "nicht in" 45 | not_in_any: "nicht in beliebige" 46 | not_in_all: "nicht in allen" 47 | cont: "enthält" 48 | cont_any: "enthält beliebige" 49 | cont_all: "enthält alle" 50 | not_cont: "enthält nicht" 51 | not_cont_any: "enthält ein beliebiger/s nicht" 52 | not_cont_all: "enthält keine/s" 53 | start: "beginnt mit" 54 | start_any: "beginnt mit beliebigen" 55 | start_all: "beginnt mit allen" 56 | not_start: "beginnt nicht mit" 57 | not_start_any: "beginnt nicht mit beliebigen" 58 | not_start_all: "beginnt nicht mit allen" 59 | end: "endet mit" 60 | end_any: "endet mit beliebigen" 61 | end_all: "endet mit allen" 62 | not_end: "endet nicht mit" 63 | not_end_any: "endet nicht mit beliebigen" 64 | not_end_all: "endet nicht mit allen" 65 | 'true': "ist wahr" 66 | 'false': "ist falsch" 67 | present: "ist vorhanden" 68 | blank: "ist leer" 69 | 'null': "ist null" 70 | not_null: "ist nicht null" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/el.yml: -------------------------------------------------------------------------------- 1 | el: 2 | ransack: 3 | search: "αναζήτηση" 4 | predicate: "κατηγορούμενο" 5 | and: "και" 6 | or: "ή" 7 | any: "οποιοδήποτε" 8 | all: "όλα" 9 | combinator: "συνδυαστής" 10 | attribute: "ιδιότητα" 11 | value: "τιμή" 12 | condition: "συνθήκη" 13 | sort: "ταξινόμηση" 14 | asc: "αύξουσα" 15 | desc: "φθίνουσα" 16 | predicates: 17 | eq: "ίσον" 18 | eq_any: "ισούται με οποιοδήποτε" 19 | eq_all: "ισούται με όλα" 20 | not_eq: "άνισο" 21 | not_eq_any: "άνισο με οποιοδήποτε" 22 | not_eq_all: "άνισο με όλα" 23 | matches: "αντιστοιχεί" 24 | matches_any: "αντιστοιχεί με οποιοδήποτε" 25 | matches_all: "αντιστοιχεί με όλα" 26 | does_not_match: "δεν αντιστοιχεί" 27 | does_not_match_any: "δεν αντιστοιχεί με οποιοδήποτε" 28 | does_not_match_all: "δεν αντιστοιχεί με όλα" 29 | lt: "μικρότερο από" 30 | lt_any: "μικρότερο από οποιοδήποτε" 31 | lt_all: "μικρότερο από όλα" 32 | lteq: "μικρότερο ή ίσον" 33 | lteq_any: "μικρότερο ή ίσον από οποιοδήποτε" 34 | lteq_all: "μικρότερο ή ίσον από όλα" 35 | gt: "μεγαλύτερο από" 36 | gt_any: "μεγαλύτερο από οποιοδήποτε" 37 | gt_all: μεγαλύτερο από όλα" 38 | gteq: "μεγαλύτερο ή ίσον από" 39 | gteq_any: "μεγαλύτερο ή ίσον από οποιοδήποτε" 40 | gteq_all: "μεγαλύτερο ή ίσον από όλα" 41 | in: "περιέχεται" 42 | in_any: "περιέχεται σε οποιοδήποτε" 43 | in_all: "περιέχεται σε όλα" 44 | not_in: "δεν περιέχεται" 45 | not_in_any: "δεν περιέχεται σε οποιοδήποτε" 46 | not_in_all: "δεν περιέχεται σε όλα" 47 | cont: "περιέχει" 48 | cont_any: "περιέχει οποιοδήποτε" 49 | cont_all: "περιέχει όλα" 50 | not_cont: "δεν περιέχει" 51 | not_cont_any: "δεν περιέχει οποιοδήποτε" 52 | not_cont_all: "δεν περιέχει όλα" 53 | start: "αρχίζει με" 54 | start_any: "αρχίζει με οποιοδήποτε" 55 | start_all: "αρχίζει με όλα" 56 | not_start: "δεν αρχίζει με" 57 | not_start_any: "δεν αρχίζει με οποιοδήποτε" 58 | not_start_all: "δεν αρχίζει με όλα" 59 | end: "τελειώνει με" 60 | end_any: "τελειώνει οποιοδήποτε" 61 | end_all: "τελειώνει με όλα" 62 | not_end: "δεν τελειώνει με" 63 | not_end_any: "δεν τελειώνει οποιοδήποτε" 64 | not_end_all: "δεν τελειώνει με όλα" 65 | 'true': "αληθές" 66 | 'false': "ψευδές" 67 | present: "υπάρχει" 68 | blank: "είναι κενό" 69 | 'null': "άκυρο" 70 | not_null: "δεν είναι άκυρο" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | ransack: 3 | search: "search" 4 | predicate: "predicate" 5 | and: "and" 6 | or: "or" 7 | any: "any" 8 | all: "all" 9 | combinator: "combinator" 10 | attribute: "attribute" 11 | value: "value" 12 | condition: "condition" 13 | sort: "sort" 14 | asc: "ascending" 15 | desc: "descending" 16 | predicates: 17 | eq: "equals" 18 | eq_any: "equals any" 19 | eq_all: "equals all" 20 | not_eq: "not equal to" 21 | not_eq_any: "not equal to any" 22 | not_eq_all: "not equal to all" 23 | matches: "matches" 24 | matches_any: "matches any" 25 | matches_all: "matches all" 26 | does_not_match: "doesn't match" 27 | does_not_match_any: "doesn't match any" 28 | does_not_match_all: "doesn't match all" 29 | lt: "less than" 30 | lt_any: "less than any" 31 | lt_all: "less than all" 32 | lteq: "less than or equal to" 33 | lteq_any: "less than or equal to any" 34 | lteq_all: "less than or equal to all" 35 | gt: "greater than" 36 | gt_any: "greater than any" 37 | gt_all: "greater than all" 38 | gteq: "greater than or equal to" 39 | gteq_any: "greater than or equal to any" 40 | gteq_all: "greater than or equal to all" 41 | in: "in" 42 | in_any: "in any" 43 | in_all: "in all" 44 | not_in: "not in" 45 | not_in_any: "not in any" 46 | not_in_all: "not in all" 47 | cont: "contains" 48 | cont_any: "contains any" 49 | cont_all: "contains all" 50 | not_cont: "doesn't contain" 51 | not_cont_any: "doesn't contain any" 52 | not_cont_all: "doesn't contain all" 53 | start: "starts with" 54 | start_any: "starts with any" 55 | start_all: "starts with all" 56 | not_start: "doesn't start with" 57 | not_start_any: "doesn't start with any" 58 | not_start_all: "doesn't start with all" 59 | end: "ends with" 60 | end_any: "ends with any" 61 | end_all: "ends with all" 62 | not_end: "doesn't end with" 63 | not_end_any: "doesn't end with any" 64 | not_end_all: "doesn't end with all" 65 | 'true': "is true" 66 | 'false': "is false" 67 | present: "is present" 68 | blank: "is blank" 69 | 'null': "is null" 70 | not_null: "is not null" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/es.yml: -------------------------------------------------------------------------------- 1 | es: 2 | ransack: 3 | search: "buscar" 4 | predicate: "predicado" 5 | and: "y" 6 | or: "o" 7 | any: "cualquier" 8 | all: "todos" 9 | combinator: "combinador" 10 | attribute: "atributo" 11 | value: "valor" 12 | condition: "condición" 13 | sort: "ordenar" 14 | asc: "ascendente" 15 | desc: "descendente" 16 | predicates: 17 | eq: "es igual a" 18 | eq_any: "es igual a cualquier" 19 | eq_all: "es igual a todos" 20 | not_eq: "no es igual a" 21 | not_eq_any: "no es igual a cualquier" 22 | not_eq_all: "no es igual a todos" 23 | matches: "coincide con" 24 | matches_any: "coincide con cualquier" 25 | matches_all: "coincide con todos" 26 | does_not_match: "no coincide con" 27 | does_not_match_any: "no coincide con ninguno" 28 | does_not_match_all: "no coincide con todos" 29 | lt: "menor que" 30 | lt_any: "menor que cualquier" 31 | lt_all: "menor o igual a" 32 | lteq: "menor que o igual a" 33 | lteq_any: "menor o igual a cualquier" 34 | lteq_all: "menor o igual a todos" 35 | gt: "mayor que" 36 | gt_any: "mayor que cualquier" 37 | gt_all: "mayor que todos" 38 | gteq: "mayor que o igual a" 39 | gteq_any: "mayor que o igual a cualquier" 40 | gteq_all: "mayor que o igual a todos" 41 | in: "en" 42 | in_any: "en cualquier" 43 | in_all: "en todos" 44 | not_in: "no en" 45 | not_in_any: "no en cualquier" 46 | not_in_all: "no en todos" 47 | cont: "contiene" 48 | cont_any: "contiene cualquier" 49 | cont_all: "contiene todos" 50 | not_cont: "no contiene" 51 | not_cont_any: "no contiene ninguno" 52 | not_cont_all: "no contiene todos" 53 | start: "comienza con" 54 | start_any: "comienza con cualquier" 55 | start_all: "comienza con todos" 56 | not_start: "no comienza con" 57 | not_start_any: "no comienza con cualquier" 58 | not_start_all: "no comienza con todos" 59 | end: "termina en" 60 | end_any: "termina en cualquier" 61 | end_all: "termina en todos" 62 | not_end: "no termina en" 63 | not_end_any: "no termina en cualquier" 64 | not_end_all: "no termina en todos" 65 | 'true': "es verdadero" 66 | 'false': "es falso" 67 | present: "está presente" 68 | blank: "está en blanco" 69 | 'null': "es nulo" 70 | not_null: "no es nulo" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/fa.yml: -------------------------------------------------------------------------------- 1 | fa: 2 | ransack: 3 | search: "جستجو" 4 | predicate: "پیش فرض" 5 | and: "و" 6 | or: "یا" 7 | any: "هر کدام" 8 | all: "همه" 9 | combinator: "ترکیب کننده" 10 | attribute: "ویژگی" 11 | value: "مقدار" 12 | condition: "شرایط" 13 | sort: "مرتب سازی" 14 | asc: "صعودی" 15 | desc: "نزولی" 16 | predicates: 17 | eq: "مساوی با" 18 | eq_any: "مساوی با حداقل یکی" 19 | eq_all: "مساوی با همه" 20 | not_eq: "غیر مساوی با" 21 | not_eq_any: "غیر مساوی با هیچ کدام" 22 | not_eq_all: "غیر مساوی با همه" 23 | matches: "مشابهات" 24 | matches_any: "شبیه حداقل یکی" 25 | matches_all: "شبیه همه" 26 | does_not_match: "بدون شباهت" 27 | does_not_match_any: "شبیه یکی هم نیست" 28 | does_not_match_all: "شبیه هیچ کدام نیست" 29 | lt: "کمتر از" 30 | lt_any: "کمتر از حداقل یکی" 31 | lt_all: "کمتر از همه" 32 | lteq: "کمتر یا مساوی با" 33 | lteq_any: "کمتر یا مساوی با حداقل یکی" 34 | lteq_all: "کمتر یا مساوی با همه" 35 | gt: "بیشتر از" 36 | gt_any: "بیشتر از حداقل یکی" 37 | gt_all: "بیشتر از همه" 38 | gteq: "بیشتر یا مساوی با" 39 | gteq_any: "بیشتر یا مساوی با حداقل یکی" 40 | gteq_all: "بیشتر یا مساوی با همه" 41 | in: "در" 42 | in_any: "در حداقل یکی" 43 | in_all: "در همه" 44 | not_in: "نه در" 45 | not_in_any: "نه حتی در یکی" 46 | not_in_all: "در هیچ کدام" 47 | cont: "حاوی" 48 | cont_any: "حاوی حداقل یکی" 49 | cont_all: "حاوی همه" 50 | not_cont: "حاوی این نمیشود" 51 | not_cont_any: "حاوی حتی یکی هم نمیشود" 52 | not_cont_all: "حاوی هیچ کدام نمیشود" 53 | start: "شروع می شود با" 54 | start_any: "شروع می شود با حداقل یکی" 55 | start_all: "شروع می شود با همه" 56 | not_start: "شروع نمی شود با" 57 | not_start_any: "شروع نمی شود با حتی یکی" 58 | not_start_all: "شروع نمی شود با هیچ کدام" 59 | end: "به پایان می رسد با" 60 | end_any: "به پایان می رسد با حداقل یکی" 61 | end_all: "به پایان می رسد با همه" 62 | not_end: "پایان نمی یابد با" 63 | not_end_any: "پایان نمی یابد با حتی یکی" 64 | not_end_all: "پایان نمی یابد با هیچ کدام" 65 | 'true': "درست است" 66 | 'false': "نادرست است" 67 | present: "موجود است" 68 | blank: "خالی است" 69 | 'null': "صفر است" 70 | not_null: "صفر نیست" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/fi.yml: -------------------------------------------------------------------------------- 1 | fi: 2 | ransack: 3 | search: "haku" 4 | predicate: "predikaatti" 5 | and: "ja" 6 | or: "tai" 7 | any: "jokin" 8 | all: "kaikki" 9 | combinator: "kombinaattori" 10 | attribute: "attribuutti" 11 | value: "arvo" 12 | condition: "ehto" 13 | sort: "järjestys" 14 | asc: "nouseva" 15 | desc: "laskeva" 16 | predicates: 17 | eq: "sama kuin" 18 | eq_any: "sama kuin jokin" 19 | eq_all: "sama kuin kaikki" 20 | not_eq: "eri kuin" 21 | not_eq_any: "eri kuin jokin" 22 | not_eq_all: "eri kuin kaikki" 23 | matches: "täsmää" 24 | matches_any: "täsmää jonkun kanssa" 25 | matches_all: "täsmää kaikkien kanssa" 26 | does_not_match: "ei täsmää" 27 | does_not_match_any: "ei täsmää minkään kanssa" 28 | does_not_match_all: "ei täsmää kaikkien kanssa" 29 | lt: "vähemmän kuin" 30 | lt_any: "vähemmän kuin jokin" 31 | lt_all: "vähemmän kuin mikään" 32 | lteq: "vähemmän tai sama kuin" 33 | lteq_any: "vähemmän tai sama kuin jokin" 34 | lteq_all: "vähemmän tai sama kuin mikään" 35 | gt: "suurempi kuin" 36 | gt_any: "suurempi kuin jokin" 37 | gt_all: "suurempi kuin mikään" 38 | gteq: "suurempi tai yhtä suuri kuin" 39 | gteq_any: "suurempi tai yhtä suuri kuin jokin" 40 | gteq_all: "suurempi tai yhtä suuri kuin mikään" 41 | in: "kohteessa" 42 | in_any: "jossakin kohteessa" 43 | in_all: "kaikissa kohteissa" 44 | not_in: "ei kohteessa" 45 | not_in_any: "ei jossakin kohteista" 46 | not_in_all: "ei missään kohteista" 47 | cont: "sisältää" 48 | cont_any: "sisältää jonkin" 49 | cont_all: "sisältää kaikki" 50 | not_cont: "ei sisällä" 51 | not_cont_any: "ei sisällä jotakin" 52 | not_cont_all: "ei sisällä mitään" 53 | start: "alkaa" 54 | start_any: "alkaa jollakin" 55 | start_all: "alkaa kaikilla" 56 | not_start: "ei ala" 57 | not_start_any: "ei ala jollakin" 58 | not_start_all: "ei ala millään" 59 | end: "päättyy" 60 | end_any: "päättyy jollakin" 61 | end_all: "päättyy millään" 62 | not_end: "ei pääty" 63 | not_end_any: "ei pääty jollakin" 64 | not_end_all: "ei pääty millään" 65 | 'true': "on tosi" 66 | 'false': "ei ole tosi" 67 | present: "on läsnä" 68 | blank: "on tyhjä" 69 | 'null': "on määrittämätön" 70 | not_null: "on määritetty" 71 | 72 | -------------------------------------------------------------------------------- /lib/ransack/locale/fr.yml: -------------------------------------------------------------------------------- 1 | fr: 2 | ransack: 3 | search: "recherche" 4 | predicate: "prédicat" 5 | and: "et" 6 | or: "ou" 7 | any: "au moins un" 8 | all: "tous" 9 | combinator: "combinateur" 10 | attribute: "attribut" 11 | value: "valeur" 12 | condition: "condition" 13 | sort: "tri" 14 | asc: "ascendant" 15 | desc: "descendant" 16 | predicates: 17 | eq: "égal à" 18 | eq_any: "égal à au moins un" 19 | eq_all: "égal à tous" 20 | not_eq: "différent de" 21 | not_eq_any: "différent d'au moins un" 22 | not_eq_all: "différent de tous" 23 | matches: "correspond à" 24 | matches_any: "correspond à au moins un" 25 | matches_all: "correspond à tous" 26 | does_not_match: "ne correspond pas à" 27 | does_not_match_any: "ne correspond pas à au moins un" 28 | does_not_match_all: "ne correspond à aucun" 29 | lt: "inférieur à" 30 | lt_any: "inférieur à au moins un" 31 | lt_all: "inférieur à tous" 32 | lteq: "inférieur ou égal à" 33 | lteq_any: "inférieur ou égal à au moins un" 34 | lteq_all: "inférieur ou égal à tous" 35 | gt: "supérieur à" 36 | gt_any: "supérieur à au moins un" 37 | gt_all: "supérieur à tous" 38 | gteq: "supérieur ou égal à" 39 | gteq_any: "supérieur ou égal à au moins un" 40 | gteq_all: "supérieur ou égal à tous" 41 | in: "inclus dans" 42 | in_any: "inclus dans au moins un" 43 | in_all: "inclus dans tous" 44 | not_in: "non inclus dans" 45 | not_in_any: "non inclus dans au moins un" 46 | not_in_all: "non inclus dans tous" 47 | cont: "contient" 48 | cont_any: "contient au moins un" 49 | cont_all: "contient tous" 50 | not_cont: "ne contient pas" 51 | not_cont_any: "ne contient pas au moins un" 52 | not_cont_all: "ne contient pas tous" 53 | start: "commence par" 54 | start_any: "commence par au moins un" 55 | start_all: "commence par tous" 56 | not_start: "ne commence pas par" 57 | not_start_any: "ne commence pas par au moins un" 58 | not_start_all: "ne commence pas par tous" 59 | end: "finit par" 60 | end_any: "finit par au moins un" 61 | end_all: "finit par tous" 62 | not_end: "ne finit pas par" 63 | not_end_any: "ne finit pas par au moins un" 64 | not_end_all: "ne finit pas par tous" 65 | 'true': "est vrai" 66 | 'false': "est faux" 67 | present: "est présent" 68 | blank: "est blanc" 69 | 'null': "est null" 70 | not_null: "n'est pas null" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/hu.yml: -------------------------------------------------------------------------------- 1 | hu: 2 | ransack: 3 | search: "keresés" 4 | predicate: "állítás" 5 | and: "és" 6 | or: "vagy" 7 | any: "bármely" 8 | all: "mindegyik" 9 | combinator: "combinator" 10 | attribute: "attribute" 11 | value: "érték" 12 | condition: "feltétel" 13 | sort: "rendezés" 14 | asc: "növekvő" 15 | desc: "csökkenő" 16 | predicates: 17 | eq: "egyenlő" 18 | eq_any: "bármelyikkel egyenlő" 19 | eq_all: "minddel egyenlő" 20 | not_eq: "nem egyenlő" 21 | not_eq_any: "nem egyenlő bármelyikkel" 22 | not_eq_all: "nem egyenlő egyikkel sem" 23 | matches: "egyezik" 24 | matches_any: "bármelyikkel egyezik" 25 | matches_all: "minddel egyezik" 26 | does_not_match: "nem egyezik" 27 | does_not_match_any: "nem egyezik semelyikkel" 28 | does_not_match_all: "nem egyezik az összessel" 29 | lt: "kisebb, mint" 30 | lt_any: "bármelyiknél kisebb" 31 | lt_all: "mindegyiknél kisebb" 32 | lteq: "kisebb vagy egyenlő, mint" 33 | lteq_any: "bármelyiknél kisebb vagy egyenlő" 34 | lteq_all: "mindegyiknél kisebb vagy egyenlő" 35 | gt: "nagyobb, mint" 36 | gt_any: "bármelyiknél nagyobb" 37 | gt_all: "mindegyiknél nagyobb" 38 | gteq: "nagyobb vagy egyenlő, mint" 39 | gteq_any: "bármelyiknél nagyobb vagy egyenlő" 40 | gteq_all: "mindegyiknél nagyobb vagy egyenlő" 41 | in: "értéke" 42 | in_any: "értéke bármelyik" 43 | in_all: "értéke mindegyik" 44 | not_in: "nem ez az értéke" 45 | not_in_any: "értéke egyik sem" 46 | not_in_all: "értéke nem ezek az elemek" 47 | cont: "tartalmazza" 48 | cont_any: "bármelyiket tartalmazza" 49 | cont_all: "mindet tartalmazza" 50 | not_cont: "nem tartalmazza" 51 | not_cont_any: "egyiket sem tartalmazza" 52 | not_cont_all: "nem tartalmazza mindet" 53 | start: "így kezdődik" 54 | start_any: "bármelyikkel kezdődik" 55 | start_all: "ezekkel kezdődik" 56 | not_start: "nem így kezdődik" 57 | not_start_any: "nem ezek egyikével kezdődik" 58 | not_start_all: "nem ezekkel kezdődik" 59 | end: "így végződik" 60 | end_any: "bármelyikkel végződik" 61 | end_all: "ezekkel végződik" 62 | not_end: "nem úgy végződik" 63 | not_end_any: "nem ezek egyikével végződik" 64 | not_end_all: "nem ezekkel végződik" 65 | 'true': "igaz" 66 | 'false': "hamis" 67 | present: "létezik" 68 | blank: "üres" 69 | 'null': "null" 70 | not_null: "nem null" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/id.yml: -------------------------------------------------------------------------------- 1 | id: 2 | ransack: 3 | search: "cari" 4 | predicate: "predikat" 5 | and: "dan" 6 | or: "atau" 7 | any: "apapun" 8 | all: "semua" 9 | combinator: "kombinasi" 10 | attribute: "atribut" 11 | value: "data" 12 | condition: "kondisi" 13 | sort: "urutan" 14 | asc: "ascending" 15 | desc: "descending" 16 | predicates: 17 | eq: "sama dengan" 18 | eq_any: "sama beberapa dengan" 19 | eq_all: "sama seluruhnya dengan" 20 | not_eq: "tidak sama dengan" 21 | not_eq_any: "tidak sama beberapa dengan" 22 | not_eq_all: "tidak semua seluruhnya dengan" 23 | matches: "mirip" 24 | matches_any: "mirip beberapa dengan" 25 | matches_all: "mirip semua dengan" 26 | does_not_match: "tidak mirip dengan" 27 | does_not_match_any: "tidak mirip beberapa dengan" 28 | does_not_match_all: "tidak mirip semua dengan" 29 | lt: "kurang dari" 30 | lt_any: "kurang beberapa dengan" 31 | lt_all: "kurang seluruhnya dengan" 32 | lteq: "kurang lebih" 33 | lteq_any: "kurang lebih beberapa dengan" 34 | lteq_all: "kurang lebih semua dengan" 35 | gt: "lebih besar daripada" 36 | gt_any: "lebih besar beberapa dengan" 37 | gt_all: "lebih besar semua dengan" 38 | gteq: "lebih besar atau sama dengan" 39 | gteq_any: "beberapa lebih besar atau sama dengan" 40 | gteq_all: "semua lebih besar atau sama dengan" 41 | in: "di" 42 | in_any: "di beberapa" 43 | in_all: "di semua" 44 | not_in: "tidak di" 45 | not_in_any: "tidak di beberapa" 46 | not_in_all: "tidak semua di" 47 | cont: "mengandung" 48 | cont_any: "mengandung beberapa" 49 | cont_all: "mengandung semua" 50 | not_cont: "tidak mengandung" 51 | not_cont_any: "tidak mengandung beberapa" 52 | not_cont_all: "tidak mengandung semua" 53 | start: "diawali dengan" 54 | start_any: "diawali beberapa dengan" 55 | start_all: "diawali semua dengan" 56 | not_start: "tidak diawali dengan" 57 | not_start_any: "tidak diawali beberapa dengan" 58 | not_start_all: "tidak diawali semua dengan" 59 | end: "diakhiri dengan" 60 | end_any: "diakhiri beberapa dengan" 61 | end_all: "diakhiri semua dengan" 62 | not_end: "tidak diakhiri dengan" 63 | not_end_any: "tidak diakhiri dengan beberapa" 64 | not_end_all: "tidak diakhiri dengan semua" 65 | 'true': "bernilai benar" 66 | 'false': "bernilai salah" 67 | present: "ada" 68 | blank: "kosong" 69 | 'null': "null" 70 | not_null: "tidak null" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/it.yml: -------------------------------------------------------------------------------- 1 | it: 2 | ransack: 3 | search: "cerca" 4 | predicate: "predicato" 5 | and: "e" 6 | or: "o" 7 | any: "qualsiasi" 8 | all: "tutti" 9 | combinator: "combinatore" 10 | attribute: "attributo" 11 | value: "valore" 12 | condition: "condizione" 13 | sort: "ordinamento" 14 | asc: "crescente" 15 | desc: "decrescente" 16 | predicates: 17 | eq: "uguale a" 18 | eq_any: "uguale ad almeno un" 19 | eq_all: "uguale ad ognuno" 20 | not_eq: "diverso da" 21 | not_eq_any: "diverso da uno qualsiasi" 22 | not_eq_all: "diverso da tutti" 23 | matches: "combacia con" 24 | matches_any: "combacia con almeno un" 25 | matches_all: "combacia con tutti" 26 | does_not_match: "non corrisponde" 27 | does_not_match_any: "non corrisponde ad uno qualsiasi" 28 | does_not_match_all: "non corrisponde con nessuno" 29 | lt: "minore di" 30 | lt_any: "minore di almeno un" 31 | lt_all: "minore di tutti" 32 | lteq: "minore o uguale a" 33 | lteq_any: "minore o uguale ad almeno un" 34 | lteq_all: "minore o uguale a tutti" 35 | gt: "maggiore di" 36 | gt_any: "maggiore di almeno un" 37 | gt_all: "maggiore di tutti" 38 | gteq: "maggiore o uguale a" 39 | gteq_any: "maggiore o uguale ad almeno un" 40 | gteq_all: "maggiore o uguale a tutti" 41 | in: "in" 42 | in_any: "in almeno un" 43 | in_all: "in tutti" 44 | not_in: "non in" 45 | not_in_any: "non in almeno un" 46 | not_in_all: "non in tutti" 47 | cont: "contiene" 48 | cont_any: "contiene almeno un" 49 | cont_all: "contiene tutti" 50 | not_cont: "non contiene" 51 | not_cont_any: "non contiene un qualsiasi" 52 | not_cont_all: "non contiene nessuno" 53 | start: "inizia con" 54 | start_any: "inizia con almeno un" 55 | start_all: "inizia con tutti" 56 | not_start: "non inizia con" 57 | not_start_any: "non inizia con uno qualsiasi" 58 | not_start_all: "non inizia con nessuno" 59 | end: "finisce con" 60 | end_any: "finisce con almeno un" 61 | end_all: "finisce con tutti" 62 | not_end: "non finisce con" 63 | not_end_any: "non finisce con uno qualsiasi" 64 | not_end_all: "non finisce con nessuno" 65 | 'true': "è vero" 66 | 'false': "è falso" 67 | present: "è presente" 68 | blank: "è vuoto" 69 | 'null': "è nullo" 70 | not_null: "non è nullo" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/ja.yml: -------------------------------------------------------------------------------- 1 | ja: 2 | ransack: 3 | search: "検索" 4 | predicate: "は以下である" 5 | and: "と" 6 | or: "あるいは" 7 | any: "いずれか" 8 | all: "全て" 9 | combinator: "組み合わせ" 10 | attribute: "属性" 11 | value: "値" 12 | condition: "状態" 13 | sort: "分類" 14 | asc: "昇順" 15 | desc: "降順" 16 | predicates: 17 | eq: "は以下と等しい" 18 | eq_any: "は以下のいずれかに等しい" 19 | eq_all: "は以下の全てに等しい" 20 | not_eq: "は以下と等しくない" 21 | not_eq_any: "は以下のいずれかに等しくない" 22 | not_eq_all: "は以下の全てと等しくない" 23 | matches: "は以下と合致している" 24 | matches_any: "は以下のいずれかと合致している" 25 | matches_all: "は以下の全てと合致している" 26 | does_not_match: "は以下と合致していない" 27 | does_not_match_any: "は以下のいずれかに合致していない" 28 | does_not_match_all: "は以下の全てに合致していない" 29 | lt: "は以下よりも小さい" 30 | lt_any: "は以下のいずれかより小さい" 31 | lt_all: "は以下の全てよりも小さい" 32 | lteq: "は以下より小さいか等しい" 33 | lteq_any: "は以下のいずれかより小さいか等しい" 34 | lteq_all: "は以下の全てより小さいか等しい" 35 | gt: "は以下より大きい" 36 | gt_any: "は以下のいずれかより大きい" 37 | gt_all: "は以下の全てより大きい" 38 | gteq: "は以下より大きいか等しい" 39 | gteq_any: "は以下のいずれかより大きいか等しい" 40 | gteq_all: "は以下の全てより大きいか等しい" 41 | in: "は以下の範囲内である" 42 | in_any: "は以下のいずれかの範囲内である" 43 | in_all: "は以下の全ての範囲内である" 44 | not_in: "は以下の範囲内でない" 45 | not_in_any: "は以下のいずれかの範囲内でない" 46 | not_in_all: "は以下の全ての範囲内" 47 | cont: "は以下を含む" 48 | cont_any: "はいずれかを含む" 49 | cont_all: "は以下の全てを含む" 50 | not_cont: "は含まない" 51 | not_cont_any: "は以下のいずれかを含まない" 52 | not_cont_all: "は以下の全てを含まない" 53 | start: "は以下で始まる" 54 | start_any: "は以下のどれかで始まる" 55 | start_all: "は以下の全てで始まる" 56 | not_start: "は以下で始まらない" 57 | not_start_any: "は以下のいずれかで始まらない" 58 | not_start_all: "は以下の全てで始まらない" 59 | end: "は以下で終わる" 60 | end_any: "は以下のいずれかで終わる" 61 | end_all: "は以下の全てで終わる" 62 | not_end: "は以下のどれでも終わらない" 63 | not_end_any: "は以下のいずれかで終わらない" 64 | not_end_all: "は以下の全てで終わらない" 65 | 'true': "真" 66 | 'false': "偽" 67 | present: "は存在する" 68 | blank: "は空である" 69 | 'null': "無効" 70 | not_null: "は無効ではない" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/ko.yml: -------------------------------------------------------------------------------- 1 | ko: 2 | ransack: 3 | search: "검색" 4 | predicate: "조건" 5 | and: "그리고" 6 | or: "또는" 7 | any: "어떤 것이든" 8 | all: "모두" 9 | combinator: "조합기" 10 | attribute: "속성" 11 | value: "값" 12 | condition: "조건" 13 | sort: "정렬" 14 | asc: "오름차순" 15 | desc: "내림차순" 16 | predicates: 17 | eq: "같음" 18 | eq_any: "어떤 것이든 같음" 19 | eq_all: "모두 같음" 20 | not_eq: "같지 않음" 21 | not_eq_any: "어떤 것이든 같지 않음" 22 | not_eq_all: "모두 같지 않음" 23 | matches: "일치함" 24 | matches_any: "어떤 것이든 일치함" 25 | matches_all: "모두 일치함" 26 | does_not_match: "일치하지 않음" 27 | does_not_match_any: "어떤 것이든 일치하지 않음" 28 | does_not_match_all: "모두 일치하지 않음" 29 | lt: "보다 작음" 30 | lt_any: "어떤 것이든 보다 작음" 31 | lt_all: "모두 보다 작음" 32 | lteq: "보다 작거나 같음" 33 | lteq_any: "어떤 것이든 보다 작거나 같음" 34 | lteq_all: "모두 보다 작거나 같음" 35 | gt: "보다 큼" 36 | gt_any: "어떤 것이든 보다 큼" 37 | gt_all: "모두 보다 큼" 38 | gteq: "보다 크거나 같음" 39 | gteq_any: "어떤 것이든 보다 크거나 같음" 40 | gteq_all: "모두 보다 크거나 같음" 41 | in: "포함됨" 42 | in_any: "어떤 것이든 포함됨" 43 | in_all: "모두 포함됨" 44 | not_in: "포함되지 않음" 45 | not_in_any: "어떤 것이든 포함되지 않음" 46 | not_in_all: "모두 포함되지 않음" 47 | cont: "포함함" 48 | cont_any: "어떤 것이든 포함함" 49 | cont_all: "모두 포함함" 50 | not_cont: "포함하지 않음" 51 | not_cont_any: "어떤 것이든 포함하지 않음" 52 | not_cont_all: "모두 포함하지 않음" 53 | start: "시작함" 54 | start_any: "어떤 것이든 시작함" 55 | start_all: "모두 시작함" 56 | not_start: "시작하지 않음" 57 | not_start_any: "어떤 것이든 시작하지 않음" 58 | not_start_all: "모두 시작하지 않음" 59 | end: "끝남" 60 | end_any: "어떤 것이든 끝남" 61 | end_all: "모두 끝남" 62 | not_end: "끝나지 않음" 63 | not_end_any: "어떤 것이든 끝나지 않음" 64 | not_end_all: "모두 끝나지 않음" 65 | 'true': "is true" 66 | 'false': "is false" 67 | present: "is present" 68 | blank: "is blank" 69 | 'null': "is null" 70 | not_null: "is not null" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/nl.yml: -------------------------------------------------------------------------------- 1 | nl: 2 | ransack: 3 | search: "zoeken" 4 | predicate: "eigenschap" 5 | and: "en" 6 | or: "of" 7 | any: "enig" 8 | all: "alle" 9 | combinator: "combinator" 10 | attribute: "attribuut" 11 | value: "waarde" 12 | condition: "conditie" 13 | sort: "sorteren" 14 | asc: "oplopend" 15 | desc: "aflopend" 16 | predicates: 17 | eq: "gelijk" 18 | eq_any: "gelijk enig" 19 | eq_all: "gelijk alle" 20 | not_eq: "niet gelijk aan" 21 | not_eq_any: "niet gelijk aan enig" 22 | not_eq_all: "niet gelijk aan alle" 23 | matches: "evenaart" 24 | matches_any: "evenaart enig" 25 | matches_all: "evenaart alle" 26 | does_not_match: "evenaart niet" 27 | does_not_match_any: "evenaart niet voor enig" 28 | does_not_match_all: "evenaart niet voor alle" 29 | lt: "kleiner dan" 30 | lt_any: "kleiner dan enig" 31 | lt_all: "kleiner dan alle" 32 | lteq: "kleiner dan of gelijk aan" 33 | lteq_any: "kleiner dan of gelijk aan enig" 34 | lteq_all: "kleiner dan of gelijk aan alle" 35 | gt: "groter dan" 36 | gt_any: "groter dan enig" 37 | gt_all: "groter dan alle" 38 | gteq: "groter dan of gelijk aan" 39 | gteq_any: "groter dan of gelijk aan enig" 40 | gteq_all: "groter dan of gelijk aan alle" 41 | in: "in" 42 | in_any: "in enig" 43 | in_all: "in alle" 44 | not_in: "niet in" 45 | not_in_any: "niet in enig" 46 | not_in_all: "niet in alle" 47 | cont: "bevat" 48 | cont_any: "bevat enig" 49 | cont_all: "bevat alle" 50 | not_cont: "bevat niet" 51 | not_cont_any: "bevat niet enig" 52 | not_cont_all: "bevat niet alle" 53 | start: "start met" 54 | start_any: "start met enig" 55 | start_all: "start met alle" 56 | not_start: "start niet met" 57 | not_start_any: "start niet met enig" 58 | not_start_all: "start niet met alle" 59 | end: "eindigt met" 60 | end_any: "eindigt met enig" 61 | end_all: "eindigt met alle" 62 | not_end: "eindigt niet met" 63 | not_end_any: "eindigt niet met enig" 64 | not_end_all: "eindigt niet met alle" 65 | 'true': "is waar" 66 | 'false': "is niet waar" 67 | present: "is aanwezig" 68 | blank: "is afwezig" 69 | 'null': "is null" 70 | not_null: "is niet null" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/pt-BR.yml: -------------------------------------------------------------------------------- 1 | pt-BR: 2 | ransack: 3 | search: "pesquisar" 4 | predicate: "predicado" 5 | and: "e" 6 | or: "ou" 7 | any: "algum" 8 | all: "todos" 9 | combinator: "combinador" 10 | attribute: "atributo" 11 | value: "valor" 12 | condition: "condição" 13 | sort: "classificar" 14 | asc: "ascendente" 15 | desc: "descendente" 16 | predicates: 17 | eq: "igual" 18 | eq_any: "igual a algum" 19 | eq_all: "igual a todos" 20 | not_eq: "não é igual a" 21 | not_eq_any: "não é igual a algum" 22 | not_eq_all: "não é igual a todos" 23 | matches: "corresponde" 24 | matches_any: "corresponde a algum" 25 | matches_all: "corresponde a todos" 26 | does_not_match: "não corresponde" 27 | does_not_match_any: "não corresponde a algum" 28 | does_not_match_all: "não corresponde a todos" 29 | lt: "menor que" 30 | lt_any: "menor que algum" 31 | lt_all: "menor que todos" 32 | lteq: "menor ou igual a" 33 | lteq_any: "menor ou igual a algum" 34 | lteq_all: "menor ou igual a todos" 35 | gt: "maior que" 36 | gt_any: "maior que algum" 37 | gt_all: "maior que todos" 38 | gteq: "maior que ou igual a" 39 | gteq_any: "maior que ou igual a algum" 40 | gteq_all: "maior que ou igual a todos" 41 | in: "em" 42 | in_any: "em algum" 43 | in_all: "em todos" 44 | not_in: "não em" 45 | not_in_any: "não em algum" 46 | not_in_all: "não em todos" 47 | cont: "contém" 48 | cont_any: "contém algum" 49 | cont_all: "contém todos" 50 | not_cont: "não contém" 51 | not_cont_any: "não contém algum" 52 | not_cont_all: "não contém todos" 53 | start: "começa com" 54 | start_any: "começa com algum" 55 | start_all: "começa com todos" 56 | not_start: "não começa com" 57 | not_start_any: "não começa com algum" 58 | not_start_all: "não começa com algum" 59 | end: "termina com" 60 | end_any: "termina com algum" 61 | end_all: "termina com todos" 62 | not_end: "não termina com" 63 | not_end_any: "não termina com algum" 64 | not_end_all: "não termina com todos" 65 | 'true': "é verdadeiro" 66 | 'false': "é falso" 67 | present: "está presente" 68 | blank: "está em branco" 69 | 'null': "é nulo" 70 | not_null: "não é nulo" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/ro.yml: -------------------------------------------------------------------------------- 1 | ro: 2 | ransack: 3 | search: "caută" 4 | predicate: "predicat" 5 | and: "și" 6 | or: "sau" 7 | any: "oricare" 8 | all: "toate" 9 | combinator: "combinator" 10 | attribute: "atribut" 11 | value: "valoare" 12 | condition: "condiție" 13 | sort: "sortează" 14 | asc: "crescător" 15 | desc: "descrescător" 16 | predicates: 17 | eq: "egal cu" 18 | eq_any: "egal cu unul din" 19 | eq_all: "egal cu toate" 20 | not_eq: "diferit de" 21 | not_eq_any: "diferit de toate" 22 | not_eq_all: "nu este egal cu toate" 23 | matches: "corespunde" 24 | matches_any: "corespunde cu unul din" 25 | matches_all: "corespunde cu toate" 26 | does_not_match: "nu corespunde" 27 | does_not_match_any: "nu corespunde cu nici un" 28 | does_not_match_all: "nu corespunde cu toate" 29 | lt: "mai mic de" 30 | lt_any: "mai mic decât cel puțin unul din" 31 | lt_all: "mai mic decât toate" 32 | lteq: "mai mic sau egal decât" 33 | lteq_any: "mai mic sau egal decât cel puțin unul din" 34 | lteq_all: "mai mic sau egal decât toate" 35 | gt: "mai mare de" 36 | gt_any: "mai mare decât cel puțin unul din" 37 | gt_all: "mai mare decât toate" 38 | gteq: "mai mare sau egal decât" 39 | gteq_any: "mai mare sau egal decât cel puțin unul din" 40 | gteq_all: "mai mare sau egal decât toate" 41 | in: "inclus în" 42 | in_any: "inclus într-unul din" 43 | in_all: "inclus în toate" 44 | not_in: "nu este inclus în" 45 | not_in_any: "nu este inclus într-unul din" 46 | not_in_all: "nu este inclus în toate" 47 | cont: "conține" 48 | cont_any: "conține unul din" 49 | cont_all: "conține toate" 50 | not_cont: "nu conține" 51 | not_cont_any: "nu conține unul din" 52 | not_cont_all: "nu conține toate" 53 | start: "începe cu" 54 | start_any: "începe cu unul din" 55 | start_all: "începe cu toate" 56 | not_start: "nu începe" 57 | not_start_any: "nu începe cu unul din" 58 | not_start_all: "nu începe cu toate" 59 | end: "se termină cu" 60 | end_any: "se termină cu unul din" 61 | end_all: "se termină cu toate" 62 | not_end: "nu se termină cu" 63 | not_end_any: "nu se termină cu unul din" 64 | not_end_all: "nu se termină cu toate" 65 | 'true': "este adevărat" 66 | 'false': "este fals" 67 | present: "este prezent" 68 | blank: "este gol" 69 | 'null': "este nul" 70 | not_null: "nu este nul" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/ru.yml: -------------------------------------------------------------------------------- 1 | ru: 2 | ransack: 3 | search: "Поиск" 4 | predicate: "predicate" 5 | and: "и" 6 | or: "или" 7 | any: "любое" 8 | all: "все" 9 | combinator: "combinator" 10 | attribute: "аттрибут" 11 | value: "значение" 12 | condition: "условие" 13 | sort: "сортировка" 14 | asc: "по возрастанию" 15 | desc: "по убыванию" 16 | predicates: 17 | eq: "равный" 18 | eq_any: "равный любому" 19 | eq_all: "равный всем" 20 | not_eq: "не равный любому" 21 | not_eq_any: "не равный любому" 22 | not_eq_all: "не равный всем" 23 | matches: "совпадение" 24 | matches_any: "совпадение любому" 25 | matches_all: "совпадение всем" 26 | does_not_match: "не совпадение" 27 | does_not_match_any: "не совпадение любому" 28 | does_not_match_all: "не совпадение всем" 29 | lt: "меньше чем" 30 | lt_any: "меньше чем любое" 31 | lt_all: "меньше чем все" 32 | lteq: "меньше чем или равен" 33 | lteq_any: "меньше чем или равен любому" 34 | lteq_all: "меньше чем или равен всем" 35 | gt: "больше чем" 36 | gt_any: "больше чем любое" 37 | gt_all: "больше чем все" 38 | gteq: "больше чем или равен" 39 | gteq_any: "больше чем или равен любому" 40 | gteq_all: "больше чем или равен всем" 41 | in: "в" 42 | in_any: "в любом из" 43 | in_all: "во всех" 44 | not_in: "нет в" 45 | not_in_any: "не в каком из" 46 | not_in_all: "нет во всех" 47 | cont: "содержит" 48 | cont_any: "содержит любое" 49 | cont_all: "содержит все" 50 | not_cont: "не содержит" 51 | not_cont_any: "не содержит любое" 52 | not_cont_all: "не содержит все" 53 | start: "начинается с" 54 | start_any: "начинается с любого" 55 | start_all: "начинается с всего" 56 | not_start: "не начинается с" 57 | not_start_any: "не начинается с любого" 58 | not_start_all: "не начинается со всего" 59 | end: "кончается с" 60 | end_any: "кончается с любого" 61 | end_all: "кончается со всего" 62 | not_end: "не кончается с" 63 | not_end_any: "не кончается с любого" 64 | not_end_all: "не кончается со всего" 65 | 'true': "верно" 66 | 'false': "не верно" 67 | present: "настоящее" 68 | blank: "пусто" 69 | 'null': "нулевой" 70 | not_null: "не нулевой" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/sk.yml: -------------------------------------------------------------------------------- 1 | sk: 2 | ransack: 3 | search: "vyhľadávanie" 4 | predicate: "predikát" 5 | and: "a" 6 | or: "alebo" 7 | any: "akýkoľvek" 8 | all: "každý" 9 | combinator: "kombinátor" 10 | attribute: "atribút" 11 | value: "hodnota" 12 | condition: "podmienka" 13 | sort: "poradie" 14 | asc: "vzostupne" 15 | desc: "zostupne" 16 | predicates: 17 | eq: "sa rovná" 18 | eq_any: "sa rovná akémukoľvek" 19 | eq_all: "sa rovná všetkým" 20 | not_eq: "sa nerovná" 21 | not_eq_any: "sa nerovná akémukoľvek" 22 | not_eq_all: "sa nerovná všetkým" 23 | matches: "zodpovedá" 24 | matches_any: "zodpovedá akémukoľvek" 25 | matches_all: "zodpovedá všetkým" 26 | does_not_match: "nezodpovedá" 27 | does_not_match_any: "nezodpovedá akémukoľvek" 28 | does_not_match_all: "nezodpovedá všetkým" 29 | lt: "menší ako" 30 | lt_any: "menší ako akýkoľvek" 31 | lt_all: "menší ako všetky" 32 | lteq: "menší alebo rovný" 33 | lteq_any: "menší alebo rovný akémukoľvek" 34 | lteq_all: "menší alebo rovný všetkým" 35 | gt: "väčší ako" 36 | gt_any: "väčší ako akýkoľvek" 37 | gt_all: "väčší ako všetky" 38 | gteq: "väčší alebo rovný" 39 | gteq_any: "väčší alebo rovný akémukoľvek" 40 | gteq_all: "väčší alebo rovný všetkým" 41 | in: "v" 42 | in_any: "v akejkoľvek" 43 | in_all: "vo všetkých" 44 | not_in: "nie je v" 45 | not_in_any: "nie je v akejkoľvek" 46 | not_in_all: "nie je vo všetkých" 47 | cont: "obsahuje" 48 | cont_any: "obsahuje akúkoľvek" 49 | cont_all: "obsahuje všetky" 50 | not_cont: "neobsahuje" 51 | not_cont_any: "neobsahuje akúkoľvek" 52 | not_cont_all: "neobsahuje všetky" 53 | start: "začína na" 54 | start_any: "začína s akoukoľvek" 55 | start_all: "začína so všetkými" 56 | not_start: "nezačíná s" 57 | not_start_any: "nezačíná s akoukoľvek" 58 | not_start_all: "nezačíná so všetkými" 59 | end: "končí s" 60 | end_any: "končí s akoukoľvek" 61 | end_all: "končí so všetkými" 62 | not_end: "nekončí s" 63 | not_end_any: "nekončí s akoukoľvek" 64 | not_end_all: "nekončí so všetkými" 65 | 'true': "je pravdivé" 66 | 'false': "nie je pravdivé" 67 | present: "je vyplnené" 68 | blank: "je prázdne" 69 | 'null': "je null" 70 | not_null: "nie je null" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/sv.yml: -------------------------------------------------------------------------------- 1 | sv: 2 | ransack: 3 | search: "sök" 4 | predicate: "predikat" 5 | and: "och" 6 | or: "eller" 7 | any: "vilken som" 8 | all: "alla" 9 | combinator: "kombinator" 10 | attribute: "attribut" 11 | value: "värde" 12 | condition: "villkor" 13 | sort: "sortera" 14 | asc: "stigande" 15 | desc: "fallande" 16 | predicates: 17 | eq: "lika med" 18 | eq_any: "lika med vilket som" 19 | eq_all: "lika med alla" 20 | not_eq: "inte lika med" 21 | not_eq_any: "inte lika med någon" 22 | not_eq_all: "inte lika med alla" 23 | matches: "matchar" 24 | matches_any: "matchar någon" 25 | matches_all: "matchar alla" 26 | does_not_match: "matchar inte" 27 | does_not_match_any: "matchar inte någon" 28 | does_not_match_all: "matchar inte alla" 29 | lt: "mindre än" 30 | lt_any: "mindre än någon" 31 | lt_all: "mindre än alla" 32 | lteq: "mindre än eller lika med" 33 | lteq_any: "mindre än eller lika med någon" 34 | lteq_all: "mindre än eller lika med alla" 35 | gt: "större än" 36 | gt_any: "större än någon" 37 | gt_all: "större än alla" 38 | gteq: "större än eller lika med" 39 | gteq_any: "större än eller lika med någon" 40 | gteq_all: "större än eller lika med alla" 41 | in: "i" 42 | in_any: "i någon" 43 | in_all: "i alla" 44 | not_in: "inte i" 45 | not_in_any: "inte i någon" 46 | not_in_all: "inte i alla" 47 | cont: "innehåller" 48 | cont_any: "innehåller någon" 49 | cont_all: "innehåller alla" 50 | not_cont: "innehåller inte" 51 | not_cont_any: "innehåller inte någon" 52 | not_cont_all: "innehåller inte alla" 53 | start: "börjar med" 54 | start_any: "börjar med någon" 55 | start_all: "börjar med alla" 56 | not_start: "börjar inte med" 57 | not_start_any: "börjar inte med någon" 58 | not_start_all: "börjar inte med alla" 59 | end: "slutar med" 60 | end_any: "slutar med någon" 61 | end_all: "slutar med alla" 62 | not_end: "slutar inte med" 63 | not_end_any: "slutar inte med någon" 64 | not_end_all: "slutar inte med alla" 65 | 'true': "är sant" 66 | 'false': "är falskt" 67 | present: "existerar" 68 | blank: "är tom" 69 | 'null': "är null" 70 | not_null: "är inte null" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/tr.yml: -------------------------------------------------------------------------------- 1 | tr: 2 | ransack: 3 | search: "ara" 4 | predicate: "doğrula" 5 | and: "ve" 6 | or: "veya" 7 | any: "herhangi" 8 | all: "hepsi" 9 | combinator: "birleştirici" 10 | attribute: "nitelik" 11 | value: "değer" 12 | condition: "şart" 13 | sort: "sırala" 14 | asc: "artan" 15 | desc: "azalan" 16 | predicates: 17 | eq: "eşit" 18 | eq_any: "herhangi birine eşit" 19 | eq_all: "hepsine eşit" 20 | not_eq: "eşit değil" 21 | not_eq_any: "herhangi birine eşit değil" 22 | not_eq_all: "hiçbirine eşit değil" 23 | matches: "eşleşen" 24 | matches_any: "herhangi biri ile eşleşen" 25 | matches_all: "hepsi ile eşleşen" 26 | does_not_match: "eşleşmeyen" 27 | does_not_match_any: "herhangi biri ile eşleşmeyen" 28 | does_not_match_all: "hiçbiri ile eşleşmeyen" 29 | lt: "daha küçük" 30 | lt_any: "herhangi birinden küçük" 31 | lt_all: "hepsinden küçük" 32 | lteq: "daha küçük veya eşit" 33 | lteq_any: "daha küçük veya herhangi birine eşit" 34 | lteq_all: "daha küçük veya hepsine eşit" 35 | gt: "daha büyük " 36 | gt_any: "herhangi birinden daha büyük" 37 | gt_all: "hepsinden daha büyük" 38 | gteq: "daha büyük veya eşit" 39 | gteq_any: "daha büyük veya herhangi birine eşit" 40 | gteq_all: "daha büyük veya hepsine eşit" 41 | in: "içinde" 42 | in_any: "herhangi birinde" 43 | in_all: "hepsinde" 44 | not_in: "içinde değil" 45 | not_in_any: "herhangi birinde değil" 46 | not_in_all: "hiçbirinde değil" 47 | cont: "içeren" 48 | cont_any: "herhangi birini içeren" 49 | cont_all: "hepsini içeren" 50 | not_cont: "içermeyen" 51 | not_cont_any: "herhangi birini içermeyen" 52 | not_cont_all: "hiçbirini birini içermeyen" 53 | start: "ile başlayan" 54 | start_any: "herhangi biriyle başlayan" 55 | start_all: "hepsiyle başlayan" 56 | not_start: "ile başlamayan" 57 | not_start_any: "herhangi biriyle başlamayan" 58 | not_start_all: "hiçbiriyle başlamayan" 59 | end: "ile biten" 60 | end_any: "herhangi biriyle biten" 61 | end_all: "hepsi ile biten" 62 | not_end: "ile bitmeyen" 63 | not_end_any: "herhangi biriyle bitmeyen" 64 | not_end_all: "hiçbiriyle bitmeyen" 65 | 'true': "doğru" 66 | 'false': "yanlış" 67 | present: "mevcut" 68 | blank: "boş" 69 | 'null': "geçersiz" 70 | not_null: "geçerli" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | ransack: 3 | search: "搜索" 4 | predicate: "基于(predicate)" 5 | and: "并且" 6 | or: "或者" 7 | any: "任意" 8 | all: "所有" 9 | combinator: "条件组合(combinator)" 10 | attribute: "属性" 11 | value: "数值" 12 | condition: "条件" 13 | sort: "排序" 14 | asc: "升序" 15 | desc: "降序" 16 | predicates: 17 | eq: "等于" 18 | eq_any: "等于任意值" 19 | eq_all: "等于所有值" 20 | not_eq: "不等于" 21 | not_eq_any: "不等于任意值" 22 | not_eq_all: "不等于所有值" 23 | matches: "符合" 24 | matches_any: "符合任意条件" 25 | matches_all: "符合所有条件" 26 | does_not_match: "不符合" 27 | does_not_match_any: "符合任意条件" 28 | does_not_match_all: "不符合所有条件" 29 | lt: "小于" 30 | lt_any: "小于任意一个值" 31 | lt_all: "小于所有值" 32 | lteq: "小于等于" 33 | lteq_any: "小于等于任意一个值" 34 | lteq_all: "小于等于所有值" 35 | gt: "大于" 36 | gt_any: "大于任意一个值" 37 | gt_all: "大于所有值" 38 | gteq: "大于等于" 39 | gteq_any: "大于等于任意一个值" 40 | gteq_all: "大于等于所有值" 41 | in: "被包含" 42 | in_any: "被任意值包含" 43 | in_all: "被所有值包含" 44 | not_in: "不被包含" 45 | not_in_any: "不被任意值包含" 46 | not_in_all: "不被所有值包含" 47 | cont: "包含" 48 | cont_any: "包含任意一个值" 49 | cont_all: "包含所有值" 50 | not_cont: "不包含" 51 | not_cont_any: "不包含任意一个值" 52 | not_cont_all: "不包含所有值" 53 | start: "始于" 54 | start_any: "始于任一值" 55 | start_all: "始于任意值" 56 | not_start: "非始于" 57 | not_start_any: "非始于任一值" 58 | not_start_all: "非始于任意值" 59 | end: "止于" 60 | end_any: "止于任一值" 61 | end_all: "止于任意值" 62 | not_end: "非止于" 63 | not_end_any: "非止于任一值" 64 | not_end_all: "非止于任意值" 65 | 'true': "等于true" 66 | 'false': "等于false" 67 | present: "有值" 68 | blank: "为空" 69 | 'null': "是null" 70 | not_null: "不是null" 71 | -------------------------------------------------------------------------------- /lib/ransack/locale/zh-TW.yml: -------------------------------------------------------------------------------- 1 | zh-TW: 2 | ransack: 3 | search: "搜尋" 4 | predicate: "基於" 5 | and: "而且" 6 | or: "或者" 7 | any: "任何" 8 | all: "所有" 9 | combinator: "條件組合" 10 | attribute: "屬性" 11 | value: "數值" 12 | condition: "條件" 13 | sort: "排序" 14 | asc: "升冪排序" 15 | desc: "降冪排序" 16 | predicates: 17 | eq: "等於" 18 | eq_any: "等於任何一個值" 19 | eq_all: "等於所有值" 20 | not_eq: "不等於" 21 | not_eq_any: "不等於任何一個值" 22 | not_eq_all: "不等於所有值" 23 | matches: "符合" 24 | matches_any: "符合任何一個條件" 25 | matches_all: "符合所有條件" 26 | does_not_match: "不符合" 27 | does_not_match_any: "不符合任何一個條件" 28 | does_not_match_all: "不符合所有條件" 29 | lt: "小於" 30 | lt_any: "小於任何一個值" 31 | lt_all: "小於所有值" 32 | lteq: "小於或等於" 33 | lteq_any: "小於或等於任何一個值" 34 | lteq_all: "小於或等於所有值" 35 | gt: "大於" 36 | gt_any: "大於任何一個值" 37 | gt_all: "大於所有值" 38 | gteq: "大於或等於" 39 | gteq_any: "大於或等於任何一個值" 40 | gteq_all: "大於或等於所有值" 41 | in: "被包含於" 42 | in_any: "被包含於任何一個值" 43 | in_all: "被包含於所有值" 44 | not_in: "不被包含於" 45 | not_in_any: "不被包含於任何一個值" 46 | not_in_all: "不被包含於所有值" 47 | cont: "包含" 48 | cont_any: "包含任何一個值" 49 | cont_all: "包含所有值" 50 | not_cont: "不包含" 51 | not_cont_any: "不包含任何一個值" 52 | not_cont_all: "不包含所有值" 53 | start: "以某個值開始" 54 | start_any: "以任何一個值開始" 55 | start_all: "以所有值開始" 56 | not_start: "不以某個值開始" 57 | not_start_any: "不以任何一值開始" 58 | not_start_all: "不以所有值開始" 59 | end: "以某個值結尾" 60 | end_any: "以任何一個值結尾" 61 | end_all: "以所有值結尾" 62 | not_end: "不以某個值結尾" 63 | not_end_any: "不以任何一個值結尾" 64 | not_end_all: "不以所有值結尾" 65 | 'true': "為真" 66 | 'false': "為假" 67 | present: "有值" 68 | blank: "為空" 69 | 'null': "為 null" 70 | not_null: "不為 null" 71 | -------------------------------------------------------------------------------- /lib/ransack/naming.rb: -------------------------------------------------------------------------------- 1 | module Ransack 2 | module Naming 3 | 4 | def self.included(base) 5 | base.extend ClassMethods 6 | end 7 | 8 | def persisted? 9 | false 10 | end 11 | 12 | def to_key 13 | nil 14 | end 15 | 16 | def to_param 17 | nil 18 | end 19 | 20 | def to_model 21 | self 22 | end 23 | 24 | def model_name 25 | self.class.model_name 26 | end 27 | end 28 | 29 | class Name < String 30 | attr_reader :singular, :plural, :element, :collection, :partial_path, 31 | :human, :param_key, :route_key, :i18n_key 32 | alias_method :cache_key, :collection 33 | 34 | def initialize 35 | super(Constants::CAP_SEARCH) 36 | @singular = Constants::SEARCH 37 | @plural = Constants::SEARCHES 38 | @element = Constants::SEARCH 39 | @human = Constants::CAP_SEARCH 40 | @collection = Constants::RANSACK_SLASH_SEARCHES 41 | @partial_path = Constants::RANSACK_SLASH_SEARCHES_SLASH_SEARCH 42 | @param_key = Constants::Q 43 | @route_key = Constants::SEARCHES 44 | @i18n_key = :ransack 45 | end 46 | end 47 | 48 | module ClassMethods 49 | def model_name 50 | @_model_name ||= Name.new 51 | end 52 | 53 | def i18n_scope 54 | :ransack 55 | end 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /lib/ransack/nodes/attribute.rb: -------------------------------------------------------------------------------- 1 | module Ransack 2 | module Nodes 3 | class Attribute < Node 4 | include Bindable 5 | 6 | attr_reader :name, :ransacker_args 7 | 8 | delegate :blank?, :present?, to: :name 9 | delegate :engine, to: :context 10 | 11 | def initialize(context, name = nil, ransacker_args = []) 12 | super(context) 13 | self.name = name unless name.blank? 14 | @ransacker_args = ransacker_args 15 | end 16 | 17 | def name=(name) 18 | @name = name 19 | end 20 | 21 | def valid? 22 | bound? && attr && 23 | context.klassify(parent).ransackable_attributes(context.auth_object) 24 | .include?(attr_name.split('.').last) 25 | end 26 | 27 | def associated_collection? 28 | parent.respond_to?(:reflection) && parent.reflection.collection? 29 | end 30 | 31 | def type 32 | if ransacker 33 | ransacker.type 34 | else 35 | context.type_for(self) 36 | end 37 | end 38 | 39 | def eql?(other) 40 | self.class == other.class && 41 | self.name == other.name 42 | end 43 | alias :== :eql? 44 | 45 | def hash 46 | self.name.hash 47 | end 48 | 49 | def persisted? 50 | false 51 | end 52 | 53 | def inspect 54 | "Attribute <#{name}>" 55 | end 56 | 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/ransack/nodes/bindable.rb: -------------------------------------------------------------------------------- 1 | module Ransack 2 | module Nodes 3 | module Bindable 4 | 5 | attr_accessor :parent, :attr_name 6 | 7 | def attr 8 | @attr ||= get_arel_attribute 9 | end 10 | alias :arel_attribute :attr 11 | 12 | def ransacker 13 | klass._ransackers[attr_name] 14 | end 15 | 16 | def klass 17 | @klass ||= context.klassify(parent) 18 | end 19 | 20 | def bound? 21 | attr_name.present? && parent.present? 22 | end 23 | 24 | def reset_binding! 25 | @parent = @attr_name = @attr = @klass = nil 26 | end 27 | 28 | private 29 | 30 | def get_arel_attribute 31 | if ransacker 32 | ransacker.attr_from(self) 33 | else 34 | get_attribute 35 | end 36 | end 37 | 38 | def get_attribute 39 | if is_alias_attribute? 40 | context.table_for(parent)[parent.base_klass.attribute_aliases[attr_name]] 41 | else 42 | context.table_for(parent)[attr_name] 43 | end 44 | end 45 | 46 | def is_alias_attribute? 47 | Ransack::SUPPORTS_ATTRIBUTE_ALIAS && 48 | parent.base_klass.attribute_aliases.key?(attr_name) 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/ransack/nodes/node.rb: -------------------------------------------------------------------------------- 1 | module Ransack 2 | module Nodes 3 | class Node 4 | attr_reader :context 5 | delegate :contextualize, to: :context 6 | class_attribute :i18n_words 7 | class_attribute :i18n_aliases 8 | self.i18n_words = [] 9 | self.i18n_aliases = {} 10 | 11 | class << self 12 | def i18n_word(*args) 13 | self.i18n_words += args.map(&:to_s) 14 | end 15 | 16 | def i18n_alias(opts = {}) 17 | self.i18n_aliases.merge! Hash[opts.map { |k, v| [k.to_s, v.to_s] }] 18 | end 19 | end 20 | 21 | def initialize(context) 22 | @context = context 23 | end 24 | 25 | def translate(key, options = {}) 26 | key = i18n_aliases[key.to_s] if i18n_aliases.has_key?(key.to_s) 27 | if i18n_words.include?(key.to_s) 28 | Translate.word(key) 29 | end 30 | end 31 | 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/ransack/nodes/sort.rb: -------------------------------------------------------------------------------- 1 | module Ransack 2 | module Nodes 3 | class Sort < Node 4 | include Bindable 5 | 6 | attr_reader :name, :dir, :ransacker_args 7 | i18n_word :asc, :desc 8 | 9 | class << self 10 | def extract(context, str) 11 | return if str.blank? 12 | attr, direction = str.split(/\s+/, 2) 13 | self.new(context).build(name: attr, dir: direction) 14 | end 15 | end 16 | 17 | def build(params) 18 | params.with_indifferent_access.each do |key, value| 19 | if key.match(/^(name|dir|ransacker_args)$/) 20 | self.send("#{key}=", value) 21 | end 22 | end 23 | 24 | self 25 | end 26 | 27 | def valid? 28 | bound? && attr && 29 | context.klassify(parent).ransortable_attributes(context.auth_object) 30 | .include?(attr_name) 31 | end 32 | 33 | def name=(name) 34 | @name = context.ransackable_alias(name) || name 35 | context.bind(self, @name) 36 | end 37 | 38 | def dir=(dir) 39 | dir = dir.downcase if dir 40 | @dir = 41 | if dir == 'asc'.freeze || dir == 'desc'.freeze 42 | dir 43 | else 44 | 'asc'.freeze 45 | end 46 | end 47 | 48 | def ransacker_args=(ransack_args) 49 | @ransacker_args = ransack_args 50 | end 51 | 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/ransack/nodes/value.rb: -------------------------------------------------------------------------------- 1 | module Ransack 2 | module Nodes 3 | class Value < Node 4 | attr_accessor :value 5 | delegate :present?, :blank?, to: :value 6 | 7 | def initialize(context, value = nil) 8 | super(context) 9 | @value = value 10 | end 11 | 12 | def persisted? 13 | false 14 | end 15 | 16 | def eql?(other) 17 | self.class == other.class && self.value == other.value 18 | end 19 | alias :== :eql? 20 | 21 | def hash 22 | value.hash 23 | end 24 | 25 | def cast(type) 26 | case type 27 | when :date 28 | cast_to_date(value) 29 | when :datetime, :timestamp, :time, :timestamptz 30 | cast_to_time(value) 31 | when :boolean 32 | cast_to_boolean(value) 33 | when :integer 34 | cast_to_integer(value) 35 | when :float 36 | cast_to_float(value) 37 | when :decimal 38 | cast_to_decimal(value) 39 | when :money 40 | cast_to_money(value) 41 | else 42 | cast_to_string(value) 43 | end 44 | end 45 | 46 | def cast_array 47 | if value.is_a?(Array) 48 | cast_to_date(value) 49 | else 50 | value 51 | end 52 | end 53 | 54 | def cast_to_date(val) 55 | if val.respond_to?(:to_date) 56 | val.to_date rescue nil 57 | else 58 | y, m, d = *[val].flatten 59 | m ||= 1 60 | d ||= 1 61 | Date.new(y, m, d) rescue nil 62 | end 63 | end 64 | 65 | def cast_to_time(val) 66 | if val.is_a?(Array) 67 | Time.zone.local(*val) rescue nil 68 | else 69 | unless val.acts_like?(:time) 70 | val = val.is_a?(String) ? Time.zone.parse(val) : val.to_time rescue val 71 | end 72 | val.in_time_zone rescue nil 73 | end 74 | end 75 | 76 | def cast_to_boolean(val) 77 | if Constants::TRUE_VALUES.include?(val) 78 | true 79 | elsif Constants::FALSE_VALUES.include?(val) 80 | false 81 | else 82 | nil 83 | end 84 | end 85 | 86 | def cast_to_string(val) 87 | val.respond_to?(:to_s) ? val.to_s : String.new(val) 88 | end 89 | 90 | def cast_to_integer(val) 91 | val.blank? ? nil : val.to_i 92 | end 93 | 94 | def cast_to_float(val) 95 | val.blank? ? nil : val.to_f 96 | end 97 | 98 | def cast_to_decimal(val) 99 | if val.blank? 100 | nil 101 | elsif val.class == BigDecimal 102 | val 103 | elsif val.respond_to?(:to_d) 104 | val.to_d 105 | else 106 | val.to_s.to_d 107 | end 108 | end 109 | 110 | def cast_to_money(val) 111 | val.blank? ? nil : val.to_f.to_s 112 | end 113 | 114 | def inspect 115 | "Value <#{value}>" 116 | end 117 | 118 | def array_of_arrays?(val) 119 | Array === val && Array === val.first 120 | end 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /lib/ransack/predicate.rb: -------------------------------------------------------------------------------- 1 | module Ransack 2 | class Predicate 3 | attr_reader :name, :arel_predicate, :type, :formatter, :validator, 4 | :compound, :wants_array, :case_insensitive 5 | 6 | class << self 7 | 8 | def names 9 | Ransack.predicates.keys 10 | end 11 | 12 | def named(name) 13 | Ransack.predicates[(name || Ransack.options[:default_predicate]).to_s] 14 | end 15 | 16 | def detect_and_strip_from_string!(str) 17 | detect_from_string str, chomp: true 18 | end 19 | 20 | def detect_from_string(str, chomp: false) 21 | return unless str 22 | 23 | Ransack.predicates.sorted_names_with_underscores.each do |predicate, underscored| 24 | if str.end_with? underscored 25 | str.chomp! underscored if chomp 26 | return predicate 27 | end 28 | end 29 | 30 | nil 31 | end 32 | 33 | end 34 | 35 | def initialize(opts = {}) 36 | @name = opts[:name] 37 | @arel_predicate = opts[:arel_predicate] 38 | @type = opts[:type] 39 | @formatter = opts[:formatter] 40 | @validator = opts[:validator] || 41 | lambda { |v| v.respond_to?(:empty?) ? !v.empty? : !v.nil? } 42 | @compound = opts[:compound] 43 | @wants_array = opts.fetch(:wants_array, 44 | @compound || Constants::IN_NOT_IN.include?(@arel_predicate)) 45 | @case_insensitive = opts[:case_insensitive] 46 | end 47 | 48 | def eql?(other) 49 | self.class == other.class && 50 | self.name == other.name 51 | end 52 | alias :== :eql? 53 | 54 | def hash 55 | name.hash 56 | end 57 | 58 | def format(val) 59 | if formatter 60 | formatter.call(val) 61 | else 62 | val 63 | end 64 | end 65 | 66 | def validate(vals, type = @type) 67 | vals.any? { |v| validator.call(type ? v.cast(type) : v.value) } 68 | end 69 | 70 | def negative? 71 | @name.include?("not_".freeze) 72 | end 73 | 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/ransack/ransacker.rb: -------------------------------------------------------------------------------- 1 | module Ransack 2 | class Ransacker 3 | 4 | attr_reader :name, :type, :formatter, :args 5 | 6 | delegate :call, to: :@callable 7 | 8 | def initialize(klass, name, opts = {}, &block) 9 | @klass, @name = klass, name 10 | 11 | @type = opts[:type] || :string 12 | @args = opts[:args] || [:parent] 13 | @formatter = opts[:formatter] 14 | @callable = opts[:callable] || block || 15 | (@klass.method(name) if @klass.respond_to?(name)) || 16 | proc { |parent| parent.table[name] } 17 | end 18 | 19 | def attr_from(bindable) 20 | call(*args.map { |arg| bindable.send(arg) }) 21 | end 22 | 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/ransack/translate.rb: -------------------------------------------------------------------------------- 1 | require 'i18n' 2 | 3 | I18n.load_path += Dir[ 4 | File.join(File.dirname(__FILE__), 'locale'.freeze, '*.yml'.freeze) 5 | ] 6 | 7 | module Ransack 8 | module Translate 9 | class << self 10 | def word(key, options = {}) 11 | I18n.translate(:"ransack.#{key}", default: key.to_s) 12 | end 13 | 14 | def predicate(key, options = {}) 15 | I18n.translate(:"ransack.predicates.#{key}", default: key.to_s) 16 | end 17 | 18 | def attribute(key, options = {}) 19 | unless context = options.delete(:context) 20 | raise ArgumentError, "A context is required to translate attributes" 21 | end 22 | 23 | original_name = key.to_s 24 | base_class = context.klass 25 | base_ancestors = base_class.ancestors.select { 26 | |x| x.respond_to?(:model_name) 27 | } 28 | attributes_str = original_name.dup # will be modified by ⬇ 29 | predicate = Predicate.detect_and_strip_from_string!(attributes_str) 30 | attribute_names = attributes_str.split(/_and_|_or_/) 31 | combinator = attributes_str =~ /_and_/ ? :and : :or 32 | defaults = base_ancestors.map do |klass| 33 | "ransack.attributes.#{i18n_key(klass)}.#{original_name}".to_sym 34 | end 35 | defaults << options.delete(:default) if options[:default] 36 | 37 | translated_names = attribute_names.map do |name| 38 | attribute_name(context, name, options[:include_associations]) 39 | end 40 | 41 | interpolations = { 42 | attributes: translated_names.join(" #{Translate.word(combinator)} ") 43 | } 44 | 45 | if predicate 46 | defaults << "%{attributes} %{predicate}".freeze 47 | interpolations[:predicate] = Translate.predicate(predicate) 48 | else 49 | defaults << "%{attributes}".freeze 50 | end 51 | 52 | options.reverse_merge! count: 1, default: defaults 53 | I18n.translate(defaults.shift, **options.merge(interpolations)) 54 | end 55 | 56 | def association(key, options = {}) 57 | unless context = options.delete(:context) 58 | raise ArgumentError, "A context is required to translate associations" 59 | end 60 | 61 | defaults = 62 | if key.blank? 63 | [:"ransack.models.#{i18n_key(context.klass)}", 64 | :"#{context.klass.i18n_scope}.models.#{i18n_key(context.klass)}"] 65 | else 66 | [:"ransack.associations.#{i18n_key(context.klass)}.#{key}"] 67 | end 68 | defaults << context.traverse(key).model_name.human 69 | options = { count: 1, default: defaults } 70 | I18n.translate(defaults.shift, **options) 71 | end 72 | 73 | private 74 | 75 | def attribute_name(context, name, include_associations = nil) 76 | @context, @name = context, name 77 | @assoc_path = context.association_path(name) 78 | @attr_name = @name.sub(/^#{@assoc_path}_/, ''.freeze) 79 | associated_class = @context.traverse(@assoc_path) if @assoc_path.present? 80 | @include_associated = include_associations && associated_class 81 | 82 | defaults = default_attribute_name << fallback_args 83 | options = { count: 1, default: defaults } 84 | interpolations = build_interpolations(associated_class) 85 | 86 | I18n.translate(defaults.shift, **options.merge(interpolations)) 87 | end 88 | 89 | def default_attribute_name 90 | ["ransack.attributes.#{i18n_key(@context.klass)}.#{@name}".to_sym] 91 | end 92 | 93 | def fallback_args 94 | if @include_associated 95 | '%{association_name} %{attr_fallback_name}'.freeze 96 | else 97 | '%{attr_fallback_name}'.freeze 98 | end 99 | end 100 | 101 | def build_interpolations(associated_class) 102 | { 103 | attr_fallback_name: attr_fallback_name(associated_class), 104 | association_name: association_name 105 | }.reject { |_, value| value.nil? } 106 | end 107 | 108 | def attr_fallback_name(associated_class) 109 | I18n.t( 110 | :"ransack.attributes.#{fallback_class(associated_class)}.#{@attr_name}", 111 | default: default_interpolation(associated_class) 112 | ) 113 | end 114 | 115 | def fallback_class(associated_class) 116 | i18n_key(associated_class || @context.klass) 117 | end 118 | 119 | def association_name 120 | association(@assoc_path, context: @context) if @include_associated 121 | end 122 | 123 | def default_interpolation(associated_class) 124 | [ 125 | associated_attribute(associated_class), 126 | ".attributes.#{@attr_name}".to_sym, 127 | @attr_name.humanize 128 | ].flatten 129 | end 130 | 131 | def associated_attribute(associated_class) 132 | if associated_class 133 | translated_attribute(associated_class) 134 | else 135 | translated_ancestor_attributes 136 | end 137 | end 138 | 139 | def translated_attribute(associated_class) 140 | key = "#{associated_class.i18n_scope}.attributes.#{ 141 | i18n_key(associated_class)}.#{@attr_name}" 142 | ["#{key}.one".to_sym, key.to_sym] 143 | end 144 | 145 | def translated_ancestor_attributes 146 | @context.klass.ancestors 147 | .select { |ancestor| ancestor.respond_to?(:model_name) } 148 | .map { |ancestor| translated_attribute(ancestor) } 149 | end 150 | 151 | def i18n_key(klass) 152 | klass.model_name.i18n_key 153 | end 154 | end 155 | end 156 | end 157 | -------------------------------------------------------------------------------- /lib/ransack/version.rb: -------------------------------------------------------------------------------- 1 | module Ransack 2 | VERSION = '4.3.0' 3 | end 4 | -------------------------------------------------------------------------------- /lib/ransack/visitor.rb: -------------------------------------------------------------------------------- 1 | module Ransack 2 | class Visitor 3 | 4 | def accept(object) 5 | visit(object) 6 | end 7 | 8 | def can_accept?(object) 9 | respond_to? DISPATCH[object.class] 10 | end 11 | 12 | def visit_Array(object) 13 | object.map { |o| accept(o) }.compact 14 | end 15 | 16 | def visit_Ransack_Nodes_Condition(object) 17 | object.arel_predicate if object.valid? 18 | end 19 | 20 | def visit_Ransack_Nodes_Grouping(object) 21 | if object.combinator == Constants::OR 22 | visit_or(object) 23 | else 24 | visit_and(object) 25 | end 26 | end 27 | 28 | def visit_and(object) 29 | nodes = object.values.map { |o| accept(o) }.compact 30 | return nil unless nodes.size > 0 31 | 32 | if nodes.size > 1 33 | Arel::Nodes::Grouping.new(Arel::Nodes::And.new(nodes)) 34 | else 35 | nodes.first 36 | end 37 | end 38 | 39 | def visit_or(object) 40 | nodes = object.values.map { |o| accept(o) }.compact 41 | nodes.inject(&:or) 42 | end 43 | 44 | def quoted?(object) 45 | case object 46 | when Arel::Nodes::SqlLiteral, Bignum, Fixnum 47 | false 48 | else 49 | true 50 | end 51 | end 52 | 53 | def visit(object) 54 | send(DISPATCH[object.class], object) 55 | end 56 | 57 | def visit_Ransack_Nodes_Sort(object) 58 | if object.valid? 59 | if object.attr.is_a?(Arel::Attributes::Attribute) 60 | object.attr.send(object.dir) 61 | else 62 | ordered(object) 63 | end 64 | else 65 | scope_name = :"sort_by_#{object.name}_#{object.dir}" 66 | scope_name if object.context.object.respond_to?(scope_name) 67 | end 68 | end 69 | 70 | DISPATCH = Hash.new do |hash, klass| 71 | hash[klass] = "visit_#{ 72 | klass.name.gsub(Constants::TWO_COLONS, Constants::UNDERSCORE) 73 | }" 74 | end 75 | 76 | private 77 | 78 | def ordered(object) 79 | case object.dir 80 | when 'asc'.freeze 81 | Arel::Nodes::Ascending.new(object.attr) 82 | when 'desc'.freeze 83 | Arel::Nodes::Descending.new(object.attr) 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /ransack.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | $:.push File.expand_path("../lib", __FILE__) 4 | require "ransack/version" 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "ransack" 8 | s.version = Ransack::VERSION 9 | s.platform = Gem::Platform::RUBY 10 | s.authors = ["Ernie Miller", "Ryan Bigg", "Jon Atack", "Sean Carroll", "David Rodríguez"] 11 | s.email = ["ernie@erniemiller.org", "radarlistener@gmail.com", "jonnyatack@gmail.com", "sfcarroll@gmail.com"] 12 | s.homepage = "https://github.com/activerecord-hackery/ransack" 13 | s.summary = %q{Object-based searching for Active Record.} 14 | s.description = %q{Ransack is the successor to the MetaSearch gem. It improves and expands upon MetaSearch's functionality, but does not have a 100%-compatible API.} 15 | s.required_ruby_version = '>= 3.0' 16 | s.license = 'MIT' 17 | 18 | s.add_dependency 'activerecord', '>= 7.1' 19 | s.add_dependency 'activesupport', '>= 7.1' 20 | s.add_dependency 'i18n' 21 | 22 | s.files = `git ls-files`.split("\n") 23 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 24 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 25 | s.require_paths = ["lib"] 26 | end 27 | -------------------------------------------------------------------------------- /spec/blueprints/articles.rb: -------------------------------------------------------------------------------- 1 | Article.blueprint do 2 | person 3 | title 4 | body 5 | end 6 | -------------------------------------------------------------------------------- /spec/blueprints/comments.rb: -------------------------------------------------------------------------------- 1 | Comment.blueprint do 2 | article 3 | person 4 | body 5 | end 6 | -------------------------------------------------------------------------------- /spec/blueprints/notes.rb: -------------------------------------------------------------------------------- 1 | Note.blueprint do 2 | note 3 | notable_type { "Article" } 4 | notable_id 5 | end 6 | -------------------------------------------------------------------------------- /spec/blueprints/people.rb: -------------------------------------------------------------------------------- 1 | Person.blueprint do 2 | name 3 | email { "test@example.com" } 4 | salary 5 | only_sort 6 | only_search 7 | only_admin 8 | end 9 | -------------------------------------------------------------------------------- /spec/blueprints/tags.rb: -------------------------------------------------------------------------------- 1 | Tag.blueprint do 2 | name { Sham.tag_name } 3 | end 4 | -------------------------------------------------------------------------------- /spec/console.rb: -------------------------------------------------------------------------------- 1 | Bundler.setup 2 | require 'machinist/active_record' 3 | require 'sham' 4 | require 'faker' 5 | require 'ransack' 6 | 7 | Dir[File.expand_path('../../spec/{helpers,support,blueprints}/*.rb', __FILE__)] 8 | .each do |f| 9 | require f 10 | end 11 | 12 | Sham.define do 13 | name { Faker::Name.name } 14 | title { Faker::Lorem.sentence } 15 | body { Faker::Lorem.paragraph } 16 | salary { |index| 30000 + (index * 1000) } 17 | tag_name { Faker::Lorem.words(number: 3).join(' ') } 18 | note { Faker::Lorem.words(number: 7).join(' ') } 19 | only_admin { Faker::Lorem.words(number: 3).join(' ') } 20 | only_search { Faker::Lorem.words(number: 3).join(' ') } 21 | only_sort { Faker::Lorem.words(number: 3).join(' ') } 22 | notable_id { |id| id } 23 | end 24 | 25 | Schema.create 26 | -------------------------------------------------------------------------------- /spec/helpers/polyamorous_helper.rb: -------------------------------------------------------------------------------- 1 | module PolyamorousHelper 2 | def new_join_association(reflection, children, klass) 3 | Polyamorous::JoinAssociation.new reflection, children, klass 4 | end 5 | 6 | def new_join_dependency(klass, associations = {}) 7 | Polyamorous::JoinDependency.new klass, klass.arel_table, associations, Polyamorous::InnerJoin 8 | end 9 | 10 | def new_join(name, type = Polyamorous::InnerJoin, klass = nil) 11 | Polyamorous::Join.new name, type, klass 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/helpers/ransack_helper.rb: -------------------------------------------------------------------------------- 1 | module RansackHelper 2 | def quote_table_name(table) 3 | ActiveRecord::Base.connection.quote_table_name(table) 4 | end 5 | 6 | def quote_column_name(column) 7 | ActiveRecord::Base.connection.quote_column_name(column) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/polyamorous/activerecord_compatibility_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Polyamorous 4 | describe "ActiveRecord Compatibility" do 5 | it 'works with self joins and includes' do 6 | trade_account = Account.create! 7 | Account.create!(trade_account: trade_account) 8 | 9 | accounts = Account.joins(:trade_account).includes(:trade_account, :agent_account) 10 | account = accounts.first 11 | 12 | expect(account.agent_account).to be_nil 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/polyamorous/join_association_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Polyamorous 4 | describe JoinAssociation do 5 | let(:join_dependency) { new_join_dependency Note, {} } 6 | let(:reflection) { Note.reflect_on_association(:notable) } 7 | let(:parent) { join_dependency.send(:join_root) } 8 | let(:join_association) { 9 | new_join_association(reflection, parent.children, Article) 10 | } 11 | 12 | subject { new_join_association(reflection, parent.children, Person) } 13 | 14 | it 'leaves the original reflection intact for thread safety' do 15 | reflection.instance_variable_set(:@klass, Article) 16 | join_association 17 | .swapping_reflection_klass(reflection, Person) do |new_reflection| 18 | expect(new_reflection.options).not_to equal reflection.options 19 | expect(new_reflection.options).not_to have_key(:polymorphic) 20 | expect(new_reflection.klass).to eq(Person) 21 | expect(reflection.klass).to eq(Article) 22 | end 23 | end 24 | 25 | it 'sets the polymorphic option to true after initializing' do 26 | expect(join_association.reflection.options[:polymorphic]).to be true 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/polyamorous/join_dependency_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Polyamorous 4 | describe JoinDependency do 5 | context 'with symbol joins' do 6 | subject { new_join_dependency Person, articles: :comments } 7 | 8 | specify { expect(subject.send(:join_root).drop(1).size) 9 | .to eq(2) } 10 | specify { expect(subject.send(:join_root).drop(1).map(&:join_type).uniq) 11 | .to eq [Polyamorous::InnerJoin] } 12 | end 13 | 14 | context 'with has_many :through association' do 15 | subject { new_join_dependency Person, :authored_article_comments } 16 | 17 | specify { expect(subject.send(:join_root).drop(1).size) 18 | .to eq 1 } 19 | specify { expect(subject.send(:join_root).drop(1).first.table_name) 20 | .to eq 'comments' } 21 | end 22 | 23 | context 'with outer join' do 24 | subject { new_join_dependency Person, new_join(:articles, :outer) } 25 | 26 | specify { expect(subject.send(:join_root).drop(1).size) 27 | .to eq 1 } 28 | specify { expect(subject.send(:join_root).drop(1).first.join_type) 29 | .to eq Polyamorous::OuterJoin } 30 | end 31 | 32 | context 'with nested outer joins' do 33 | subject { new_join_dependency Person, 34 | new_join(:articles, :outer) => new_join(:comments, :outer) } 35 | 36 | specify { expect(subject.send(:join_root).drop(1).size) 37 | .to eq 2 } 38 | specify { expect(subject.send(:join_root).drop(1).map(&:join_type)) 39 | .to eq [Polyamorous::OuterJoin, Polyamorous::OuterJoin] } 40 | specify { expect(subject.send(:join_root).drop(1).map(&:join_type).uniq) 41 | .to eq [Polyamorous::OuterJoin] } 42 | end 43 | 44 | context 'with polymorphic belongs_to join' do 45 | subject { new_join_dependency Note, new_join(:notable, :inner, Person) } 46 | 47 | specify { expect(subject.send(:join_root).drop(1).size) 48 | .to eq 1 } 49 | specify { expect(subject.send(:join_root).drop(1).first.join_type) 50 | .to eq Polyamorous::InnerJoin } 51 | specify { expect(subject.send(:join_root).drop(1).first.table_name) 52 | .to eq 'people' } 53 | end 54 | 55 | context 'with polymorphic belongs_to join and nested symbol join' do 56 | subject { new_join_dependency Note, 57 | new_join(:notable, :inner, Person) => :comments } 58 | 59 | specify { expect(subject.send(:join_root).drop(1).size) 60 | .to eq 2 } 61 | specify { expect(subject.send(:join_root).drop(1).map(&:join_type).uniq) 62 | .to eq [Polyamorous::InnerJoin] } 63 | specify { expect(subject.send(:join_root).drop(1).first.table_name) 64 | .to eq 'people' } 65 | specify { expect(subject.send(:join_root).drop(1)[1].table_name) 66 | .to eq 'comments' } 67 | end 68 | 69 | context 'with polymorphic belongs_to join and nested join' do 70 | subject { new_join_dependency Note, 71 | new_join(:notable, :outer, Person) => :comments } 72 | specify { expect(subject.send(:join_root).drop(1).size).to eq 2 } 73 | specify { expect(subject.send(:join_root).drop(1).map(&:join_type)).to eq [Polyamorous::OuterJoin, Polyamorous::InnerJoin] } 74 | specify { expect(subject.send(:join_root).drop(1).first.table_name) 75 | .to eq 'people' } 76 | specify { expect(subject.send(:join_root).drop(1)[1].table_name) 77 | .to eq 'comments' } 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/polyamorous/join_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Polyamorous 4 | describe Join do 5 | it 'is a tree node' do 6 | join = new_join(:articles, :outer) 7 | expect(join).to be_kind_of(TreeNode) 8 | end 9 | 10 | it 'can be added to a tree' do 11 | join = new_join(:articles, :outer) 12 | 13 | tree_hash = {} 14 | join.add_to_tree(tree_hash) 15 | 16 | expect(tree_hash[join]).to be {} 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/ransack/nodes/condition_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Ransack 4 | module Nodes 5 | describe Condition do 6 | context 'bug report #1245' do 7 | it 'preserves tuple behavior' do 8 | ransack_hash = { 9 | m: 'and', 10 | g: [ 11 | { title_type_in: ['["title 1", ""]'] } 12 | ] 13 | } 14 | 15 | sql = Article.ransack(ransack_hash).result.to_sql 16 | expect(sql).to include("IN (('title 1', ''))") 17 | end 18 | end 19 | 20 | context 'with an alias' do 21 | subject { 22 | Condition.extract( 23 | Context.for(Person), 'term_start', Person.first(2).map(&:name) 24 | ) 25 | } 26 | 27 | specify { expect(subject.combinator).to eq 'or' } 28 | specify { expect(subject.predicate.name).to eq 'start' } 29 | 30 | it 'converts the alias to the correct attributes' do 31 | expect(subject.attributes.map(&:name)).to eq(['name', 'email']) 32 | end 33 | end 34 | 35 | context 'with multiple values and an _any predicate' do 36 | subject { 37 | Condition.extract( 38 | Context.for(Person), 'name_eq_any', Person.first(2).map(&:name) 39 | ) 40 | } 41 | 42 | specify { expect(subject.values.size).to eq(2) } 43 | end 44 | 45 | describe '#negative?' do 46 | let(:context) { Context.for(Person) } 47 | let(:eq) { Condition.extract(context, 'name_eq', 'A') } 48 | let(:not_eq) { Condition.extract(context, 'name_not_eq', 'A') } 49 | 50 | specify { expect(not_eq.negative?).to be true } 51 | specify { expect(eq.negative?).to be false } 52 | end 53 | 54 | context 'with an invalid predicate' do 55 | subject { 56 | Condition.extract( 57 | Context.for(Person), 'name_invalid', Person.first.name 58 | ) 59 | } 60 | 61 | context "when ignore_unknown_conditions is false" do 62 | before do 63 | Ransack.configure { |c| c.ignore_unknown_conditions = false } 64 | end 65 | 66 | specify { expect { subject }.to raise_error ArgumentError } 67 | specify { expect { subject }.to raise_error InvalidSearchError } 68 | end 69 | 70 | context "when ignore_unknown_conditions is true" do 71 | before do 72 | Ransack.configure { |c| c.ignore_unknown_conditions = true } 73 | end 74 | 75 | specify { expect(subject).to be_nil } 76 | end 77 | end 78 | 79 | context 'with an empty predicate' do 80 | subject { 81 | Condition.extract( 82 | Context.for(Person), 'full_name', Person.first.name 83 | ) 84 | } 85 | 86 | context "when default_predicate = nil" do 87 | before do 88 | Ransack.configure { |c| c.default_predicate = nil } 89 | end 90 | 91 | specify { expect(subject).to be_nil } 92 | end 93 | 94 | context "when default_predicate = 'eq'" do 95 | before do 96 | Ransack.configure { |c| c.default_predicate = 'eq' } 97 | end 98 | 99 | specify { expect(subject).to eq Condition.extract(Context.for(Person), 'full_name_eq', Person.first.name) } 100 | end 101 | end 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /spec/ransack/nodes/grouping_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Ransack 4 | module Nodes 5 | describe Grouping do 6 | before do 7 | @g = 1 8 | end 9 | 10 | let(:context) { Context.for(Person) } 11 | 12 | subject { described_class.new(context) } 13 | 14 | describe '#attribute_method?' do 15 | context 'for attributes of the context' do 16 | it 'is true' do 17 | expect(subject.attribute_method?('name')).to be true 18 | end 19 | 20 | context "when the attribute contains '_and_'" do 21 | it 'is true' do 22 | expect(subject.attribute_method?('terms_and_conditions')).to be true 23 | end 24 | end 25 | 26 | context "when the attribute contains '_or_'" do 27 | it 'is true' do 28 | expect(subject.attribute_method?('true_or_false')).to be true 29 | end 30 | end 31 | 32 | context "when the attribute ends with '_start'" do 33 | it 'is true' do 34 | expect(subject.attribute_method?('life_start')).to be true 35 | end 36 | end 37 | 38 | context "when the attribute ends with '_end'" do 39 | it 'is true' do 40 | expect(subject.attribute_method?('stop_end')).to be true 41 | end 42 | end 43 | end 44 | 45 | context 'for unknown attributes' do 46 | it 'is false' do 47 | expect(subject.attribute_method?('not_an_attribute')).to be false 48 | end 49 | end 50 | end 51 | 52 | describe '#conditions=' do 53 | context 'when conditions are identical' do 54 | let(:conditions) do 55 | { 56 | '0' => { 57 | 'a' => { '0'=> { 'name' => 'name', 'ransacker_args' => '' } }, 58 | 'p' => 'cont', 59 | 'v' => { '0' => { 'value' => 'John' } } 60 | }, 61 | '1' => { 62 | 'a' => { '0' => { 'name' => 'name', 'ransacker_args' => '' } }, 63 | 'p' => 'cont', 64 | 'v' => { '0' => { 'value' => 'John' } } 65 | } 66 | } 67 | end 68 | 69 | before { subject.conditions = conditions } 70 | 71 | it 'expect duplicates to be removed' do 72 | expect(subject.conditions.count).to eq 1 73 | end 74 | end 75 | 76 | context 'when conditions differ only by ransacker_args' do 77 | let(:conditions) do 78 | { 79 | '0' => { 80 | 'a' => { 81 | '0' => { 82 | 'name' => 'with_arguments', 83 | 'ransacker_args' => [1, 2] 84 | } 85 | }, 86 | 'p' => 'eq', 87 | 'v' => { '0' => { 'value' => '10' } } 88 | }, 89 | '1' => { 90 | 'a' => { 91 | '0' => { 92 | 'name' => 'with_arguments', 93 | 'ransacker_args' => [3, 4] 94 | } 95 | }, 96 | 'p' => 'eq', 97 | 'v' => { '0' => { 'value' => '10' } } 98 | } 99 | } 100 | end 101 | 102 | before { subject.conditions = conditions } 103 | 104 | it 'expect them to be parsed as different and not as duplicates' do 105 | expect(subject.conditions.count).to eq 2 106 | end 107 | end 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /spec/ransack/nodes/value_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Ransack 4 | module Nodes 5 | describe Value do 6 | let(:context) { Context.for(Person) } 7 | 8 | subject do 9 | Value.new(context, raw_value) 10 | end 11 | 12 | context "with a date value" do 13 | let(:raw_value) { "2022-05-23" } 14 | 15 | [:date].each do |type| 16 | it "should cast #{type} correctly" do 17 | result = subject.cast(type) 18 | 19 | expect(result).to be_a_kind_of(Date) 20 | expect(result).to eq(Date.parse(raw_value)) 21 | end 22 | end 23 | end 24 | 25 | context "with a timestamp value" do 26 | let(:raw_value) { "2022-05-23 10:40:02 -0400" } 27 | 28 | [:datetime, :timestamp, :time, :timestamptz].each do |type| 29 | it "should cast #{type} correctly" do 30 | result = subject.cast(type) 31 | 32 | expect(result).to be_a_kind_of(Time) 33 | expect(result).to eq(Time.zone.parse(raw_value)) 34 | end 35 | end 36 | end 37 | 38 | Constants::TRUE_VALUES.each do |value| 39 | context "with a true boolean value (#{value})" do 40 | let(:raw_value) { value.to_s } 41 | 42 | it "should cast boolean correctly" do 43 | result = subject.cast(:boolean) 44 | expect(result).to eq(true) 45 | end 46 | end 47 | end 48 | 49 | Constants::FALSE_VALUES.each do |value| 50 | context "with a false boolean value (#{value})" do 51 | let(:raw_value) { value.to_s } 52 | 53 | it "should cast boolean correctly" do 54 | result = subject.cast(:boolean) 55 | 56 | expect(result).to eq(false) 57 | end 58 | end 59 | end 60 | 61 | ["12", "101.5"].each do |value| 62 | context "with an integer value (#{value})" do 63 | let(:raw_value) { value } 64 | 65 | it "should cast #{value} to integer correctly" do 66 | result = subject.cast(:integer) 67 | 68 | expect(result).to be_an(Integer) 69 | expect(result).to eq(value.to_i) 70 | end 71 | end 72 | end 73 | 74 | ["12", "101.5"].each do |value| 75 | context "with a float value (#{value})" do 76 | let(:raw_value) { value } 77 | 78 | it "should cast #{value} to float correctly" do 79 | result = subject.cast(:float) 80 | 81 | expect(result).to be_an(Float) 82 | expect(result).to eq(value.to_f) 83 | end 84 | end 85 | end 86 | 87 | ["12", "101.5"].each do |value| 88 | context "with a decimal value (#{value})" do 89 | let(:raw_value) { value } 90 | 91 | it "should cast #{value} to decimal correctly" do 92 | result = subject.cast(:decimal) 93 | 94 | expect(result).to be_a(BigDecimal) 95 | expect(result).to eq(value.to_d) 96 | end 97 | end 98 | end 99 | 100 | ["12", "101.513"].each do |value| 101 | context "with a money value (#{value})" do 102 | let(:raw_value) { value } 103 | 104 | it "should cast #{value} to money correctly" do 105 | result = subject.cast(:money) 106 | 107 | expect(result).to be_a(String) 108 | expect(result).to eq(value.to_f.to_s) 109 | end 110 | end 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /spec/ransack/translate_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Ransack 4 | describe Translate do 5 | describe '.attribute' do 6 | it 'translate namespaced attribute like AR does' do 7 | ar_translation = ::Namespace::Article.human_attribute_name(:title) 8 | ransack_translation = Ransack::Translate.attribute( 9 | :title, 10 | context: ::Namespace::Article.ransack.context 11 | ) 12 | expect(ransack_translation).to eq ar_translation 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'ransack' 2 | require 'sham' 3 | require 'faker' 4 | require 'action_controller' 5 | require 'ransack/helpers' 6 | require 'pry' 7 | require 'simplecov' 8 | require 'byebug' 9 | require 'machinist/active_record' 10 | 11 | SimpleCov.start 12 | I18n.enforce_available_locales = false 13 | Time.zone = 'Eastern Time (US & Canada)' 14 | I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')] 15 | 16 | Dir[File.expand_path('../{helpers,support,blueprints}/*.rb', __FILE__)] 17 | .each { |f| require f } 18 | 19 | Faker::Config.random = Random.new(0) 20 | Sham.define do 21 | name { Faker::Name.name } 22 | title { Faker::Lorem.sentence } 23 | body { Faker::Lorem.paragraph } 24 | salary { |index| 30000 + (index * 1000) } 25 | tag_name { Faker::Lorem.words(number: 3).join(' ') } 26 | note { Faker::Lorem.words(number: 7).join(' ') } 27 | only_admin { Faker::Lorem.words(number: 3).join(' ') } 28 | only_search { Faker::Lorem.words(number: 3).join(' ') } 29 | only_sort { Faker::Lorem.words(number: 3).join(' ') } 30 | notable_id { |id| id } 31 | end 32 | 33 | RSpec.configure do |config| 34 | config.alias_it_should_behave_like_to :it_has_behavior, 'has behavior' 35 | 36 | config.before(:suite) do 37 | message = "Running Ransack specs with #{ 38 | ActiveRecord::Base.connection.adapter_name 39 | }, Active Record #{::ActiveRecord::VERSION::STRING}, Arel #{Arel::VERSION 40 | } and Ruby #{RUBY_VERSION}" 41 | line = '=' * message.length 42 | puts line, message, line 43 | Schema.create 44 | SubDB::Schema.create 45 | end 46 | 47 | config.before(:all) { Sham.reset(:before_all) } 48 | config.before(:each) { Sham.reset(:before_each) } 49 | 50 | config.include RansackHelper 51 | config.include PolyamorousHelper 52 | end 53 | 54 | RSpec::Matchers.define :be_like do |expected| 55 | match do |actual| 56 | actual.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip == 57 | expected.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip 58 | end 59 | end 60 | 61 | RSpec::Matchers.define :have_attribute_method do |expected| 62 | match do |actual| 63 | actual.attribute_method?(expected) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/support/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | attributes: 3 | id: 'id' 4 | ransack: 5 | attributes: 6 | person: 7 | name: Full Name 8 | only_admin: admin uSer Only 9 | salary: wages 10 | awesome: ransack is really awesome 11 | article: 12 | body: maiN BoDy 13 | activerecord: 14 | attributes: 15 | namespace/article: 16 | title: AR Namespaced Title 17 | namespace_article: 18 | title: Old Ransack Namespaced Title 19 | --------------------------------------------------------------------------------