From 7605b616d80c455b24aea8b62fc64e2d2f268715 Mon Sep 17 00:00:00 2001 From: nakamura Date: Fri, 27 Oct 2023 07:40:02 +0000 Subject: [PATCH 1/2] Add `AdministrateRansack.ransack?` helper --- lib/administrate_ransack.rb | 1 + lib/administrate_ransack/helpers.rb | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 lib/administrate_ransack/helpers.rb diff --git a/lib/administrate_ransack.rb b/lib/administrate_ransack.rb index 093d2fb..bc6962b 100644 --- a/lib/administrate_ransack.rb +++ b/lib/administrate_ransack.rb @@ -2,6 +2,7 @@ require 'administrate_ransack/engine' require 'administrate_ransack/filters' +require 'administrate_ransack/helpers' require 'administrate_ransack/searchable' require 'administrate_ransack/version' diff --git a/lib/administrate_ransack/helpers.rb b/lib/administrate_ransack/helpers.rb new file mode 100644 index 0000000..863026c --- /dev/null +++ b/lib/administrate_ransack/helpers.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module AdministrateRansack + class << self + def ransack?(model, params = {}, options = {}) + ransack = model.ransack(params, **options) + ransack.instance_variable_get(:@scope_args).present? || ransack.base.c.present? + end + end +end From 7d9478a724ae511b3a79b96432daf5e2e647c008 Mon Sep 17 00:00:00 2001 From: nakamura Date: Fri, 27 Oct 2023 07:44:31 +0000 Subject: [PATCH 2/2] Fix partials to support all ransack predicates --- README.md | 17 ++++++------ .../administrate_ransack/_filters.html.erb | 2 +- .../components/_field_belongs_to.html.erb | 9 ++++--- .../components/_field_boolean.html.erb | 4 +-- .../components/_field_date.html.erb | 4 +-- .../components/_field_datetime.html.erb | 4 +-- .../components/_field_has_many.html.erb | 16 +++++------ .../components/_field_number.html.erb | 11 +++++--- .../components/_field_other.html.erb | 11 ++++++-- .../components/_field_select.html.erb | 2 +- .../components/_field_string.html.erb | 13 ++++++--- .../controllers/admin/authors_controller.rb | 2 ++ spec/dummy/app/dashboards/author_dashboard.rb | 10 +++++++ spec/dummy/app/dashboards/tag_dashboard.rb | 6 ++--- .../app/views/admin/authors/index.html.erb | 17 ++++++++++++ .../app/views/admin/posts/index.html.erb | 1 + spec/rails_helper.rb | 2 +- spec/system/number_filter_spec.rb | 12 +++++++++ spec/system/string_filter_spec.rb | 27 +++++++++++++++++++ 19 files changed, 127 insertions(+), 43 deletions(-) create mode 100644 spec/dummy/app/views/admin/authors/index.html.erb diff --git a/README.md b/README.md index 986f197..a01a2d4 100644 --- a/README.md +++ b/README.md @@ -54,15 +54,14 @@ end + `attribute_types`: hash used to specify the filter fields, ex. `{ title: Administrate::Field::String }` + `search_path`: the path to use for searching (form URL) + `namespace`: the namespace used by Administrate, ex. `:supervisor` -- For associations (_has many_/_belongs to_) the label used can be customized adding an `admin_label` method to the target model which returns a string while the collection can by filtered with `admin_scope`. Example: +- For associations (_has many_/_belongs to_) the label used can be customized adding an `display_resource` method to the target dashboard which returns a string. Example: ```rb -# Sample post model -class Post < ApplicationRecord - scope :admin_scope, -> { where(published: true) } - - def admin_label - title.upcase +class PostDashboard < Administrate::BaseDashboard + # Overwrite this method to customize how posts are displayed + # across all pages of the admin dashboard. + def display_resource(post) + "##{post.id} #{post.title&.upcase}" end end ``` @@ -139,9 +138,11 @@ end # In alternative prepare an hash in the dashboard like RANSACK_TYPES = {} attribute_types = { title: Administrate::Field::String, + title_or_description_cont: Administrate::Field::String, author: Administrate::Field::BelongsTo, category: Administrate::Field::Select.with_options(collection: Post.categories.to_a), - published: Administrate::Field::Boolean + published: Administrate::Field::Boolean, + updated_at_lteq: Administrate::Field::Date } attribute_labels = { author: 'Written by', diff --git a/app/views/administrate_ransack/_filters.html.erb b/app/views/administrate_ransack/_filters.html.erb index ca53b9f..44db5a9 100644 --- a/app/views/administrate_ransack/_filters.html.erb +++ b/app/views/administrate_ransack/_filters.html.erb @@ -21,7 +21,7 @@ <% attribute_types.each do |field, type| %> <% next if field == :id %> - <% label = attribute_labels.include?(field) ? attribute_labels[field] : field %> + <% label = attribute_labels.include?(field) ? attribute_labels[field] : nil %> <% model = @ransack_results.klass %> <% input_type = type.is_a?(Administrate::Field::Deferred) ? type.deferred_class.to_s : type.to_s %> <% component = AdministrateRansack::FILTERS[input_type] || 'field_other' %> diff --git a/app/views/administrate_ransack/components/_field_belongs_to.html.erb b/app/views/administrate_ransack/components/_field_belongs_to.html.erb index 0fa4001..df45d0c 100644 --- a/app/views/administrate_ransack/components/_field_belongs_to.html.erb +++ b/app/views/administrate_ransack/components/_field_belongs_to.html.erb @@ -1,9 +1,10 @@ <% association = model.reflections[field.to_s] %> <% if association %> - <% field_key = model.ransackable_scopes.include?(field) ? field : "#{field}_id_eq" %> - <% desc = association.klass.method_defined?(:admin_label) ? :admin_label : :to_s %> - <% collection = association.klass.send(association.klass.respond_to?(:admin_scope) ? :admin_scope : :all) %> + <% field_key = AdministrateRansack.ransack?(model, {field => "1,2"}) ? field : "#{field}_id_eq" %> + <% label ||= AdministrateRansack.ransack?(model, {field => "1,2"}) ? field : "#{field}_id" %> + <% resource_field = type.new(field, nil, Administrate::Page::Collection.new(@dashboard), resource: model.new) %> + <% collection = resource_field.associated_resource_options %> <%= form.label(label, class: 'filter-label') %> - <%= form.collection_select(field_key, collection, :id, desc, include_blank: true) %> + <%= form.select("#{field}_id_eq", collection, { include_blank: true }, { class: 'selectize' }) %> <% end %> diff --git a/app/views/administrate_ransack/components/_field_boolean.html.erb b/app/views/administrate_ransack/components/_field_boolean.html.erb index 66c0e66..ee24d29 100644 --- a/app/views/administrate_ransack/components/_field_boolean.html.erb +++ b/app/views/administrate_ransack/components/_field_boolean.html.erb @@ -1,5 +1,5 @@ -<% field_key = model.ransackable_scopes.include?(field) ? field : "#{field}_eq" %> +<% field_key = AdministrateRansack.ransack?(model, {field => "true"}) ? field : "#{field}_eq" %> <% values = [[t('administrate_ransack.filters.no'), false], [t('administrate_ransack.filters.yes'), true]] %> -<%= form.label(label, class: 'filter-label') %> +<%= form.label(label || field, class: 'filter-label') %> <%= form.select(field_key, values, include_blank: true) %> diff --git a/app/views/administrate_ransack/components/_field_date.html.erb b/app/views/administrate_ransack/components/_field_date.html.erb index 3a3edef..9e8dd45 100644 --- a/app/views/administrate_ransack/components/_field_date.html.erb +++ b/app/views/administrate_ransack/components/_field_date.html.erb @@ -1,5 +1,5 @@ -<%= form.label(label, class: 'filter-label') %> -<% if model.ransackable_scopes.include?(field) %> +<%= form.label(label || field, class: 'filter-label') %> +<% if AdministrateRansack.ransack?(model, {field => Date.today}) %> <%= form.date_field(field, value: form.object.send(field)) %> <% else %> <%= form.date_field("#{field}_gteq") %> diff --git a/app/views/administrate_ransack/components/_field_datetime.html.erb b/app/views/administrate_ransack/components/_field_datetime.html.erb index 3966cb3..593d77a 100644 --- a/app/views/administrate_ransack/components/_field_datetime.html.erb +++ b/app/views/administrate_ransack/components/_field_datetime.html.erb @@ -1,5 +1,5 @@ -<%= form.label(label, class: 'filter-label') %> -<% if model.ransackable_scopes.include?(field) %> +<%= form.label(label || field, class: 'filter-label') %> +<% if AdministrateRansack.ransack?(model, {field => DateTime.now}) %> <%= form.datetime_field(field, value: form.object.send(field)) %> <% else %> <%= form.datetime_field("#{field}_gteq") %> diff --git a/app/views/administrate_ransack/components/_field_has_many.html.erb b/app/views/administrate_ransack/components/_field_has_many.html.erb index a60dc12..d968e3a 100644 --- a/app/views/administrate_ransack/components/_field_has_many.html.erb +++ b/app/views/administrate_ransack/components/_field_has_many.html.erb @@ -1,19 +1,17 @@ <% association = model.reflections[field.to_s] %> <% if association %> - <% field_key = model.ransackable_scopes.include?(field) ? field : "#{field}_id_in" %> - <% desc = association.klass.method_defined?(:admin_label) ? :admin_label : :to_s %> - <% collection = association.klass.send(association.klass.respond_to?(:admin_scope) ? :admin_scope : :all) %> + <% field_key = AdministrateRansack.ransack?(model, {field => "1,2"}) ? field : "#{field}_id_in" %> + <% resource_field = type.new(field, nil, Administrate::Page::Collection.new(@dashboard), resource: model.new) %> + <% collection = resource_field.associated_resource_options %> - <%= form.label(label, class: 'filter-label') %> + <%= form.label(label || field_key, class: 'filter-label') %> <% if options&.include? 'select' %> - <%= form.select(field_key, nil, {}, multiple: true) do %> - <%= options_from_collection_for_select(collection, :id, desc) %> - <% end %> + <%= form.select(field_key, collection, {}, multiple: true) %> <% else %> - <%= form.collection_check_boxes(field_key, collection, :id, desc) do |b| %> + <%= form.collection_check_boxes(field_key, collection, :second, :first) do |b| %> <%= b.label do %> <%= b.check_box %> - <%= b.object.send(desc) %> + <%= b.text %> <% end %> <% end %> <% end %> diff --git a/app/views/administrate_ransack/components/_field_number.html.erb b/app/views/administrate_ransack/components/_field_number.html.erb index 4a732cd..a6ce474 100644 --- a/app/views/administrate_ransack/components/_field_number.html.erb +++ b/app/views/administrate_ransack/components/_field_number.html.erb @@ -1,4 +1,7 @@ -<% field_key = model.ransackable_scopes.include?(field) ? field : "#{field}_eq" %> - -<%= form.label(label, class: 'filter-label') %> -<%= form.number_field(field_key) %> +<%= form.label(label || field, class: 'filter-label') %> +<% if AdministrateRansack.ransack?(model, {field => "1"}) %> + <%= form.number_field "#{field}" %> +<% else %> + <%= form.number_field "#{field}_gteq" %> + <%= form.number_field "#{field}_lteq" %> +<% end %> diff --git a/app/views/administrate_ransack/components/_field_other.html.erb b/app/views/administrate_ransack/components/_field_other.html.erb index bac210d..52c3cd8 100644 --- a/app/views/administrate_ransack/components/_field_other.html.erb +++ b/app/views/administrate_ransack/components/_field_other.html.erb @@ -1,5 +1,12 @@ -<%= form.label(label, class: 'filter-label') %> -<%= form.search_field(field) %> +<% if AdministrateRansack.ransack?(model, {field => "valid"}) %> + <%= form.label(label || field, class: 'filter-label') %> + <%= form.search_field(field) %> +<% elsif AdministrateRansack.ransack?(model, {"#{field}_cont" => "valid"}) %> + <%= form.label(label || "#{field}_cont", class: 'filter-label') %> + <%= form.search_field("#{field}_cont") %> +<% else %> + <%# render nothing %> +<% end %> <%# unsupported Field::HasOne %> <%# unsupported Field::Polymorphic %> diff --git a/app/views/administrate_ransack/components/_field_select.html.erb b/app/views/administrate_ransack/components/_field_select.html.erb index 5698415..61ee11e 100644 --- a/app/views/administrate_ransack/components/_field_select.html.erb +++ b/app/views/administrate_ransack/components/_field_select.html.erb @@ -1,5 +1,5 @@ <% field_key = model.ransackable_scopes.include?(field) ? field : "#{field}_eq" %> <% collection = (type.respond_to?(:options) ? type.options[:collection] : []) || [] %> -<%= form.label(label, class: 'filter-label') %> +<%= form.label(label || field, class: 'filter-label') %> <%= form.select(field_key, collection, include_blank: true) %> diff --git a/app/views/administrate_ransack/components/_field_string.html.erb b/app/views/administrate_ransack/components/_field_string.html.erb index 4630c61..ed5c15d 100644 --- a/app/views/administrate_ransack/components/_field_string.html.erb +++ b/app/views/administrate_ransack/components/_field_string.html.erb @@ -1,4 +1,9 @@ -<% field_key = model.ransackable_scopes.include?(field) ? field : "#{field}_cont" %> - -<%= form.label(label, class: 'filter-label') %> -<%= form.search_field(field_key) %> +<% if AdministrateRansack.ransack?(model, {field => "valid"}) %> + <%= form.label(label || field, class: 'filter-label') %> + <%= form.search_field(field) %> +<% elsif AdministrateRansack.ransack?(model, {"#{field}_cont" => "valid"}) %> + <%= form.label(label || "#{field}_cont", class: 'filter-label') %> + <%= form.search_field("#{field}_cont") %> +<% else %> + <%# render nothing %> +<% end %> diff --git a/spec/dummy/app/controllers/admin/authors_controller.rb b/spec/dummy/app/controllers/admin/authors_controller.rb index a0a02a8..1c655d0 100644 --- a/spec/dummy/app/controllers/admin/authors_controller.rb +++ b/spec/dummy/app/controllers/admin/authors_controller.rb @@ -1,5 +1,7 @@ module Admin class AuthorsController < Admin::ApplicationController + prepend AdministrateRansack::Searchable + # Overwrite any of the RESTful controller actions to implement custom behavior # For example, you may want to send an email after a foo is updated. # diff --git a/spec/dummy/app/dashboards/author_dashboard.rb b/spec/dummy/app/dashboards/author_dashboard.rb index 4f7755d..c94e141 100644 --- a/spec/dummy/app/dashboards/author_dashboard.rb +++ b/spec/dummy/app/dashboards/author_dashboard.rb @@ -39,6 +39,16 @@ class AuthorDashboard < Administrate::BaseDashboard # published_posts # recent_posts + # RANSACK_TYPES + RANSACK_TYPES = { + posts: Field::HasMany, + tags: Field::HasMany, + name: Field::String, + name_or_email_cont: Field::String, + name_not_cont: Field::String, + age: Field::Number + }.freeze + # SHOW_PAGE_ATTRIBUTES # an array of attributes that will be displayed on the model's show page. SHOW_PAGE_ATTRIBUTES = %i[ diff --git a/spec/dummy/app/dashboards/tag_dashboard.rb b/spec/dummy/app/dashboards/tag_dashboard.rb index 1b2af4c..3bbc29d 100644 --- a/spec/dummy/app/dashboards/tag_dashboard.rb +++ b/spec/dummy/app/dashboards/tag_dashboard.rb @@ -63,7 +63,7 @@ class TagDashboard < Administrate::BaseDashboard # Overwrite this method to customize how tags are displayed # across all pages of the admin dashboard. # - # def display_resource(tag) - # "Tag ##{tag.id}" - # end + def display_resource(tag) + "##{tag.id} #{tag.name}" + end end diff --git a/spec/dummy/app/views/admin/authors/index.html.erb b/spec/dummy/app/views/admin/authors/index.html.erb new file mode 100644 index 0000000..ea57540 --- /dev/null +++ b/spec/dummy/app/views/admin/authors/index.html.erb @@ -0,0 +1,17 @@ +
+ <%= render( + "collection", + collection_presenter: page, + collection_field_name: resource_name, + page: page, + resources: resources, + table_title: "page-title" + ) %> + + <%= paginate resources, param_name: '_page' %> +
+ +<%= render( + 'administrate_ransack/filters', + attribute_types: @dashboard.class::RANSACK_TYPES +) %> diff --git a/spec/dummy/app/views/admin/posts/index.html.erb b/spec/dummy/app/views/admin/posts/index.html.erb index 7b798a6..2be3f9e 100644 --- a/spec/dummy/app/views/admin/posts/index.html.erb +++ b/spec/dummy/app/views/admin/posts/index.html.erb @@ -49,6 +49,7 @@ category: Administrate::Field::Select.with_options(collection: Post.categories.to_a), published: Administrate::Field::Boolean, position: Administrate::Field::Number, + position_eq: Administrate::Field::Number, tags: Administrate::Field::HasMany, dt: Administrate::Field::Date, created_at: Administrate::Field::DateTime, diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 11239d2..725cdb8 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -42,7 +42,7 @@ def setup_data author = Author.find_by!(name: 'A test author') tag = Tag.find_by!(name: 'A test tag') Post.first.update!(title: 'A post', author: author, category: 'news', published: true, dt: Time.zone.today) - Post.second.update!(title: 'Another post', author: author, category: 'story', dt: Date.yesterday, tags: [tag]) + Post.second.update!(title: 'Another post', author: author, category: 'story', position: 123, dt: Date.yesterday, tags: [tag]) Post.third.update!(title: 'Last post', author: author, category: 'news', position: 234, dt: Date.tomorrow) end end diff --git a/spec/system/number_filter_spec.rb b/spec/system/number_filter_spec.rb index 270255e..a5981fa 100644 --- a/spec/system/number_filter_spec.rb +++ b/spec/system/number_filter_spec.rb @@ -2,6 +2,18 @@ RSpec.describe 'Number filter' do let(:post3) { Post.third } + let(:author1) { Author.first } + + it 'filters the authors by age range', :aggregate_failures do + visit '/admin/authors' + + fill_in('q[age_lteq]', with: '28') + find('input[type="submit"]').click + + expect(page).to have_current_path %r{/admin/authors\?.+q%5Bage_lteq%5D=28.*} + expect(page).to have_css('.js-table-row', count: 2) + expect(page).to have_css('.js-table-row a.action-show', text: author1.name) + end it 'filters the posts by position', :aggregate_failures do visit '/admin/posts' diff --git a/spec/system/string_filter_spec.rb b/spec/system/string_filter_spec.rb index 75c3a06..a2985b8 100644 --- a/spec/system/string_filter_spec.rb +++ b/spec/system/string_filter_spec.rb @@ -2,6 +2,33 @@ RSpec.describe 'String filter' do let(:post2) { Post.second } + let(:author11) { Author.find(11) } + + it 'filters the authors by name or email', :aggregate_failures do + visit '/admin/authors' + + fill_in('q[name_or_email_cont]', with: '@bbb') + find('input[type="submit"]').click + + expect(page).to have_css('.js-table-row', count: 1) + expect(page).to have_css('.js-table-row a.action-show', text: author11.name) + + fill_in('q[name_or_email_cont]', with: 'A test') + find('input[type="submit"]').click + + expect(page).to have_css('.js-table-row', count: 1) + expect(page).to have_css('.js-table-row a.action-show', text: author11.name) + end + + it 'filters the authors by name not contain', :aggregate_failures do + visit '/admin/authors' + + fill_in('q[name_not_cont]', with: 'A test') + find('input[type="submit"]').click + + expect(page).to have_css('.js-table-row', count: 10) + expect(page).not_to have_css('.js-table-row a.action-show', text: author11.name) + end it 'filters the posts by title', :aggregate_failures do visit '/admin/posts'