diff --git a/.travis.yml b/.travis.yml index a4968b2..e529bfe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,21 @@ rvm: - - 1.8.7 - - 1.9.2 - 1.9.3 - 2.0.0 env: - - RAILS=4-0-stable - - RAILS=3-2-stable - - RAILS=3-1-stable - - RAILS=3-0-stable + - RAILS=4-0-stable DB=sqlite + - RAILS=4-0-stable DB=mysql + - RAILS=4-0-stable DB=postgres + - RAILS=3-2-stable DB=sqlite + - RAILS=3-2-stable DB=mysql + - RAILS=3-2-stable DB=postgres + - RAILS=3-1-stable DB=sqlite + - RAILS=3-1-stable DB=mysql + - RAILS=3-1-stable DB=postgres + - RAILS=3-0-stable DB=sqlite + - RAILS=3-0-stable DB=mysql + - RAILS=3-0-stable DB=postgres -matrix: - exclude: - - rvm: 1.8.7 - env: RAILS=4-0-stable +before_script: + - mysql -e 'create database ransack;' + - psql -c 'create database ransack;' -U postgres \ No newline at end of file diff --git a/README.md b/README.md index 31ed939..3ee9e33 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ransack -[![Build Status](https://travis-ci.org/ernie/ransack.png?branch=master)](https://travis-ci.org/ernie/ransack) +[![Build Status](https://travis-ci.org/activerecord-hackery/ransack)](https://travis-ci.org/ernie/ransack) Ransack is a rewrite of [MetaSearch](https://github.com/ernie/meta_search). While it supports many of the same features as MetaSearch, its underlying implementation differs diff --git a/lib/ransack.rb b/lib/ransack.rb index e418739..2ceeebf 100644 --- a/lib/ransack.rb +++ b/lib/ransack.rb @@ -19,7 +19,7 @@ end require 'ransack/translate' require 'ransack/search' require 'ransack/ransacker' -require 'ransack/adapters/active_record' +require 'ransack/adapters/active_record' if defined?(::ActiveRecord::Base) require 'ransack/helpers' require 'action_controller' diff --git a/lib/ransack/adapters/active_record.rb b/lib/ransack/adapters/active_record.rb index ca7c94e..5108a94 100644 --- a/lib/ransack/adapters/active_record.rb +++ b/lib/ransack/adapters/active_record.rb @@ -1,4 +1,3 @@ -require 'active_record' require 'ransack/adapters/active_record/base' ActiveRecord::Base.extend Ransack::Adapters::ActiveRecord::Base diff --git a/lib/ransack/adapters/active_record/base.rb b/lib/ransack/adapters/active_record/base.rb index 51d814b..a42d911 100644 --- a/lib/ransack/adapters/active_record/base.rb +++ b/lib/ransack/adapters/active_record/base.rb @@ -4,7 +4,7 @@ module Ransack module Base def self.extended(base) - alias :search :ransack unless base.method_defined? :search + alias :search :ransack unless base.respond_to? :search base.class_eval do class_attribute :_ransackers self._ransackers ||= {} diff --git a/lib/ransack/constants.rb b/lib/ransack/constants.rb index 44cdd3d..1991ea3 100644 --- a/lib/ransack/constants.rb +++ b/lib/ransack/constants.rb @@ -23,10 +23,12 @@ module Ransack module_function # replace % \ to \% \\ def escape_wildcards(unescaped) - if ActiveRecord::VERSION::MAJOR == 3 - unescaped.to_s.gsub(/([\\|\%|.])/, '\\\\\\1') - else - unescaped.to_s.gsub(/\\/){ "\\\\" }.gsub(/%/, "\\%") + case ActiveRecord::Base.connection.adapter_name + when "SQLite" + unescaped + else + # Necessary for PostgreSQL and MySQL + unescaped.to_s.gsub(/([\\|\%|.])/, '\\\\\\1') end end diff --git a/lib/ransack/version.rb b/lib/ransack/version.rb index c472356..8e0f1a8 100644 --- a/lib/ransack/version.rb +++ b/lib/ransack/version.rb @@ -1,3 +1,3 @@ module Ransack - VERSION = "1.0.0" + VERSION = "1.1.0" end diff --git a/ransack.gemspec b/ransack.gemspec index 51b145b..1c1cc67 100644 --- a/ransack.gemspec +++ b/ransack.gemspec @@ -11,6 +11,7 @@ Gem::Specification.new do |s| s.homepage = "https://github.com/ernie/ransack" s.summary = %q{Object-based searching for ActiveRecord (currently).} 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.} + s.license = 'MIT' s.rubyforge_project = "ransack" @@ -21,6 +22,9 @@ Gem::Specification.new do |s| s.add_development_dependency 'machinist', '~> 1.0.6' s.add_development_dependency 'faker', '~> 0.9.5' s.add_development_dependency 'sqlite3', '~> 1.3.3' + s.add_development_dependency 'pg', '0.17.0' + s.add_development_dependency 'mysql2', '0.3.13' + s.add_development_dependency 'pry', '0.9.12.2' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") diff --git a/spec/blueprints/people.rb b/spec/blueprints/people.rb index b7ba882..42b0292 100644 --- a/spec/blueprints/people.rb +++ b/spec/blueprints/people.rb @@ -1,4 +1,5 @@ Person.blueprint do name + email { "test@example.com" } salary end \ No newline at end of file diff --git a/spec/helpers/ransack_helper.rb b/spec/helpers/ransack_helper.rb index b7374aa..dfdaceb 100644 --- a/spec/helpers/ransack_helper.rb +++ b/spec/helpers/ransack_helper.rb @@ -1,2 +1,9 @@ module RansackHelper + def quote_table_name(table) + ActiveRecord::Base.connection.quote_table_name(table) + end + + def quote_column_name(column) + ActiveRecord::Base.connection.quote_column_name(column) + end end \ No newline at end of file diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index 012f0ff..53bb710 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -20,6 +20,15 @@ module Ransack end describe '#ransacker' do + # For infix tests + def self.sane_adapter? + case ::ActiveRecord::Base.connection.adapter_name + when "SQLite3", "PostgreSQL" + true + else + false + end + end # in schema.rb, class Person: # ransacker :reversed_name, :formatter => proc {|v| v.reverse} do |parent| # parent.table[:name] @@ -36,18 +45,36 @@ module Ransack it 'can be accessed through associations' do s = Person.search(:children_reversed_name_eq => 'htimS cirA') - s.result.to_sql.should match /"children_people"."name" = 'Aric Smith'/ + + s.result.to_sql.should match /#{quote_table_name("children_people")}.#{quote_column_name("name")} = 'Aric Smith'/ end it 'allows an "attribute" to be an InfixOperation' do s = Person.search(:doubled_name_eq => 'Aric SmithAric Smith') s.result.first.should eq Person.find_by_name('Aric Smith') - end if defined?(Arel::Nodes::InfixOperation) + end if defined?(Arel::Nodes::InfixOperation) && sane_adapter? it "doesn't break #count if using InfixOperations" do s = Person.search(:doubled_name_eq => 'Aric SmithAric Smith') s.result.count.should eq 1 - end if defined?(Arel::Nodes::InfixOperation) + end if defined?(Arel::Nodes::InfixOperation) && sane_adapter? + + it "should function correctly when using fields with dots in them" do + s = Person.search(email_cont: "example.com") + s.result.exists?.should be_true + end + + it "should function correctly when using fields with % in them" do + Person.create!(name: "110%-er") + s = Person.search(name_cont: "10%") + s.result.exists?.should be_true + end + + it "should function correctly when using fields with backslashes in them" do + Person.create!(name: "\\WINNER\\") + s = Person.search(name_cont: "\\WINNER\\") + s.result.exists?.should be_true + end it 'allows sort by "only_sort" field' do s = Person.search("s"=>{"0"=>{"dir"=>"asc", "name"=>"only_sort"}}) diff --git a/spec/ransack/adapters/active_record/context_spec.rb b/spec/ransack/adapters/active_record/context_spec.rb index fb171bd..fac20d8 100644 --- a/spec/ransack/adapters/active_record/context_spec.rb +++ b/spec/ransack/adapters/active_record/context_spec.rb @@ -18,7 +18,7 @@ module Ransack result = subject.evaluate(search) result.should be_an ::ActiveRecord::Relation - result.to_sql.should match /"name" = 'Joe Blow'/ + result.to_sql.should match /#{quote_column_name("name")} = 'Joe Blow'/ end it 'SELECTs DISTINCT when :distinct => true' do diff --git a/spec/ransack/predicate_spec.rb b/spec/ransack/predicate_spec.rb index 36f9e6e..0e9ec6e 100644 --- a/spec/ransack/predicate_spec.rb +++ b/spec/ransack/predicate_spec.rb @@ -13,13 +13,7 @@ module Ransack expect { subject.result }.to_not raise_error end - it ( - if ActiveRecord::VERSION::MAJOR == 3 - "escapes '%', '.' and '\\\\' in value" - else - "escapes % and \\ in value" - end - ) do + it "escapes '%', '.' and '\\\\' in value" do subject.send(:"#{method}=", '%._\\') subject.result.to_sql.should match(regexp) end @@ -28,12 +22,14 @@ module Ransack describe 'eq' do it 'generates an equality condition for boolean true' do @s.awesome_eq = true - @s.result.to_sql.should match /"people"."awesome" = 't'/ + field = "#{quote_table_name("people")}.#{quote_column_name("awesome")}" + @s.result.to_sql.should match /#{field} = #{ActiveRecord::Base.connection.quoted_true}/ end it 'generates an equality condition for boolean false' do @s.awesome_eq = false - @s.result.to_sql.should match /"people"."awesome" = 'f'/ + field = "#{quote_table_name("people")}.#{quote_column_name("awesome")}" + @s.result.to_sql.should match /#{field} = #{ActiveRecord::Base.connection.quoted_false}/ end it 'does not generate a condition for nil' do @@ -44,48 +40,56 @@ module Ransack describe 'cont' do - it_has_behavior 'wildcard escaping', :name_cont, ( - if ActiveRecord::VERSION::MAJOR == 3 - /"people"."name" LIKE '%\\%\\._\\\\%'/ + it_has_behavior 'wildcard escaping', :name_cont, + (if ActiveRecord::Base.connection.adapter_name == "PostgreSQL" + /"people"."name" ILIKE '%\\%\\._\\\\%'/ + elsif ActiveRecord::Base.connection.adapter_name == "Mysql2" + /`people`.`name` LIKE '%\\\\%\\\\._\\\\\\\\%'/ else - /"people"."name" LIKE '%\\%._\\\\%'/ + /"people"."name" LIKE '%%._\\%'/ end) do subject { @s } end it 'generates a LIKE query with value surrounded by %' do @s.name_cont = 'ric' - @s.result.to_sql.should match /"people"."name" LIKE '%ric%'/ + field = "#{quote_table_name("people")}.#{quote_column_name("name")}" + @s.result.to_sql.should match /#{field} I?LIKE '%ric%'/ end end describe 'not_cont' do - it_has_behavior 'wildcard escaping', :name_not_cont, ( - if ActiveRecord::VERSION::MAJOR == 3 - /"people"."name" NOT LIKE '%\\%\\._\\\\%'/ + it_has_behavior 'wildcard escaping', :name_not_cont, + (if ActiveRecord::Base.connection.adapter_name == "PostgreSQL" + /"people"."name" NOT ILIKE '%\\%\\._\\\\%'/ + elsif ActiveRecord::Base.connection.adapter_name == "Mysql2" + /`people`.`name` NOT LIKE '%\\\\%\\\\._\\\\\\\\%'/ else - /"people"."name" NOT LIKE '%\\%._\\\\%'/ + /"people"."name" NOT LIKE '%%._\\%'/ end) do subject { @s } end it 'generates a NOT LIKE query with value surrounded by %' do @s.name_not_cont = 'ric' - @s.result.to_sql.should match /"people"."name" NOT LIKE '%ric%'/ + field = "#{quote_table_name("people")}.#{quote_column_name("name")}" + @s.result.to_sql.should match /#{field} NOT I?LIKE '%ric%'/ end end describe 'null' do it 'generates a value IS NULL query' do @s.name_null = true - @s.result.to_sql.should match /"people"."name" IS NULL/ + field = "#{quote_table_name("people")}.#{quote_column_name("name")}" + @s.result.to_sql.should match /#{field} IS NULL/ end end describe 'not_null' do it 'generates a value IS NOT NULL query' do @s.name_not_null = true - @s.result.to_sql.should match /"people"."name" IS NOT NULL/ + field = "#{quote_table_name("people")}.#{quote_column_name("name")}" + @s.result.to_sql.should match /#{field} IS NOT NULL/ end end end diff --git a/spec/ransack/search_spec.rb b/spec/ransack/search_spec.rb index 4894515..7dacdf9 100644 --- a/spec/ransack/search_spec.rb +++ b/spec/ransack/search_spec.rb @@ -113,25 +113,27 @@ module Ransack end describe '#result' do + let(:people_name_field) { "#{quote_table_name("people")}.#{quote_column_name("name")}" } + let(:children_people_name_field) { "#{quote_table_name("children_people")}.#{quote_column_name("name")}" } it 'evaluates conditions contextually' do search = Search.new(Person, :children_name_eq => 'Ernie') search.result.should be_an ActiveRecord::Relation where = search.result.where_values.first - where.to_sql.should match /"children_people"\."name" = 'Ernie'/ + where.to_sql.should match /#{children_people_name_field} = 'Ernie'/ end it 'evaluates compound conditions contextually' do search = Search.new(Person, :children_name_or_name_eq => 'Ernie') search.result.should be_an ActiveRecord::Relation where = search.result.where_values.first - where.to_sql.should match /"children_people"\."name" = 'Ernie' OR "people"\."name" = 'Ernie'/ + where.to_sql.should match /#{children_people_name_field} = 'Ernie' OR #{people_name_field} = 'Ernie'/ end it 'evaluates polymorphic belongs_to association conditions contextually' do search = Search.new(Note, :notable_of_Person_type_name_eq => 'Ernie') search.result.should be_an ActiveRecord::Relation where = search.result.where_values.first - where.to_sql.should match /"people"."name" = 'Ernie'/ + where.to_sql.should match /#{people_name_field} = 'Ernie'/ end it 'evaluates nested conditions' do @@ -145,9 +147,9 @@ module Ransack ) search.result.should be_an ActiveRecord::Relation where = search.result.where_values.first - where.to_sql.should match /"children_people"."name" = 'Ernie'/ - where.to_sql.should match /"people"."name" = 'Ernie'/ - where.to_sql.should match /"children_people_2"."name" = 'Ernie'/ + where.to_sql.should match /#{children_people_name_field} = 'Ernie'/ + where.to_sql.should match /#{people_name_field} = 'Ernie'/ + where.to_sql.should match /#{quote_table_name("children_people_2")}.#{quote_column_name("name")} = 'Ernie'/ end it 'evaluates arrays of groupings' do @@ -161,10 +163,10 @@ module Ransack where = search.result.where_values.first sql = where.to_sql first, second = sql.split(/ AND /) - first.should match /"people"."name" = 'Ernie'/ - first.should match /"children_people"."name" = 'Ernie'/ - second.should match /"people"."name" = 'Bert'/ - second.should match /"children_people"."name" = 'Bert'/ + first.should match /#{people_name_field} = 'Ernie'/ + first.should match /#{children_people_name_field} = 'Ernie'/ + second.should match /#{people_name_field} = 'Bert'/ + second.should match /#{children_people_name_field} = 'Bert'/ end it 'returns distinct records when passed :distinct => true' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 203a3ec..a25d698 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -24,7 +24,8 @@ RSpec.configure do |config| config.before(:suite) do puts '=' * 80 - puts "Running specs against ActiveRecord #{ActiveRecord::VERSION::STRING} and ARel #{Arel::VERSION}..." + connection_name = ActiveRecord::Base.connection.adapter_name + puts "Running specs against #{connection_name}, ActiveRecord #{ActiveRecord::VERSION::STRING} and ARel #{Arel::VERSION}..." puts '=' * 80 Schema.create end diff --git a/spec/support/schema.rb b/spec/support/schema.rb index 3782c09..5ecc8a5 100644 --- a/spec/support/schema.rb +++ b/spec/support/schema.rb @@ -1,9 +1,26 @@ require 'active_record' -ActiveRecord::Base.establish_connection( - :adapter => 'sqlite3', - :database => ':memory:' -) +case ENV['DB'] +when "mysql" + ActiveRecord::Base.establish_connection( + adapter: 'mysql2', + database: 'ransack', + encoding: 'utf8' + ) +when "postgres" + ActiveRecord::Base.establish_connection( + adapter: 'postgresql', + database: 'ransack', + username: 'postgres', + min_messages: 'warning' + ) +else + # Assume SQLite3 + ActiveRecord::Base.establish_connection( + adapter: 'sqlite3', + database: ':memory:' + ) +end class Person < ActiveRecord::Base if ActiveRecord::VERSION::MAJOR == 3 @@ -86,6 +103,7 @@ module Schema create_table :people, :force => true do |t| t.integer :parent_id t.string :name + t.string :email t.integer :salary t.boolean :awesome, :default => false t.timestamps