Catch up with changes in master branch

This commit is contained in:
jonatack 2013-12-07 01:51:55 +01:00
parent 37dbebc579
commit 0a2e4caaa9
17 changed files with 266 additions and 58 deletions

View File

@ -9,3 +9,7 @@ env:
- RAILS=4-0-stable DB=postgres
before_script:
- mysql -e 'create database ransack collate utf8_general_ci;'
- mysql -e 'use ransack;show variables like "%character%";show variables like "%collation%";'
- psql -c 'create database ransack;' -U postgres

View File

@ -8,13 +8,17 @@ When filing an issue on the Ransack project, please provide these details:
* The version of Ransack *and* the version of Rails.
* Any relevant stack traces ("Full trace" preferred)
In 99% of cases, this information is enough to determine the cause and solution to the problem that is being described.
In 99% of cases, this information is enough to determine the cause and
solution to the problem that is being described.
Any issue that is open for 14 days without actionable information or activity will be marked as "stalled" and then closed. Stalled issues can be re-opened if the information requested is provided.
Any issue that is open for 14 days without actionable information or activity
will be marked as "stalled" and then closed. Stalled issues can be re-opened
if the information requested is provided.
## Pull requests
We gladly accept pull requests to fix bugs and, in some circumstances, add new features to Ransack.
We gladly accept pull requests to fix bugs and, in some circumstances, add new
features to Ransack.
Here's a quick guide:
@ -24,8 +28,7 @@ Here's a quick guide:
to know that you have a clean slate:
$ bundle install
$ bundle exec rake test_app
$ bundle exec rake
$ bundle exec rake spec
3. Add a test for your change. Only refactoring and documentation changes
require no new tests. If you are adding functionality or fixing a bug, we need
@ -33,7 +36,9 @@ a test!
4. Make the test pass.
5. Push to your fork and submit a pull request. If the changes will apply cleanly to the latest stable branches and master branch, you will only need to submit one pull request.
5. Push to your fork and submit a pull request. If the changes will apply
cleanly to the latest stable branches and master branch, you will only need
to submit one pull request.
At this point you're waiting on us. We like to at least comment on, if not
accept, pull requests within three business days (and, typically, one business

View File

@ -1,4 +1,4 @@
source "https://rubygems.org"
source 'https://rubygems.org'
gemspec
gem 'rake'
@ -27,4 +27,7 @@ else
gem 'activerecord'
gem 'actionpack'
end
if rails == '3-0-stable'
gem 'mysql2', '< 0.3'
end
end

View File

@ -1,6 +1,6 @@
# Ransack
[![Build Status](https://travis-ci.org/activerecord-hackery/ransack)](https://travis-ci.org/ernie/ransack)
[![Build Status](https://travis-ci.org/activerecord-hackery/ransack.png)](https://travis-ci.org/activerecord-hackery/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
@ -23,13 +23,13 @@ gem "ransack" # Last officially released gem (Rails 3 and 4)
Or if you want to use the bleeding edge (Rails 3 and 4):
```ruby
gem "ransack", github: "ernie/ransack" # Track git repo
gem "ransack", github: "activerecord-hackery/ransack" # Track git repo
```
A dedicated, slimmer version of Ransack for Rails 4 only is here on the "rails-4" branch:
A lighter, faster, latest-commits version of Ransack dedicated to Rails 4 is available on the "rails-4" branch:
```ruby
gem "ransack", github: "ernie/ransack", branch: "rails-4"
gem "ransack", github: "activerecord-hackery/ransack", branch: "rails-4"
```
## Usage

View File

@ -25,6 +25,11 @@ module Ransack
column_names + _ransackers.keys
end
def ransortable_attributes(auth_object = nil)
# Here so users can overwrite the attributes that show up in the sort_select
ransackable_attributes(auth_object)
end
def ransackable_associations(auth_object = nil)
reflect_on_all_associations.map { |a| a.name.to_s }
end

View File

@ -87,7 +87,14 @@ module Ransack
module_function
# replace % \ to \% \\
def escape_wildcards(unescaped)
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
end
end

View File

@ -120,7 +120,8 @@ module Ransack
end
def ransackable_attribute?(str, klass)
klass.ransackable_attributes(auth_object).include? str
klass.ransackable_attributes(auth_object).include?(str) ||
klass.ransortable_attributes(auth_object).include?(str)
end
def ransackable_association?(str, klass)

View File

@ -19,7 +19,9 @@ module Ransack
end
def valid?
bound? && attr
bound? && attr &&
context.klassify(parent).ransackable_attributes(context.auth_object)
.include?(attr_name)
end
def type

View File

@ -24,7 +24,9 @@ module Ransack
end
def valid?
bound? && attr
bound? && attr &&
context.klassify(parent).ransortable_attributes(context.auth_object)
.include?(attr_name)
end
def name=(name)

View File

@ -25,7 +25,7 @@ module Ransack
end
def detect_from_string(str)
names_by_decreasing_length.detect {|p| str.end_with?("_#{p}")}
names_by_decreasing_length.detect { |p| str.end_with?("_#{p}") }
end
def name_from_attribute_name(attribute_name)

View File

@ -2,4 +2,7 @@ Person.blueprint do
name
email { "test@example.com" }
salary
only_sort
only_search
only_admin
end

View File

@ -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

View File

@ -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,20 @@ 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 remove empty key value pairs from the params hash" do
s = Person.search(children_reversed_name_eq: '')
@ -63,14 +74,138 @@ module Ransack
s = Person.search(nil)
end
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" } }
)
s.result.to_sql.should match(
/ORDER BY #{quote_table_name("people")}.#{quote_column_name("only_sort")} ASC/
)
end
it "doesn't sort by 'only_search' field" do
s = Person.search(
"s" => { "0" => { "dir" => "asc", "name" => "only_search" } }
)
s.result.to_sql.should_not match(
/ORDER BY #{quote_table_name("people")}.#{quote_column_name("only_search")} ASC/
)
end
it 'allows search by "only_search" field' do
s = Person.search(only_search_eq: 'htimS cirA')
s.result.to_sql.should match(
/WHERE #{quote_table_name("people")}.#{quote_column_name("only_search")} = 'htimS cirA'/
)
end
it "can't be searched by 'only_sort'" do
s = Person.search(only_sort_eq: 'htimS cirA')
s.result.to_sql.should_not match(
/WHERE #{quote_table_name("people")}.#{quote_column_name("only_sort")} = 'htimS cirA'/
)
end
it 'allows sort by "only_admin" field, if auth_object: :admin' do
s = Person.search(
{ "s" => { "0" => { "dir" => "asc", "name" => "only_admin" } } },
{ auth_object: :admin }
)
s.result.to_sql.should match(
/ORDER BY #{quote_table_name("people")}.#{quote_column_name("only_admin")} ASC/
)
end
it "doesn't sort by 'only_admin' field, if auth_object: nil" do
s = Person.search(
"s" => { "0" => { "dir" => "asc", "name" => "only_admin" } }
)
s.result.to_sql.should_not match(
/ORDER BY #{quote_table_name("people")}.#{quote_column_name("only_admin")} ASC/
)
end
it 'allows search by "only_admin" field, if auth_object: :admin' do
s = Person.search(
{ only_admin_eq: 'htimS cirA' },
{ auth_object: :admin }
)
s.result.to_sql.should match(
/WHERE #{quote_table_name("people")}.#{quote_column_name("only_admin")} = 'htimS cirA'/
)
end
it "can't be searched by 'only_admin', if auth_object: nil" do
s = Person.search(only_admin_eq: 'htimS cirA')
s.result.to_sql.should_not match(
/WHERE #{quote_table_name("people")}.#{quote_column_name("only_admin")} = 'htimS cirA'/
)
end
end
describe '#ransackable_attributes' do
subject { Person.ransackable_attributes }
context 'when auth_object is nil' do
subject { Person.ransackable_attributes }
it { should include 'name' }
it { should include 'reversed_name' }
it { should include 'doubled_name' }
it { should include 'name' }
it { should include 'reversed_name' }
it { should include 'doubled_name' }
it { should include 'only_search' }
it { should_not include 'only_sort' }
it { should_not include 'only_admin' }
end
context 'with auth_object :admin' do
subject { Person.ransackable_attributes(:admin) }
it { should include 'name' }
it { should include 'reversed_name' }
it { should include 'doubled_name' }
it { should include 'only_search' }
it { should_not include 'only_sort' }
it { should include 'only_admin' }
end
end
describe '#ransortable_attributes' do
context 'when auth_object is nil' do
subject { Person.ransortable_attributes }
it { should include 'name' }
it { should include 'reversed_name' }
it { should include 'doubled_name' }
it { should include 'only_sort' }
it { should_not include 'only_search' }
it { should_not include 'only_admin' }
end
context 'with auth_object :admin' do
subject { Person.ransortable_attributes(:admin) }
it { should include 'name' }
it { should include 'reversed_name' }
it { should include 'doubled_name' }
it { should include 'only_sort' }
it { should_not include 'only_search' }
it { should include 'only_admin' }
end
end
describe '#ransackable_associations' do

View File

@ -12,7 +12,7 @@ module Ransack
subject.parent_id_cont = 1
expect { subject.result }.to_not raise_error
end
it "escapes % and \\ in value" do
it "escapes '%', '.' and '\\\\' in value" do
subject.send(:"#{method}=", '%._\\')
subject.result.to_sql.should match(regexp)
end
@ -36,7 +36,15 @@ module Ransack
end
describe 'cont' do
it_has_behavior 'wildcard escaping', :name_cont, /"people"."name" I?LIKE '%\\%._\\\\%'/ do
it_has_behavior 'wildcard escaping', :name_cont,
(
if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
/"people"."name" ILIKE '%\\%\\._\\\\%'/
else
/"people"."name" LIKE '%%._\\%'/
end
) do
subject { @s }
end
@ -47,7 +55,14 @@ module Ransack
end
describe 'not_cont' do
it_has_behavior 'wildcard escaping', :name_not_cont, /"people"."name" NOT I?LIKE '%\\%._\\\\%'/ do
it_has_behavior 'wildcard escaping', :name_not_cont,
(
if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
/"people"."name" NOT ILIKE '%\\%\\._\\\\%'/
else
/"people"."name" NOT LIKE '%%._\\%'/
end
) do
subject { @s }
end

View File

@ -135,11 +135,9 @@ module Ransack
it 'evaluates nested conditions' do
search = Search.new(Person, children_name_eq: 'Ernie',
g: [{
m: 'or',
name_eq: 'Ernie',
children_children_name_eq: 'Ernie'
}]
g: [
{ m: 'or', name_eq: 'Ernie', children_children_name_eq: 'Ernie' }
]
)
search.result.should be_an ActiveRecord::Relation
where = search.result.where_values.first
@ -245,27 +243,21 @@ module Ransack
it 'creates sorts based on multiple attributes/directions in hash format' do
@s.sorts = {
'0' => {
name: 'id',
dir: 'desc'
},
'1' => {
name: 'name',
dir: 'asc'
}
'0' => { name: 'id', dir: 'desc' },
'1' => { name: 'name', dir: 'asc' }
}
@s.sorts.should have(2).items
@s.sorts.should be_all {|s| Nodes::Sort === s}
id_sort = @s.sorts.detect {|s| s.name == 'id'}
name_sort = @s.sorts.detect {|s| s.name == 'name'}
@s.sorts.should be_all { |s| Nodes::Sort === s }
id_sort = @s.sorts.detect { |s| s.name == 'id' }
name_sort = @s.sorts.detect { |s| s.name == 'name' }
id_sort.dir.should eq 'desc'
name_sort.dir.should eq 'asc'
end
it 'creates sorts based on multiple attributes and uppercase directions in hash format' do
@s.sorts = {
'0' => { :name => 'id', :dir => 'DESC' },
'1' => { :name => 'name', :dir => 'ASC' }
'0' => { name: 'id', dir: 'DESC' },
'1' => { name: 'name', dir: 'ASC' }
}
@s.sorts.should have(2).items
@s.sorts.should be_all { |s| Nodes::Sort === s }
@ -277,8 +269,8 @@ module Ransack
it 'creates sorts based on multiple attributes and different directions in hash format' do
@s.sorts = {
'0' => { :name => 'id', :dir => 'DESC' },
'1' => { :name => 'name', :dir => nil }
'0' => { name: 'id', dir: 'DESC' },
'1' => { name: 'name', dir: nil }
}
@s.sorts.should have(2).items
@s.sorts.should be_all { |s| Nodes::Sort === s }

View File

@ -6,18 +6,22 @@ require 'ransack'
Time.zone = 'Eastern Time (US & Canada)'
I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')]
Dir[File.expand_path('../{helpers,support,blueprints}/*.rb', __FILE__)].each do |f|
Dir[File.expand_path('../{helpers,support,blueprints}/*.rb', __FILE__)]
.each do |f|
require f
end
Sham.define do
name { Faker::Name.name }
title { Faker::Lorem.sentence }
body { Faker::Lorem.paragraph }
salary {|index| 30000 + (index * 1000)}
tag_name { Faker::Lorem.words(3).join(' ') }
note { Faker::Lorem.words(7).join(' ') }
notable_id { |id| id }
name { Faker::Name.name }
title { Faker::Lorem.sentence }
body { Faker::Lorem.paragraph }
salary { |index| 30000 + (index * 1000) }
tag_name { Faker::Lorem.words(3).join(' ') }
note { Faker::Lorem.words(7).join(' ') }
only_admin { Faker::Lorem.words(3).join(' ') }
only_search { Faker::Lorem.words(3).join(' ') }
only_sort { Faker::Lorem.words(3).join(' ') }
notable_id { |id| id }
end
RSpec.configure do |config|
@ -26,7 +30,8 @@ RSpec.configure do |config|
config.before(:suite) do
puts '=' * 80
connection_name = ActiveRecord::Base.connection.adapter_name
puts "Running specs against #{connection_name}, ActiveRecord #{ActiveRecord::VERSION::STRING} and ARel #{Arel::VERSION}..."
puts "Running specs against #{connection_name}, ActiveRecord #{
ActiveRecord::VERSION::STRING} and ARel #{Arel::VERSION}..."
puts '=' * 80
Schema.create
end

View File

@ -39,7 +39,25 @@ class Person < ActiveRecord::Base
end
ransacker :doubled_name do |parent|
Arel::Nodes::InfixOperation.new('||', parent.table[:name], parent.table[:name])
Arel::Nodes::InfixOperation.new(
'||', parent.table[:name], parent.table[:name]
)
end
def self.ransackable_attributes(auth_object = nil)
if auth_object == :admin
column_names + _ransackers.keys - ['only_sort']
else
column_names + _ransackers.keys - ['only_sort', 'only_admin']
end
end
def self.ransortable_attributes(auth_object = nil)
if auth_object == :admin
column_names + _ransackers.keys - ['only_search']
else
column_names + _ransackers.keys - ['only_search', 'only_admin']
end
end
end
@ -72,6 +90,9 @@ module Schema
t.integer :parent_id
t.string :name
t.string :email
t.string :only_search
t.string :only_sort
t.string :only_admin
t.integer :salary
t.boolean :awesome, default: false
t.timestamps
@ -120,7 +141,8 @@ module Schema
end
end
Comment.make(body: 'First post!',
article: Article.make(title: 'Hello, world!'))
Comment.make(
body: 'First post!', article: Article.make(title: 'Hello, world!')
)
end
end