Skip to content

Commit

Permalink
Add custom search feature. Closes #343, Closes #3019
Browse files Browse the repository at this point in the history
  • Loading branch information
mshibuya committed Jun 1, 2019
1 parent c6796b0 commit 9abc7f9
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 30 deletions.
16 changes: 10 additions & 6 deletions lib/rails_admin/adapters/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,17 @@ def build
end

def query_scope(scope, query, fields = config.list.fields.select(&:queryable?))
wb = WhereBuilder.new(scope)
fields.each do |field|
value = parse_field_value(field, query)
wb.add(field, value, field.search_operator)
if config.list.search_by
scope.send(config.list.search_by, query)
else
wb = WhereBuilder.new(scope)
fields.each do |field|
value = parse_field_value(field, query)
wb.add(field, value, field.search_operator)
end
# OR all query statements
wb.build
end
# OR all query statements
wb.build
end

# filters example => {"string_field"=>{"0055"=>{"o"=>"like", "v"=>"test_value"}}, ...}
Expand Down
28 changes: 16 additions & 12 deletions lib/rails_admin/adapters/mongoid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ def all(options = {}, scope = nil)
scope = scope.includes(*options[:include]) if options[:include]
scope = scope.limit(options[:limit]) if options[:limit]
scope = scope.any_in(_id: options[:bulk_ids]) if options[:bulk_ids]
scope = scope.where(query_conditions(options[:query])) if options[:query]
scope = scope.where(filter_conditions(options[:filters])) if options[:filters]
scope = query_scope(scope, options[:query]) if options[:query]
scope = filter_scope(scope, options[:filters]) if options[:filters]
if options[:page] && options[:per]
scope = scope.send(Kaminari.config.page_method_name, options[:page]).per(options[:per])
end
Expand Down Expand Up @@ -113,21 +113,25 @@ def make_field_conditions(field, value, operator)
conditions_per_collection
end

def query_conditions(query, fields = config.list.fields.select(&:queryable?))
statements = []
def query_scope(scope, query, fields = config.list.fields.select(&:queryable?))
if config.list.search_by
scope.send(config.list.search_by, query)
else
statements = []

fields.each do |field|
value = parse_field_value(field, query)
conditions_per_collection = make_field_conditions(field, value, field.search_operator)
statements.concat make_condition_for_current_collection(field, conditions_per_collection)
end
fields.each do |field|
value = parse_field_value(field, query)
conditions_per_collection = make_field_conditions(field, value, field.search_operator)
statements.concat make_condition_for_current_collection(field, conditions_per_collection)
end

statements.any? ? {'$or' => statements} : {}
scope.where(statements.any? ? {'$or' => statements} : {})
end
end

# filters example => {"string_field"=>{"0055"=>{"o"=>"like", "v"=>"test_value"}}, ...}
# "0055" is the filter index, no use here. o is the operator, v the value
def filter_conditions(filters, fields = config.list.fields.select(&:filterable?))
def filter_scope(scope, filters, fields = config.list.fields.select(&:filterable?))
statements = []

