From 39ef1321aaf1a5ced69def879cffd11ee28c4bdc Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Mon, 25 Jul 2016 13:43:10 +1000 Subject: [PATCH 1/5] Fix #1099: presence filtering on boolean columns Filtering a boolean field on "Is present" or "Is blank" produces SQL that includes checking against an empty string. Since a boolean field can only be true, false, or null, this causes errors in some databases. This commit removes the empty string search for boolean field types. --- lib/rails_admin/adapters/active_record.rb | 44 ++++++++++++++++------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/lib/rails_admin/adapters/active_record.rb b/lib/rails_admin/adapters/active_record.rb index 999fe65ee3..77b6885c07 100644 --- a/lib/rails_admin/adapters/active_record.rb +++ b/lib/rails_admin/adapters/active_record.rb @@ -152,22 +152,40 @@ def build_statement(column, type, value, operator) end class StatementBuilder < RailsAdmin::AbstractModel::StatementBuilder - protected + protected - def unary_operators - { - '_blank' => ["(#{@column} IS NULL OR #{@column} = '')"], - '_present' => ["(#{@column} IS NOT NULL AND #{@column} != '')"], - '_null' => ["(#{@column} IS NULL)"], - '_not_null' => ["(#{@column} IS NOT NULL)"], - '_empty' => ["(#{@column} = '')"], - '_not_empty' => ["(#{@column} != '')"], - } - end + def unary_operators + case @type + when :boolean + boolean_unary_operators + else + generic_unary_operators + end + end - private + private + + def generic_unary_operators + { + '_blank' => ["(#{@column} IS NULL OR #{@column} = '')"], + '_present' => ["(#{@column} IS NOT NULL AND #{@column} != '')"], + '_null' => ["(#{@column} IS NULL)"], + '_not_null' => ["(#{@column} IS NOT NULL)"], + '_empty' => ["(#{@column} = '')"], + '_not_empty' => ["(#{@column} != '')"] + } + end + + def boolean_unary_operators + generic_unary_operators.merge( + { + '_blank' => ["(#{@column} IS NULL)"], + '_present' => ["(#{@column} IS NOT NULL)"] + } + ) + end - def range_filter(min, max) + def range_filter(min, max) if min && max ["(#{@column} BETWEEN ? AND ?)", min, max] elsif min From 3dc50133dea7b354b93cbe946667ecfa85196269 Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Mon, 25 Jul 2016 13:50:23 +1000 Subject: [PATCH 2/5] Fix incorrect indentation --- lib/rails_admin/adapters/active_record.rb | 54 +++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/rails_admin/adapters/active_record.rb b/lib/rails_admin/adapters/active_record.rb index 77b6885c07..3c979e1007 100644 --- a/lib/rails_admin/adapters/active_record.rb +++ b/lib/rails_admin/adapters/active_record.rb @@ -152,40 +152,40 @@ def build_statement(column, type, value, operator) end class StatementBuilder < RailsAdmin::AbstractModel::StatementBuilder - protected + protected - def unary_operators - case @type - when :boolean - boolean_unary_operators - else - generic_unary_operators - end + def unary_operators + case @type + when :boolean + boolean_unary_operators + else + generic_unary_operators end + end - private + private + + def generic_unary_operators + { + '_blank' => ["(#{@column} IS NULL OR #{@column} = '')"], + '_present' => ["(#{@column} IS NOT NULL AND #{@column} != '')"], + '_null' => ["(#{@column} IS NULL)"], + '_not_null' => ["(#{@column} IS NOT NULL)"], + '_empty' => ["(#{@column} = '')"], + '_not_empty' => ["(#{@column} != '')"] + } + end - def generic_unary_operators + def boolean_unary_operators + generic_unary_operators.merge( { - '_blank' => ["(#{@column} IS NULL OR #{@column} = '')"], - '_present' => ["(#{@column} IS NOT NULL AND #{@column} != '')"], - '_null' => ["(#{@column} IS NULL)"], - '_not_null' => ["(#{@column} IS NOT NULL)"], - '_empty' => ["(#{@column} = '')"], - '_not_empty' => ["(#{@column} != '')"] + '_blank' => ["(#{@column} IS NULL)"], + '_present' => ["(#{@column} IS NOT NULL)"] } - end - - def boolean_unary_operators - generic_unary_operators.merge( - { - '_blank' => ["(#{@column} IS NULL)"], - '_present' => ["(#{@column} IS NOT NULL)"] - } - ) - end + ) + end - def range_filter(min, max) + def range_filter(min, max) if min && max ["(#{@column} BETWEEN ? AND ?)", min, max] elsif min From f8e826d8648060104faffdb53ae3acbd79bb74bb Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Wed, 27 Jul 2016 11:12:34 +1000 Subject: [PATCH 3/5] Remove redundant curly braces --- lib/rails_admin/adapters/active_record.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/rails_admin/adapters/active_record.rb b/lib/rails_admin/adapters/active_record.rb index 3c979e1007..6260990e73 100644 --- a/lib/rails_admin/adapters/active_record.rb +++ b/lib/rails_admin/adapters/active_record.rb @@ -178,10 +178,8 @@ def generic_unary_operators def boolean_unary_operators generic_unary_operators.merge( - { - '_blank' => ["(#{@column} IS NULL)"], - '_present' => ["(#{@column} IS NOT NULL)"] - } + '_blank' => ["(#{@column} IS NULL)"], + '_present' => ["(#{@column} IS NOT NULL)"] ) end From ac12d19248e2846bec5e8fe4a5f80ebc724ff8e6 Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Mon, 22 Aug 2016 10:20:02 +1000 Subject: [PATCH 4/5] Support empty and not empty on boolean fields incase they are ever added to the UI --- lib/rails_admin/adapters/active_record.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/rails_admin/adapters/active_record.rb b/lib/rails_admin/adapters/active_record.rb index 6260990e73..27baec8088 100644 --- a/lib/rails_admin/adapters/active_record.rb +++ b/lib/rails_admin/adapters/active_record.rb @@ -172,14 +172,16 @@ def generic_unary_operators '_null' => ["(#{@column} IS NULL)"], '_not_null' => ["(#{@column} IS NOT NULL)"], '_empty' => ["(#{@column} = '')"], - '_not_empty' => ["(#{@column} != '')"] + '_not_empty' => ["(#{@column} != '')"], } end def boolean_unary_operators generic_unary_operators.merge( '_blank' => ["(#{@column} IS NULL)"], - '_present' => ["(#{@column} IS NOT NULL)"] + '_empty' => ["(#{@column} IS NULL)"], + '_present' => ["(#{@column} IS NOT NULL)"], + '_not_empty' => ["(#{@column} IS NOT NULL)"], ) end From 3e78509b90c1df94e7c849d9b0a6a916ed03bfd5 Mon Sep 17 00:00:00 2001 From: Adam Rice Date: Mon, 22 Aug 2016 10:20:16 +1000 Subject: [PATCH 5/5] Add tests for checking if boolean fields are blank or present --- .../adapters/active_record_spec.rb | 288 ++++++++++-------- 1 file changed, 168 insertions(+), 120 deletions(-) diff --git a/spec/rails_admin/adapters/active_record_spec.rb b/spec/rails_admin/adapters/active_record_spec.rb index 05424bdeea..86113a8e00 100644 --- a/spec/rails_admin/adapters/active_record_spec.rb +++ b/spec/rails_admin/adapters/active_record_spec.rb @@ -179,158 +179,206 @@ class PlayerWithDefaultScope < Player describe '#build_statement' do let(:abstract_model) { RailsAdmin::AbstractModel.new('FieldTest') } + def build_statement(type, value, operator) + abstract_model.send(:build_statement, :field, type, value, operator) + end + it "ignores '_discard' operator or value" do [['_discard', ''], ['', '_discard']].each do |value, operator| - expect(abstract_model.send(:build_statement, :name, :string, value, operator)).to be_nil + expect(build_statement(:string, value, operator)).to be_nil end end - it "supports '_blank' operator" do - [['_blank', ''], ['', '_blank']].each do |value, operator| - expect(abstract_model.send(:build_statement, :name, :string, value, operator)).to eq(["(name IS NULL OR name = '')"]) + describe 'string type queries' do + it 'supports string type query' do + expect(build_statement(:string, '', nil)).to be_nil + expect(build_statement(:string, 'foo', 'was')).to be_nil + expect(build_statement(:string, 'foo', 'default')).to eq([@like, '%foo%']) + expect(build_statement(:string, 'foo', 'like')).to eq([@like, '%foo%']) + expect(build_statement(:string, 'foo', 'starts_with')).to eq([@like, 'foo%']) + expect(build_statement(:string, 'foo', 'ends_with')).to eq([@like, '%foo']) + expect(build_statement(:string, 'foo', 'is')).to eq([@like, 'foo']) end - end - it "supports '_present' operator" do - [['_present', ''], ['', '_present']].each do |value, operator| - expect(abstract_model.send(:build_statement, :name, :string, value, operator)).to eq(["(name IS NOT NULL AND name != '')"]) + it 'performs case-insensitive searches' do + expect(build_statement(:string, 'foo', 'default')).to eq([@like, '%foo%']) + expect(build_statement(:string, 'FOO', 'default')).to eq([@like, '%foo%']) end - end - it "supports '_null' operator" do - [['_null', ''], ['', '_null']].each do |value, operator| - expect(abstract_model.send(:build_statement, :name, :string, value, operator)).to eq(['(name IS NULL)']) + it "supports '_blank' operator" do + [['_blank', ''], ['', '_blank']].each do |value, operator| + expect(build_statement(:string, value, operator)).to eq(["(field IS NULL OR field = '')"]) + end end - end - it "supports '_not_null' operator" do - [['_not_null', ''], ['', '_not_null']].each do |value, operator| - expect(abstract_model.send(:build_statement, :name, :string, value, operator)).to eq(['(name IS NOT NULL)']) + it "supports '_present' operator" do + [['_present', ''], ['', '_present']].each do |value, operator| + expect(build_statement(:string, value, operator)).to eq(["(field IS NOT NULL AND field != '')"]) + end end - end - it "supports '_empty' operator" do - [['_empty', ''], ['', '_empty']].each do |value, operator| - expect(abstract_model.send(:build_statement, :name, :string, value, operator)).to eq(["(name = '')"]) + it "supports '_null' operator" do + [['_null', ''], ['', '_null']].each do |value, operator| + expect(build_statement(:string, value, operator)).to eq(['(field IS NULL)']) + end end - end - it "supports '_not_empty' operator" do - [['_not_empty', ''], ['', '_not_empty']].each do |value, operator| - expect(abstract_model.send(:build_statement, :name, :string, value, operator)).to eq(["(name != '')"]) + it "supports '_not_null' operator" do + [['_not_null', ''], ['', '_not_null']].each do |value, operator| + expect(build_statement(:string, value, operator)).to eq(['(field IS NOT NULL)']) + end end - end - it 'supports boolean type query' do - %w(false f 0).each do |value| - expect(abstract_model.send(:build_statement, :field, :boolean, value, nil)).to eq(['(field IS NULL OR field = ?)', false]) + it "supports '_empty' operator" do + [['_empty', ''], ['', '_empty']].each do |value, operator| + expect(build_statement(:string, value, operator)).to eq(["(field = '')"]) + end end - %w(true t 1).each do |value| - expect(abstract_model.send(:build_statement, :field, :boolean, value, nil)).to eq(['(field = ?)', true]) + + it "supports '_not_empty' operator" do + [['_not_empty', ''], ['', '_not_empty']].each do |value, operator| + expect(build_statement(:string, value, operator)).to eq(["(field != '')"]) + end end - expect(abstract_model.send(:build_statement, :field, :boolean, 'word', nil)).to be_nil end - it 'supports integer type query' do - expect(abstract_model.send(:build_statement, :field, :integer, '1', nil)).to eq(['(field = ?)', 1]) - expect(abstract_model.send(:build_statement, :field, :integer, 'word', nil)).to be_nil - expect(abstract_model.send(:build_statement, :field, :integer, '1', 'default')).to eq(['(field = ?)', 1]) - expect(abstract_model.send(:build_statement, :field, :integer, 'word', 'default')).to be_nil - expect(abstract_model.send(:build_statement, :field, :integer, '1', 'between')).to eq(['(field = ?)', 1]) - expect(abstract_model.send(:build_statement, :field, :integer, 'word', 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :integer, ['6', '', ''], 'default')).to eq(['(field = ?)', 6]) - expect(abstract_model.send(:build_statement, :field, :integer, ['7', '10', ''], 'default')).to eq(['(field = ?)', 7]) - expect(abstract_model.send(:build_statement, :field, :integer, ['8', '', '20'], 'default')).to eq(['(field = ?)', 8]) - expect(abstract_model.send(:build_statement, :field, :integer, %w(9 10 20), 'default')).to eq(['(field = ?)', 9]) - end + describe 'boolean type queries' do + it 'supports boolean type query' do + %w(false f 0).each do |value| + expect(build_statement(:boolean, value, nil)).to eq(['(field IS NULL OR field = ?)', false]) + end + %w(true t 1).each do |value| + expect(build_statement(:boolean, value, nil)).to eq(['(field = ?)', true]) + end + expect(build_statement(:boolean, 'word', nil)).to be_nil + end - it 'supports integer type range query' do - expect(abstract_model.send(:build_statement, :field, :integer, ['', '', ''], 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :integer, ['2', '', ''], 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :integer, ['', '3', ''], 'between')).to eq(['(field >= ?)', 3]) - expect(abstract_model.send(:build_statement, :field, :integer, ['', '', '5'], 'between')).to eq(['(field <= ?)', 5]) - expect(abstract_model.send(:build_statement, :field, :integer, ['', '10', '20'], 'between')).to eq(['(field BETWEEN ? AND ?)', 10, 20]) - expect(abstract_model.send(:build_statement, :field, :integer, %w(15 10 20), 'between')).to eq(['(field BETWEEN ? AND ?)', 10, 20]) - expect(abstract_model.send(:build_statement, :field, :integer, ['', 'word1', ''], 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :integer, ['', '', 'word2'], 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :integer, ['', 'word3', 'word4'], 'between')).to be_nil - end + it "supports '_blank' operator" do + [['_blank', ''], ['', '_blank']].each do |value, operator| + expect(build_statement(:boolean, value, operator)).to eq(["(field IS NULL)"]) + end + end - it 'supports both decimal and float type queries' do - expect(abstract_model.send(:build_statement, :field, :decimal, '1.1', nil)).to eq(['(field = ?)', 1.1]) - expect(abstract_model.send(:build_statement, :field, :decimal, 'word', nil)).to be_nil - expect(abstract_model.send(:build_statement, :field, :decimal, '1.1', 'default')).to eq(['(field = ?)', 1.1]) - expect(abstract_model.send(:build_statement, :field, :decimal, 'word', 'default')).to be_nil - expect(abstract_model.send(:build_statement, :field, :decimal, '1.1', 'between')).to eq(['(field = ?)', 1.1]) - expect(abstract_model.send(:build_statement, :field, :decimal, 'word', 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :decimal, ['6.1', '', ''], 'default')).to eq(['(field = ?)', 6.1]) - expect(abstract_model.send(:build_statement, :field, :decimal, ['7.1', '10.1', ''], 'default')).to eq(['(field = ?)', 7.1]) - expect(abstract_model.send(:build_statement, :field, :decimal, ['8.1', '', '20.1'], 'default')).to eq(['(field = ?)', 8.1]) - expect(abstract_model.send(:build_statement, :field, :decimal, ['9.1', '10.1', '20.1'], 'default')).to eq(['(field = ?)', 9.1]) - expect(abstract_model.send(:build_statement, :field, :decimal, ['', '', ''], 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :decimal, ['2.1', '', ''], 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :decimal, ['', '3.1', ''], 'between')).to eq(['(field >= ?)', 3.1]) - expect(abstract_model.send(:build_statement, :field, :decimal, ['', '', '5.1'], 'between')).to eq(['(field <= ?)', 5.1]) - expect(abstract_model.send(:build_statement, :field, :decimal, ['', '10.1', '20.1'], 'between')).to eq(['(field BETWEEN ? AND ?)', 10.1, 20.1]) - expect(abstract_model.send(:build_statement, :field, :decimal, ['15.1', '10.1', '20.1'], 'between')).to eq(['(field BETWEEN ? AND ?)', 10.1, 20.1]) - expect(abstract_model.send(:build_statement, :field, :decimal, ['', 'word1', ''], 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :decimal, ['', '', 'word2'], 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :decimal, ['', 'word3', 'word4'], 'between')).to be_nil - - expect(abstract_model.send(:build_statement, :field, :float, '1.1', nil)).to eq(['(field = ?)', 1.1]) - expect(abstract_model.send(:build_statement, :field, :float, 'word', nil)).to be_nil - expect(abstract_model.send(:build_statement, :field, :float, '1.1', 'default')).to eq(['(field = ?)', 1.1]) - expect(abstract_model.send(:build_statement, :field, :float, 'word', 'default')).to be_nil - expect(abstract_model.send(:build_statement, :field, :float, '1.1', 'between')).to eq(['(field = ?)', 1.1]) - expect(abstract_model.send(:build_statement, :field, :float, 'word', 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :float, ['6.1', '', ''], 'default')).to eq(['(field = ?)', 6.1]) - expect(abstract_model.send(:build_statement, :field, :float, ['7.1', '10.1', ''], 'default')).to eq(['(field = ?)', 7.1]) - expect(abstract_model.send(:build_statement, :field, :float, ['8.1', '', '20.1'], 'default')).to eq(['(field = ?)', 8.1]) - expect(abstract_model.send(:build_statement, :field, :float, ['9.1', '10.1', '20.1'], 'default')).to eq(['(field = ?)', 9.1]) - expect(abstract_model.send(:build_statement, :field, :float, ['', '', ''], 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :float, ['2.1', '', ''], 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :float, ['', '3.1', ''], 'between')).to eq(['(field >= ?)', 3.1]) - expect(abstract_model.send(:build_statement, :field, :float, ['', '', '5.1'], 'between')).to eq(['(field <= ?)', 5.1]) - expect(abstract_model.send(:build_statement, :field, :float, ['', '10.1', '20.1'], 'between')).to eq(['(field BETWEEN ? AND ?)', 10.1, 20.1]) - expect(abstract_model.send(:build_statement, :field, :float, ['15.1', '10.1', '20.1'], 'between')).to eq(['(field BETWEEN ? AND ?)', 10.1, 20.1]) - expect(abstract_model.send(:build_statement, :field, :float, ['', 'word1', ''], 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :float, ['', '', 'word2'], 'between')).to be_nil - expect(abstract_model.send(:build_statement, :field, :float, ['', 'word3', 'word4'], 'between')).to be_nil - end + it "supports '_present' operator" do + [['_present', ''], ['', '_present']].each do |value, operator| + expect(build_statement(:boolean, value, operator)).to eq(["(field IS NOT NULL)"]) + end + end - it 'supports string type query' do - expect(abstract_model.send(:build_statement, :field, :string, '', nil)).to be_nil - expect(abstract_model.send(:build_statement, :field, :string, 'foo', 'was')).to be_nil - expect(abstract_model.send(:build_statement, :field, :string, 'foo', 'default')).to eq([@like, '%foo%']) - expect(abstract_model.send(:build_statement, :field, :string, 'foo', 'like')).to eq([@like, '%foo%']) - expect(abstract_model.send(:build_statement, :field, :string, 'foo', 'starts_with')).to eq([@like, 'foo%']) - expect(abstract_model.send(:build_statement, :field, :string, 'foo', 'ends_with')).to eq([@like, '%foo']) - expect(abstract_model.send(:build_statement, :field, :string, 'foo', 'is')).to eq([@like, 'foo']) - end + it "supports '_null' operator" do + [['_null', ''], ['', '_null']].each do |value, operator| + expect(build_statement(:boolean, value, operator)).to eq(['(field IS NULL)']) + end + end + + it "supports '_not_null' operator" do + [['_not_null', ''], ['', '_not_null']].each do |value, operator| + expect(build_statement(:boolean, value, operator)).to eq(['(field IS NOT NULL)']) + end + end + + it "supports '_empty' operator" do + [['_empty', ''], ['', '_empty']].each do |value, operator| + expect(build_statement(:boolean, value, operator)).to eq(["(field IS NULL)"]) + end + end - it 'performs case-insensitive searches' do - expect(abstract_model.send(:build_statement, :field, :string, 'foo', 'default')).to eq([@like, '%foo%']) - expect(abstract_model.send(:build_statement, :field, :string, 'FOO', 'default')).to eq([@like, '%foo%']) + it "supports '_not_empty' operator" do + [['_not_empty', ''], ['', '_not_empty']].each do |value, operator| + expect(build_statement(:boolean, value, operator)).to eq(["(field IS NOT NULL)"]) + end + end end - it 'supports date type query' do - scope = FieldTest.all - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', 'February 01, 2012', 'March 01, 2012'], o: 'between'}}))).to eq(["(field_tests.date_field BETWEEN '2012-02-01' AND '2012-03-01')"]) - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', 'March 01, 2012', ''], o: 'between'}}))).to eq(["(field_tests.date_field >= '2012-03-01')"]) - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', '', 'February 01, 2012'], o: 'between'}}))).to eq(["(field_tests.date_field <= '2012-02-01')"]) - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['February 01, 2012'], o: 'default'}}))).to eq(["(field_tests.date_field BETWEEN '2012-02-01' AND '2012-02-01')"]) + describe 'numeric type queries' do + it 'supports integer type query' do + expect(build_statement(:integer, '1', nil)).to eq(['(field = ?)', 1]) + expect(build_statement(:integer, 'word', nil)).to be_nil + expect(build_statement(:integer, '1', 'default')).to eq(['(field = ?)', 1]) + expect(build_statement(:integer, 'word', 'default')).to be_nil + expect(build_statement(:integer, '1', 'between')).to eq(['(field = ?)', 1]) + expect(build_statement(:integer, 'word', 'between')).to be_nil + expect(build_statement(:integer, ['6', '', ''], 'default')).to eq(['(field = ?)', 6]) + expect(build_statement(:integer, ['7', '10', ''], 'default')).to eq(['(field = ?)', 7]) + expect(build_statement(:integer, ['8', '', '20'], 'default')).to eq(['(field = ?)', 8]) + expect(build_statement(:integer, %w(9 10 20), 'default')).to eq(['(field = ?)', 9]) + end + + it 'supports integer type range query' do + expect(build_statement(:integer, ['', '', ''], 'between')).to be_nil + expect(build_statement(:integer, ['2', '', ''], 'between')).to be_nil + expect(build_statement(:integer, ['', '3', ''], 'between')).to eq(['(field >= ?)', 3]) + expect(build_statement(:integer, ['', '', '5'], 'between')).to eq(['(field <= ?)', 5]) + expect(build_statement(:integer, ['', '10', '20'], 'between')).to eq(['(field BETWEEN ? AND ?)', 10, 20]) + expect(build_statement(:integer, %w(15 10 20), 'between')).to eq(['(field BETWEEN ? AND ?)', 10, 20]) + expect(build_statement(:integer, ['', 'word1', ''], 'between')).to be_nil + expect(build_statement(:integer, ['', '', 'word2'], 'between')).to be_nil + expect(build_statement(:integer, ['', 'word3', 'word4'], 'between')).to be_nil + end + + it 'supports both decimal and float type queries' do + expect(build_statement(:decimal, '1.1', nil)).to eq(['(field = ?)', 1.1]) + expect(build_statement(:decimal, 'word', nil)).to be_nil + expect(build_statement(:decimal, '1.1', 'default')).to eq(['(field = ?)', 1.1]) + expect(build_statement(:decimal, 'word', 'default')).to be_nil + expect(build_statement(:decimal, '1.1', 'between')).to eq(['(field = ?)', 1.1]) + expect(build_statement(:decimal, 'word', 'between')).to be_nil + expect(build_statement(:decimal, ['6.1', '', ''], 'default')).to eq(['(field = ?)', 6.1]) + expect(build_statement(:decimal, ['7.1', '10.1', ''], 'default')).to eq(['(field = ?)', 7.1]) + expect(build_statement(:decimal, ['8.1', '', '20.1'], 'default')).to eq(['(field = ?)', 8.1]) + expect(build_statement(:decimal, ['9.1', '10.1', '20.1'], 'default')).to eq(['(field = ?)', 9.1]) + expect(build_statement(:decimal, ['', '', ''], 'between')).to be_nil + expect(build_statement(:decimal, ['2.1', '', ''], 'between')).to be_nil + expect(build_statement(:decimal, ['', '3.1', ''], 'between')).to eq(['(field >= ?)', 3.1]) + expect(build_statement(:decimal, ['', '', '5.1'], 'between')).to eq(['(field <= ?)', 5.1]) + expect(build_statement(:decimal, ['', '10.1', '20.1'], 'between')).to eq(['(field BETWEEN ? AND ?)', 10.1, 20.1]) + expect(build_statement(:decimal, ['15.1', '10.1', '20.1'], 'between')).to eq(['(field BETWEEN ? AND ?)', 10.1, 20.1]) + expect(build_statement(:decimal, ['', 'word1', ''], 'between')).to be_nil + expect(build_statement(:decimal, ['', '', 'word2'], 'between')).to be_nil + expect(build_statement(:decimal, ['', 'word3', 'word4'], 'between')).to be_nil + + expect(build_statement(:float, '1.1', nil)).to eq(['(field = ?)', 1.1]) + expect(build_statement(:float, 'word', nil)).to be_nil + expect(build_statement(:float, '1.1', 'default')).to eq(['(field = ?)', 1.1]) + expect(build_statement(:float, 'word', 'default')).to be_nil + expect(build_statement(:float, '1.1', 'between')).to eq(['(field = ?)', 1.1]) + expect(build_statement(:float, 'word', 'between')).to be_nil + expect(build_statement(:float, ['6.1', '', ''], 'default')).to eq(['(field = ?)', 6.1]) + expect(build_statement(:float, ['7.1', '10.1', ''], 'default')).to eq(['(field = ?)', 7.1]) + expect(build_statement(:float, ['8.1', '', '20.1'], 'default')).to eq(['(field = ?)', 8.1]) + expect(build_statement(:float, ['9.1', '10.1', '20.1'], 'default')).to eq(['(field = ?)', 9.1]) + expect(build_statement(:float, ['', '', ''], 'between')).to be_nil + expect(build_statement(:float, ['2.1', '', ''], 'between')).to be_nil + expect(build_statement(:float, ['', '3.1', ''], 'between')).to eq(['(field >= ?)', 3.1]) + expect(build_statement(:float, ['', '', '5.1'], 'between')).to eq(['(field <= ?)', 5.1]) + expect(build_statement(:float, ['', '10.1', '20.1'], 'between')).to eq(['(field BETWEEN ? AND ?)', 10.1, 20.1]) + expect(build_statement(:float, ['15.1', '10.1', '20.1'], 'between')).to eq(['(field BETWEEN ? AND ?)', 10.1, 20.1]) + expect(build_statement(:float, ['', 'word1', ''], 'between')).to be_nil + expect(build_statement(:float, ['', '', 'word2'], 'between')).to be_nil + expect(build_statement(:float, ['', 'word3', 'word4'], 'between')).to be_nil + end end - it 'supports datetime type query' do - scope = FieldTest.all - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', 'February 01, 2012 12:00', 'March 01, 2012 12:00'], o: 'between'}}))).to eq(predicates_for(scope.where(['(field_tests.datetime_field BETWEEN ? AND ?)', Time.local(2012, 2, 1), Time.local(2012, 3, 1).end_of_day]))) - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', 'March 01, 2012 12:00', ''], o: 'between'}}))).to eq(predicates_for(scope.where(['(field_tests.datetime_field >= ?)', Time.local(2012, 3, 1)]))) - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', '', 'February 01, 2012 12:00'], o: 'between'}}))).to eq(predicates_for(scope.where(['(field_tests.datetime_field <= ?)', Time.local(2012, 2, 1).end_of_day]))) - expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['February 01, 2012 12:00'], o: 'default'}}))).to eq(predicates_for(scope.where(['(field_tests.datetime_field BETWEEN ? AND ?)', Time.local(2012, 2, 1), Time.local(2012, 2, 1).end_of_day]))) + describe 'date type queries' do + let(:scope) { FieldTest.all } + + it 'supports date type query' do + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', 'February 01, 2012', 'March 01, 2012'], o: 'between'}}))).to eq(["(field_tests.date_field BETWEEN '2012-02-01' AND '2012-03-01')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', 'March 01, 2012', ''], o: 'between'}}))).to eq(["(field_tests.date_field >= '2012-03-01')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['', '', 'February 01, 2012'], o: 'between'}}))).to eq(["(field_tests.date_field <= '2012-02-01')"]) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'date_field' => {'1' => {v: ['February 01, 2012'], o: 'default'}}))).to eq(["(field_tests.date_field BETWEEN '2012-02-01' AND '2012-02-01')"]) + end + + it 'supports datetime type query' do + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', 'February 01, 2012 12:00', 'March 01, 2012 12:00'], o: 'between'}}))).to eq(predicates_for(scope.where(['(field_tests.datetime_field BETWEEN ? AND ?)', Time.local(2012, 2, 1), Time.local(2012, 3, 1).end_of_day]))) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', 'March 01, 2012 12:00', ''], o: 'between'}}))).to eq(predicates_for(scope.where(['(field_tests.datetime_field >= ?)', Time.local(2012, 3, 1)]))) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['', '', 'February 01, 2012 12:00'], o: 'between'}}))).to eq(predicates_for(scope.where(['(field_tests.datetime_field <= ?)', Time.local(2012, 2, 1).end_of_day]))) + expect(predicates_for(abstract_model.send(:filter_scope, scope, 'datetime_field' => {'1' => {v: ['February 01, 2012 12:00'], o: 'default'}}))).to eq(predicates_for(scope.where(['(field_tests.datetime_field BETWEEN ? AND ?)', Time.local(2012, 2, 1), Time.local(2012, 2, 1).end_of_day]))) + end end it 'supports enum type query' do - expect(abstract_model.send(:build_statement, :field, :enum, '1', nil)).to eq(['(field IN (?))', ['1']]) + expect(build_statement(:enum, '1', nil)).to eq(['(field IN (?))', ['1']]) end end