filters.each_pair do |field_name, filters_dump|
Expand All @@ -145,7 +149,7 @@ def filter_conditions(filters, fields = config.list.fields.select(&:filterable?)
end
end

statements.any? ? {'$and' => statements} : {}
scope.where(statements.any? ? {'$and' => statements} : {})
end

def parse_collection_name(column)
Expand Down
4 changes: 4 additions & 0 deletions lib/rails_admin/config/sections/list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class List < RailsAdmin::Config::Sections::Base
false
end

register_instance_option :search_by do
nil
end

register_instance_option :sort_by do
parent.abstract_model.primary_key
end
Expand Down
2 changes: 2 additions & 0 deletions spec/dummy_app/app/active_record/player.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class Player < ActiveRecord::Base

before_destroy :destroy_hook

scope :rails_admin_search, ->(query) { where(name: query.reverse) }

def destroy_hook; end

def draft_id
Expand Down
2 changes: 2 additions & 0 deletions spec/dummy_app/app/mongoid/player.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class Player

before_destroy :destroy_hook

scope :rails_admin_search, ->(query) { where(name: query.reverse) }

def destroy_hook; end

def draft_id
Expand Down
22 changes: 22 additions & 0 deletions spec/integration/basic/list/rails_admin_basic_list_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,28 @@ def visit_page(page)
end
end

describe 'Custom search' do
before do
RailsAdmin.config do |config|
config.model Player do
list do
search_by :rails_admin_search
end
end
end
end
let!(:players) do
[FactoryBot.create(:player, name: 'Joe'),
FactoryBot.create(:player, name: 'George')]
end

it 'performs search using given scope' do
visit index_path(model_name: 'player', query: 'eoJ')
is_expected.to have_content(players[0].name)
is_expected.to have_no_content(players[1].name)
end
end

describe 'list for objects with overridden to_param' do
before do
@ball = FactoryBot.create :ball
Expand Down
4 changes: 2 additions & 2 deletions spec/rails_admin/adapters/active_record_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class PlayerWithDefaultScope < Player
end
end

describe '#query_conditions' do
describe '#query_scope' do
let(:abstract_model) { RailsAdmin::AbstractModel.new('Team') }

before do
Expand All @@ -155,7 +155,7 @@ class PlayerWithDefaultScope < Player
end
end

describe '#filter_conditions' do
describe '#filter_scope' do
let(:abstract_model) { RailsAdmin::AbstractModel.new('Team') }

before do
Expand Down
20 changes: 10 additions & 10 deletions spec/rails_admin/adapters/mongoid_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
end
end

describe '#query_conditions' do
describe '#query_scope' do
before do
@abstract_model = RailsAdmin::AbstractModel.new('Player')
@players = [{}, {name: 'Many foos'}, {position: 'foo shortage'}].
Expand All @@ -200,7 +200,7 @@
end
end

describe '#filter_conditions' do
describe '#filter_scope' do
before do
@abstract_model = RailsAdmin::AbstractModel.new('Player')
@team = FactoryBot.create :team, name: 'king of bar'
Expand Down Expand Up @@ -341,17 +341,17 @@
end

it 'supports date type query' do
expect(@abstract_model.send(:filter_conditions, 'date_field' => {'1' => {v: ['', 'January 02, 2012', 'January 03, 2012'], o: 'between'}})).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 2), '$lte' => Date.new(2012, 1, 3)}}])
expect(@abstract_model.send(:filter_conditions, 'date_field' => {'1' => {v: ['', 'January 03, 2012', ''], o: 'between'}})).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 3)}}])
expect(@abstract_model.send(:filter_conditions, 'date_field' => {'1' => {v: ['', '', 'January 02, 2012'], o: 'between'}})).to eq('$and' => [{'date_field' => {'$lte' => Date.new(2012, 1, 2)}}])
expect(@abstract_model.send(:filter_conditions, 'date_field' => {'1' => {v: ['January 02, 2012'], o: 'default'}})).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 2), '$lte' => Date.new(2012, 1, 2)}}])
expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['', 'January 02, 2012', 'January 03, 2012'], o: 'between'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 2), '$lte' => Date.new(2012, 1, 3)}}])
expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['', 'January 03, 2012', ''], o: 'between'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 3)}}])
expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['', '', 'January 02, 2012'], o: 'between'}}).selector).to eq('$and' => [{'date_field' => {'$lte' => Date.new(2012, 1, 2)}}])
expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['January 02, 2012'], o: 'default'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 2), '$lte' => Date.new(2012, 1, 2)}}])
end

it 'supports datetime type query' do
expect(@abstract_model.send(:filter_conditions, 'datetime_field' => {'1' => {v: ['', 'January 02, 2012 00:00', 'January 03, 2012 00:00'], o: 'between'}})).to eq('$and' => [{'datetime_field' => {'$gte' => Time.local(2012, 1, 2), '$lte' => Time.local(2012, 1, 3).end_of_day}}])
expect(@abstract_model.send(:filter_conditions, 'datetime_field' => {'1' => {v: ['', 'January 03, 2012 00:00', ''], o: 'between'}})).to eq('$and' => [{'datetime_field' => {'$gte' => Time.local(2012, 1, 3)}}])
expect(@abstract_model.send(:filter_conditions, 'datetime_field' => {'1' => {v: ['', '', 'January 02, 2012 00:00'], o: 'between'}})).to eq('$and' => [{'datetime_field' => {'$lte' => Time.local(2012, 1, 2).end_of_day}}])
expect(@abstract_model.send(:filter_conditions, 'datetime_field' => {'1' => {v: ['January 02, 2012 00:00'], o: 'default'}})).to eq('$and' => [{'datetime_field' => {'$gte' => Time.local(2012, 1, 2), '$lte' => Time.local(2012, 1, 2).end_of_day}}])
expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['', 'January 02, 2012 00:00', 'January 03, 2012 00:00'], o: 'between'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Time.local(2012, 1, 2), '$lte' => Time.local(2012, 1, 3).end_of_day}}])
expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['', 'January 03, 2012 00:00', ''], o: 'between'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Time.local(2012, 1, 3)}}])
expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['', '', 'January 02, 2012 00:00'], o: 'between'}}).selector).to eq('$and' => [{'datetime_field' => {'$lte' => Time.local(2012, 1, 2).end_of_day}}])
expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['January 02, 2012 00:00'], o: 'default'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Time.local(2012, 1, 2), '$lte' => Time.local(2012, 1, 2).end_of_day}}])
end

it 'supports enum type query' do
Expand Down

0 comments on commit 9abc7f9

Please sign in to comment.