Merge branch 'master' into bring-rails-3

Conflicts:
	.travis.yml
	Gemfile
	README.md
	lib/ransack/adapters/active_record/3.0/context.rb
	lib/ransack/adapters/active_record/3.1/context.rb
	lib/ransack/adapters/active_record/3.2/context.rb
	lib/ransack/adapters/active_record/base.rb
	lib/ransack/adapters/active_record/context.rb
	lib/ransack/constants.rb
	lib/ransack/context.rb
	lib/ransack/helpers/form_builder.rb
	lib/ransack/helpers/form_helper.rb
	lib/ransack/nodes/attribute.rb
	lib/ransack/nodes/condition.rb
	lib/ransack/nodes/grouping.rb
	lib/ransack/nodes/sort.rb
	lib/ransack/predicate.rb
	lib/ransack/translate.rb
	ransack.gemspec
	spec/blueprints/people.rb
	spec/ransack/adapters/active_record/base_spec.rb
	spec/ransack/adapters/active_record/context_spec.rb
	spec/ransack/configuration_spec.rb
	spec/ransack/dependencies_spec.rb
	spec/ransack/helpers/form_builder_spec.rb
	spec/ransack/helpers/form_helper_spec.rb
	spec/ransack/predicate_spec.rb
	spec/ransack/search_spec.rb
	spec/ransack/translate_spec.rb
	spec/support/schema.rb
This commit is contained in:
Washington Luiz 2014-04-10 00:28:29 -03:00
commit b4973315e9
35 changed files with 308 additions and 164 deletions

View File

@ -9,12 +9,21 @@ rvm:
- 2.1.1 - 2.1.1
env: env:
- RAILS=4-0-stable DB=sqlite3
- RAILS=4-0-stable DB=mysql
- RAILS=4-0-stable DB=postgres
- RAILS=4-1-stable DB=sqlite3 - RAILS=4-1-stable DB=sqlite3
- RAILS=4-1-stable DB=mysql - RAILS=4-1-stable DB=mysql
- RAILS=4-1-stable DB=postgres - RAILS=4-1-stable DB=postgres
- RAILS=4-0-stable DB=sqlite3
- 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
before_script: before_script:
- mysql -e 'create database ransack collate utf8_general_ci;' - mysql -e 'create database ransack collate utf8_general_ci;'

View File

@ -27,4 +27,7 @@ else
gem 'activerecord' gem 'activerecord'
gem 'actionpack' gem 'actionpack'
end end
if rails == '3-0-stable'
gem 'mysql2', '< 0.3'
end
end end

View File

@ -133,6 +133,15 @@ Once you've done so, you can make use of the helpers in Ransack::Helpers::FormBu
construct much more complex search forms, such as the one on the construct much more complex search forms, such as the one on the
[demo page](http://ransack-demo.heroku.com) (source code [here](https://github.com/activerecord-hackery/ransack_demo)). [demo page](http://ransack-demo.heroku.com) (source code [here](https://github.com/activerecord-hackery/ransack_demo)).
### Ransack #search method
Ransack will try to to make `#search` available in your models, but in the case that `#search` has already been defined, you can use `#ransack` instead. For example the following would be equivalent:
```
Article.search(params[:q])
Article.ransack(params[:q])
```
### has_many and belongs_to associations ### has_many and belongs_to associations
You can easily use Ransack to search in associated objects. You can easily use Ransack to search in associated objects.

View File

@ -25,4 +25,4 @@ require 'ransack/adapters/active_record' if defined?(::ActiveRecord::Base)
require 'ransack/helpers' require 'ransack/helpers'
require 'action_controller' require 'action_controller'
ActionController::Base.helper Ransack::Helpers::FormHelper ActionController::Base.helper Ransack::Helpers::FormHelper

View File

@ -2,7 +2,7 @@
if Arel::Nodes::And < Arel::Nodes::Binary if Arel::Nodes::And < Arel::Nodes::Binary
class Ransack::Visitor class Ransack::Visitor
def visit_Ransack_Nodes_And(object) def visit_Ransack_Nodes_And(object)
nodes = object.values.map {|o| accept(o)}.compact nodes = object.values.map { |o| accept(o) }.compact
return nil unless nodes.size > 0 return nil unless nodes.size > 0
if nodes.size > 1 if nodes.size > 1
@ -132,9 +132,15 @@ module Arel
end end
def visit_Arel_Nodes_NamedFunction o def visit_Arel_Nodes_NamedFunction o
"#{o.name}(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x| "#{
visit x o.name
}.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}" }(#{
o.distinct ? 'DISTINCT ' : ''
}#{
o.expressions.map { |x| visit x }.join(', ')
})#{
o.alias ? " AS #{visit o.alias}" : ''
}"
end end
def visit_Arel_Nodes_And o def visit_Arel_Nodes_And o
@ -146,13 +152,17 @@ module Arel
end end
def visit_Arel_Nodes_Values o def visit_Arel_Nodes_Values o
"VALUES (#{o.expressions.zip(o.columns).map { |value, attr| "VALUES (#{
o.expressions.zip(o.columns)
.map { |value, attr|
if Nodes::SqlLiteral === value if Nodes::SqlLiteral === value
visit_Arel_Nodes_SqlLiteral value visit_Arel_Nodes_SqlLiteral value
else else
quote(value, attr && column_for(attr)) quote(value, attr && column_for(attr))
end end
}.join ', '})" }
.join ', '
})"
end end
end end
end end

View File

@ -11,6 +11,8 @@ module Ransack
JoinDependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency JoinDependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency
JoinBase = JoinDependency::JoinBase JoinBase = JoinDependency::JoinBase
# Redefine a few things for ActiveRecord 3.0.
def initialize(object, options = {}) def initialize(object, options = {})
super super
@arel_visitor = Arel::Visitors.visitor_for @engine @arel_visitor = Arel::Visitors.visitor_for @engine
@ -24,10 +26,11 @@ module Ransack
viz = Visitor.new viz = Visitor.new
relation = @object.where(viz.accept(search.base)) relation = @object.where(viz.accept(search.base))
if search.sorts.any? if search.sorts.any?
relation = relation.except(:order).reorder(viz.accept(search.sorts)) relation = relation.except(:order)
.reorder(viz.accept(search.sorts))
end end
opts[:distinct] ? relation.select( opts[:distinct] ?
"DISTINCT #{@klass.quoted_table_name}.*") : relation relation.select("DISTINCT #{@klass.quoted_table_name}.*") : relation
end end
def attribute_method?(str, klass = @klass) def attribute_method?(str, klass = @klass)
@ -38,10 +41,15 @@ module Ransack
elsif (segments = str.split(/_/)).size > 1 elsif (segments = str.split(/_/)).size > 1
remainder = [] remainder = []
found_assoc = nil found_assoc = nil
while !found_assoc && remainder.unshift(segments.pop) && segments.size > 0 do while !found_assoc && remainder.unshift(segments.pop) &&
assoc, poly_class = unpolymorphize_association(segments.join('_')) segments.size > 0 do
assoc, poly_class = unpolymorphize_association(
segments.join('_')
)
if found_assoc = get_association(assoc, klass) if found_assoc = get_association(assoc, klass)
exists = attribute_method?(remainder.join('_'), poly_class || found_assoc.klass) exists = attribute_method?(
remainder.join('_'), poly_class || found_assoc.klass
)
end end
end end
end end
@ -52,10 +60,12 @@ module Ransack
def table_for(parent) def table_for(parent)
parent.table parent.table
end end
def type_for(attr) def type_for(attr)
return nil unless attr && attr.valid? return nil unless attr && attr.valid?
klassify(attr.parent).columns_hash[attr.arel_attribute.name.to_s].type klassify(attr.parent)
.columns_hash[attr.arel_attribute.name.to_s]
.type
end end
private private
@ -68,22 +78,26 @@ module Ransack
elsif (segments = str.split(/_/)).size > 1 elsif (segments = str.split(/_/)).size > 1
remainder = [] remainder = []
found_assoc = nil found_assoc = nil
while remainder.unshift(segments.pop) && segments.size > 0 && !found_assoc do while remainder.unshift(segments.pop) && segments.size > 0 &&
!found_assoc do
assoc, klass = unpolymorphize_association(segments.join('_')) assoc, klass = unpolymorphize_association(segments.join('_'))
if found_assoc = get_association(assoc, parent) if found_assoc = get_association(assoc, parent)
join = build_or_find_association(found_assoc.name, parent, klass) join = build_or_find_association(
parent, attr_name = get_parent_and_attribute_name(remainder.join('_'), join) found_assoc.name, parent, klass
)
parent, attr_name = get_parent_and_attribute_name(
remainder.join('_'), join
)
end end
end end
end end
[parent, attr_name] [parent, attr_name]
end end
def get_association(str, parent = @base) def get_association(str, parent = @base)
klass = klassify parent klass = klassify parent
ransackable_association?(str, klass) && ransackable_association?(str, klass) &&
klass.reflect_on_all_associations.detect {|a| a.name.to_s == str} klass.reflect_on_all_associations.detect { |a| a.name.to_s == str }
end end
def join_dependency(relation) def join_dependency(relation)
@ -113,9 +127,9 @@ module Ransack
association_joins = buckets['association_join'] || [] association_joins = buckets['association_join'] || []
stashed_association_joins = buckets['stashed_join'] || [] stashed_association_joins = buckets['stashed_join'] || []
join_nodes = buckets['join_node'] || [] join_nodes = buckets['join_node'] || []
string_joins = (buckets['string_join'] || []).map { |x| string_joins = (buckets['string_join'] || [])
x.strip .map { |x| x.strip }
}.uniq .uniq
join_list = relation.send :custom_join_sql, (string_joins + join_nodes) join_list = relation.send :custom_join_sql, (string_joins + join_nodes)
@ -133,13 +147,16 @@ module Ransack
end end
def build_or_find_association(name, parent = @base, klass = nil) def build_or_find_association(name, parent = @base, klass = nil)
found_association = @join_dependency.join_associations.detect do |assoc| found_association = @join_dependency.join_associations
.detect do |assoc|
assoc.reflection.name == name && assoc.reflection.name == name &&
assoc.parent == parent && assoc.parent == parent &&
(!klass || assoc.reflection.klass == klass) (!klass || assoc.reflection.klass == klass)
end end
unless found_association unless found_association
@join_dependency.send(:build, Polyamorous::Join.new(name, @join_type, klass), parent) @join_dependency.send(
:build, Polyamorous::Join.new(name, @join_type, klass), parent
)
found_association = @join_dependency.join_associations.last found_association = @join_dependency.join_associations.last
# Leverage the stashed association functionality in AR # Leverage the stashed association functionality in AR
@object = @object.joins(found_association) @object = @object.joins(found_association)

View File

@ -10,6 +10,8 @@ module Ransack
JoinDependency = ::ActiveRecord::Associations::JoinDependency JoinDependency = ::ActiveRecord::Associations::JoinDependency
JoinPart = JoinDependency::JoinPart JoinPart = JoinDependency::JoinPart
# Redefine a few things for ActiveRecord 3.1.
def initialize(object, options = {}) def initialize(object, options = {})
super super
@arel_visitor = Arel::Visitors.visitor_for @engine @arel_visitor = Arel::Visitors.visitor_for @engine
@ -23,10 +25,12 @@ module Ransack
viz = Visitor.new viz = Visitor.new
relation = @object.where(viz.accept(search.base)) relation = @object.where(viz.accept(search.base))
if search.sorts.any? if search.sorts.any?
relation = relation.except(:order).reorder(viz.accept(search.sorts)) relation = relation.except(:order)
.reorder(viz.accept(search.sorts))
end end
opts[:distinct] ? relation.select( opts[:distinct] ?
"DISTINCT #{@klass.quoted_table_name}.*") : relation relation.select("DISTINCT #{@klass.quoted_table_name}.*") :
relation
end end
def attribute_method?(str, klass = @klass) def attribute_method?(str, klass = @klass)
@ -37,10 +41,15 @@ module Ransack
elsif (segments = str.split(/_/)).size > 1 elsif (segments = str.split(/_/)).size > 1
remainder = [] remainder = []
found_assoc = nil found_assoc = nil
while !found_assoc && remainder.unshift(segments.pop) && segments.size > 0 do while !found_assoc && remainder.unshift(segments.pop) &&
assoc, poly_class = unpolymorphize_association(segments.join('_')) segments.size > 0 do
assoc, poly_class = unpolymorphize_association(
segments.join('_')
)
if found_assoc = get_association(assoc, klass) if found_assoc = get_association(assoc, klass)
exists = attribute_method?(remainder.join('_'), poly_class || found_assoc.klass) exists = attribute_method?(
remainder.join('_'), poly_class || found_assoc.klass
)
end end
end end
end end
@ -74,11 +83,16 @@ module Ransack
elsif (segments = str.split(/_/)).size > 1 elsif (segments = str.split(/_/)).size > 1
remainder = [] remainder = []
found_assoc = nil found_assoc = nil
while remainder.unshift(segments.pop) && segments.size > 0 && !found_assoc do while remainder.unshift(segments.pop) && segments.size > 0 &&
!found_assoc do
assoc, klass = unpolymorphize_association(segments.join('_')) assoc, klass = unpolymorphize_association(segments.join('_'))
if found_assoc = get_association(assoc, parent) if found_assoc = get_association(assoc, parent)
join = build_or_find_association(found_assoc.name, parent, klass) join = build_or_find_association(
parent, attr_name = get_parent_and_attribute_name(remainder.join('_'), join) found_assoc.name, parent, klass
)
parent, attr_name = get_parent_and_attribute_name(
remainder.join('_'), join
)
end end
end end
end end
@ -89,7 +103,7 @@ module Ransack
def get_association(str, parent = @base) def get_association(str, parent = @base)
klass = klassify parent klass = klassify parent
ransackable_association?(str, klass) && ransackable_association?(str, klass) &&
klass.reflect_on_all_associations.detect {|a| a.name.to_s == str} klass.reflect_on_all_associations.detect { |a| a.name.to_s == str }
end end
def join_dependency(relation) def join_dependency(relation)
@ -119,11 +133,12 @@ module Ransack
association_joins = buckets['association_join'] || [] association_joins = buckets['association_join'] || []
stashed_association_joins = buckets['stashed_join'] || [] stashed_association_joins = buckets['stashed_join'] || []
join_nodes = buckets['join_node'] || [] join_nodes = buckets['join_node'] || []
string_joins = (buckets['string_join'] || []).map { |x| string_joins = (buckets['string_join'] || [])
x.strip .map { |x| x.strip }
}.uniq .uniq
join_list = relation.send :custom_join_ast, relation.table.from(relation.table), string_joins join_list = relation.send :custom_join_ast,
relation.table.from(relation.table), string_joins
join_dependency = JoinDependency.new( join_dependency = JoinDependency.new(
relation.klass, relation.klass,
@ -139,13 +154,16 @@ module Ransack
end end
def build_or_find_association(name, parent = @base, klass = nil) def build_or_find_association(name, parent = @base, klass = nil)
found_association = @join_dependency.join_associations.detect do |assoc| found_association = @join_dependency.join_associations
.detect do |assoc|
assoc.reflection.name == name && assoc.reflection.name == name &&
assoc.parent == parent && assoc.parent == parent &&
(!klass || assoc.reflection.klass == klass) (!klass || assoc.reflection.klass == klass)
end end
unless found_association unless found_association
@join_dependency.send(:build, Polyamorous::Join.new(name, @join_type, klass), parent) @join_dependency.send(
:build, Polyamorous::Join.new(name, @join_type, klass), parent
)
found_association = @join_dependency.join_associations.last found_association = @join_dependency.join_associations.last
# Leverage the stashed association functionality in AR # Leverage the stashed association functionality in AR
@object = @object.joins(found_association) @object = @object.joins(found_association)

View File

@ -8,7 +8,7 @@ module Ransack
module ActiveRecord module ActiveRecord
class Context < ::Ransack::Context class Context < ::Ransack::Context
# Redefine a few things that have changed with 3.2. # Redefine a few things for ActiveRecord 3.2.
def initialize(object, options = {}) def initialize(object, options = {})
super super

View File

@ -17,8 +17,8 @@ module Ransack
end end
def ransacker(name, opts = {}, &block) def ransacker(name, opts = {}, &block)
self._ransackers = _ransackers.merge name.to_s => Ransacker.new( self._ransackers = _ransackers.merge name.to_s => Ransacker
self, name, opts, &block) .new(self, name, opts, &block)
end end
def ransackable_attributes(auth_object = nil) def ransackable_attributes(auth_object = nil)
@ -26,7 +26,8 @@ module Ransack
end end
def ransortable_attributes(auth_object = nil) def ransortable_attributes(auth_object = nil)
# Here so users can overwrite the attributes that show up in the sort_select # Here so users can overwrite the attributes
# that show up in the sort_select
ransackable_attributes(auth_object) ransackable_attributes(auth_object)
end end

View File

@ -95,6 +95,5 @@ module Ransack
unescaped.to_s.gsub(/([\\|\%|.])/, '\\\\\\1') unescaped.to_s.gsub(/([\\|\%|.])/, '\\\\\\1')
end end
end end
end end
end end

View File

@ -61,7 +61,9 @@ module Ransack
obj obj
elsif obj.respond_to? :klass elsif obj.respond_to? :klass
obj.klass obj.klass
elsif obj.respond_to? :base_klass elsif obj.respond_to? :active_record # Rails 3
obj.active_record
elsif obj.respond_to? :base_klass # Rails 4
obj.base_klass obj.base_klass
else else
raise ArgumentError, "Don't know how to klassify #{obj.inspect}" raise ArgumentError, "Don't know how to klassify #{obj.inspect}"
@ -148,5 +150,9 @@ module Ransack
def sortable_attributes(str = '') def sortable_attributes(str = '')
traverse(str).ransortable_attributes(auth_object) traverse(str).ransortable_attributes(auth_object)
end end
def searchable_associations(str = '')
traverse(str).ransackable_associations(auth_object)
end
end end
end end

View File

@ -1,6 +1,4 @@
require 'action_view' require 'action_view'
require 'simple_form' if
(ENV['RANSACK_FORM_BUILDER'] || '').match('SimpleForm')
require 'simple_form' if require 'simple_form' if
(ENV['RANSACK_FORM_BUILDER'] || '').match('SimpleForm') (ENV['RANSACK_FORM_BUILDER'] || '').match('SimpleForm')
@ -109,7 +107,12 @@ module Ransack
def predicate_select(options = {}, html_options = {}) def predicate_select(options = {}, html_options = {})
options[:compounds] = true if options[:compounds].nil? options[:compounds] = true if options[:compounds].nil?
default = options.delete(:default) || 'cont' if ::ActiveRecord::VERSION::STRING >= "4"
default = options.delete(:default) || 'cont'
else
default = options.delete(:default) || 'eq'
end
keys = options[:compounds] ? Predicate.names : keys = options[:compounds] ? Predicate.names :
Predicate.names.reject { |k| k.match(/_(any|all)$/) } Predicate.names.reject { |k| k.match(/_(any|all)$/) }
if only = options[:only] if only = options[:only]

View File

@ -23,7 +23,8 @@ module Ransack
end end
class Name < String class Name < String
attr_reader :singular, :plural, :element, :collection, :partial_path, :human, :param_key, :route_key, :i18n_key attr_reader :singular, :plural, :element, :collection, :partial_path,
:human, :param_key, :route_key, :i18n_key
alias_method :cache_key, :collection alias_method :cache_key, :collection
def initialize def initialize

View File

@ -52,4 +52,4 @@ module Ransack
end end
end end
end end

View File

@ -19,7 +19,8 @@ module Ransack
m: combinator, m: combinator,
v: predicate.wants_array ? Array(values) : [values] v: predicate.wants_array ? Array(values) : [values]
) )
# TODO: Figure out what to do with multiple types of attributes, if anything. # TODO: Figure out what to do with multiple types of attributes,
# if anything.
# Tempted to go with "garbage in, garbage out" on this one # Tempted to go with "garbage in, garbage out" on this one
predicate.validate(condition.values, condition.default_type) ? predicate.validate(condition.values, condition.default_type) ?
condition : nil condition : nil
@ -208,14 +209,14 @@ module Ransack
def inspect def inspect
data = [ data = [
['attributes', a.try(:map, &:name)], ['attributes', a.try(:map, &:name)],
['predicate', p], ['predicate', p],
['combinator', m], ['combinator', m],
['values', v.try(:map, &:value)] ['values', v.try(:map, &:value)]
] ]
.reject { |e| e[1].blank? } .reject { |e| e[1].blank? }
.map { |v| "#{v[0]}: #{v[1]}" } .map { |v| "#{v[0]}: #{v[1]}" }
.join(', ') .join(', ')
"Condition <#{data}>" "Condition <#{data}>"
end end
@ -228,4 +229,4 @@ module Ransack
end end
end end
end end

View File

@ -129,10 +129,9 @@ module Ransack
when /^(g|c|m|groupings|conditions|combinator)=?$/ when /^(g|c|m|groupings|conditions|combinator)=?$/
true true
else else
name. name.split(/_and_|_or_/)
split(/_and_|_or_/). .select { |n| !@context.attribute_method?(n) }
select { |n| !@context.attribute_method?(n) }. .empty?
empty?
end end
end end
@ -191,7 +190,6 @@ module Ransack
Predicate.detect_and_strip_from_string!(string) Predicate.detect_and_strip_from_string!(string)
string string
end end
end end
end end
end end

View File

@ -31,4 +31,4 @@ module Ransack
end end
end end
end end

View File

@ -41,4 +41,4 @@ module Ransack
end end
end end
end end

View File

@ -107,4 +107,4 @@ module Ransack
end end
end end
end end
end end

View File

@ -29,8 +29,9 @@ module Ransack
end end
def name_from_attribute_name(attribute_name) def name_from_attribute_name(attribute_name)
names_by_decreasing_length. names_by_decreasing_length.detect {
detect { |p| attribute_name.to_s.match(/_#{p}$/) } |p| attribute_name.to_s.match(/_#{p}$/)
}
end end
def for_attribute_name(attribute_name) def for_attribute_name(attribute_name)
@ -75,4 +76,4 @@ module Ransack
end end
end end
end end

View File

@ -21,4 +21,4 @@ module Ransack
end end
end end
end end

View File

@ -104,7 +104,11 @@ module Ransack
end end
def self.i18n_key(klass) def self.i18n_key(klass)
klass.model_name.i18n_key if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0
klass.model_name.i18n_key.to_s.tr('.', '/')
else
klass.model_name.i18n_key.to_s
end
end end
end end
end end

View File

@ -15,9 +15,9 @@ Gem::Specification.new do |s|
s.rubyforge_project = "ransack" s.rubyforge_project = "ransack"
s.add_dependency 'actionpack', '>= 4.0' s.add_dependency 'actionpack', '>= 3.0'
s.add_dependency 'activerecord', '>= 4.0' s.add_dependency 'activerecord', '>= 3.0'
s.add_dependency 'activesupport', '>= 4.0' s.add_dependency 'activesupport', '>= 3.0'
s.add_dependency 'i18n' s.add_dependency 'i18n'
# s.add_dependency 'polyamorous', '~> 0.6.0' # s.add_dependency 'polyamorous', '~> 0.6.0'
s.add_development_dependency 'rspec', '~> 2.8.0' s.add_development_dependency 'rspec', '~> 2.8.0'

View File

@ -5,4 +5,4 @@ Person.blueprint do
only_sort only_sort
only_search only_search
only_admin only_admin
end end

View File

@ -19,4 +19,3 @@ Sham.define do
end end
Schema.create Schema.create

View File

@ -37,6 +37,7 @@ module Ransack
# ransacker :doubled_name do |parent| # 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 # end
it 'creates ransack attributes' do it 'creates ransack attributes' do
s = Person.search(reversed_name_eq: 'htimS cirA') s = Person.search(reversed_name_eq: 'htimS cirA')
s.result.should have(1).person s.result.should have(1).person
@ -60,18 +61,20 @@ module Ransack
s.result.count.should eq 1 s.result.count.should eq 1
end if defined?(Arel::Nodes::InfixOperation) && sane_adapter? end if defined?(Arel::Nodes::InfixOperation) && sane_adapter?
it "should remove empty key value pairs from the params hash" do if ::ActiveRecord::VERSION::STRING >= "4"
s = Person.search(children_reversed_name_eq: '') it "should remove empty key value pairs from the params hash" do
s.result.to_sql.should_not match /LEFT OUTER JOIN/ s = Person.search(children_reversed_name_eq: '')
end s.result.to_sql.should_not match /LEFT OUTER JOIN/
end
it "should keep proper key value pairs in the params hash" do it "should keep proper key value pairs in the params hash" do
s = Person.search(children_reversed_name_eq: 'Testing') s = Person.search(children_reversed_name_eq: 'Testing')
s.result.to_sql.should match /LEFT OUTER JOIN/ s.result.to_sql.should match /LEFT OUTER JOIN/
end end
it "should function correctly when nil is passed in" do it "should function correctly when nil is passed in" do
s = Person.search(nil) s = Person.search(nil)
end
end end
it "should function correctly when using fields with dots in them" do it "should function correctly when using fields with dots in them" do
@ -219,4 +222,4 @@ module Ransack
end end
end end
end end
end end

View File

@ -48,4 +48,4 @@ module Ransack
end end
end end
end end
end end

View File

@ -65,4 +65,4 @@ module Ransack
Ransack.predicates.should_not have_key 'test_array_predicate_all' Ransack.predicates.should_not have_key 'test_array_predicate_all'
end end
end end
end end

View File

@ -1,6 +1,8 @@
#describe 'Ransack' do unless ::ActiveRecord::VERSION::STRING >= "4"
# it 'can be required without errors' do describe 'Ransack' do
# output = `bundle exec ruby -e "require 'ransack'" 2>&1` it 'can be required without errors' do
# output.should be_empty output = `bundle exec ruby -e "require 'ransack'" 2>&1`
# end output.should be_empty
#end end
end
end

View File

@ -55,7 +55,11 @@ module Ransack
describe '#sort_link' do describe '#sort_link' do
it 'sort_link for ransack attribute' do it 'sort_link for ransack attribute' do
sort_link = @f.sort_link :name, controller: 'people' sort_link = @f.sort_link :name, controller: 'people'
sort_link.should match /people\?q(%5B|\[)s(%5D|\])=name\+asc/ if ActiveRecord::VERSION::STRING =~ /^3\.[1-2]\./
sort_link.should match /people\?q%5Bs%5D=name\+asc/
else
sort_link.should match /people\?q(%5B|\[)s(%5D|\])=name\+asc/
end
sort_link.should match /sort_link/ sort_link.should match /sort_link/
sort_link.should match /Full Name<\/a>/ sort_link.should match /Full Name<\/a>/
end end

View File

@ -33,48 +33,63 @@ module Ransack
controller: 'people' controller: 'people'
) )
} }
it { should match /people\?q(%5B|\[)s(%5D|\])=name\+asc/ } it { should match(
if ActiveRecord::VERSION::STRING =~ /^3\.[1-2]\./
/people\?q%5Bs%5D=name\+asc/
else
/people\?q(%5B|\[)s(%5D|\])=name\+asc/
end)
}
it { should match /sort_link desc/ } it { should match /sort_link desc/ }
it { should match /Full Name &#9660;/ } it { should match /Full Name &#9660;/ }
end end
describe '#sort_link with default search_key defined as symbol' do describe '#sort_link with default search_key defined as symbol' do
subject { @controller.view_context. subject { @controller.
sort_link( view_context.sort_link(
Person.search({ sorts: ['name desc'] }, search_key: :people_search), Person.search({ :sorts => ['name desc'] }, :search_key => :people_search),
:name, :name, :controller => 'people'
controller: 'people'
) )
} }
it { should match /people\?people_search(%5B|\[)s(%5D|\])=name\+asc/ } it { should match(
if ActiveRecord::VERSION::STRING =~ /^3\.[1-2]\./
/people\?people_search%5Bs%5D=name\+asc/
else
/people\?people_search(%5B|\[)s(%5D|\])=name\+asc/
end)
}
end end
describe '#sort_link with default search_key defined as string' do describe '#sort_link with default search_key defined as string' do
subject { @controller.view_context. subject {
sort_link( @controller.view_context.sort_link(
Person.search({ sorts: ['name desc'] }, search_key: 'people_search'), Person.search({ :sorts => ['name desc'] }, :search_key => 'people_search'),
:name, :name, :controller => 'people'
controller: 'people'
) )
} }
it { should match /people\?people_search(%5B|\[)s(%5D|\])=name\+asc/ } it { should match(
if ActiveRecord::VERSION::STRING =~ /^3\.[1-2]\./
/people\?people_search%5Bs%5D=name\+asc/
else
/people\?people_search(%5B|\[)s(%5D|\])=name\+asc/
end)
}
end end
context 'view has existing parameters' do context 'view has existing parameters' do
before do before do
@controller.view_context. @controller.view_context.params.merge!({ :exist => 'existing' })
params.merge!({ exist: 'existing' })
end end
describe '#sort_link should not remove existing params' do describe '#sort_link should not remove existing params' do
subject { subject {
@controller.view_context. @controller.view_context.sort_link(
sort_link( Person.search({ :sorts => ['name desc'] }, :search_key => 'people_search'),
Person.search({ sorts: ['name desc'] }, search_key: 'people_search'), :name, :controller => 'people'
:name, )
controller: 'people' }
) it {
should match /exist\=existing/
} }
it { should match /exist\=existing/ }
end end
end end
end end

View File

@ -42,17 +42,15 @@ module Ransack
end end
describe 'cont' do describe 'cont' do
it_has_behavior 'wildcard escaping', :name_cont, it_has_behavior 'wildcard escaping', :name_cont,
( (if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
/"people"."name" ILIKE '%\\%\\._\\\\%'/ /"people"."name" ILIKE '%\\%\\._\\\\%'/
elsif ActiveRecord::Base.connection.adapter_name == "Mysql2" elsif ActiveRecord::Base.connection.adapter_name == "Mysql2"
/`people`.`name` LIKE '%\\\\%\\\\._\\\\\\\\%'/ /`people`.`name` LIKE '%\\\\%\\\\._\\\\\\\\%'/
else else
/"people"."name" LIKE '%%._\\%'/ /"people"."name" LIKE '%%._\\%'/
end end) do
) do
subject { @s } subject { @s }
end end
@ -65,15 +63,13 @@ module Ransack
describe 'not_cont' do describe 'not_cont' do
it_has_behavior 'wildcard escaping', :name_not_cont, it_has_behavior 'wildcard escaping', :name_not_cont,
( (if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
/"people"."name" NOT ILIKE '%\\%\\._\\\\%'/ /"people"."name" NOT ILIKE '%\\%\\._\\\\%'/
elsif ActiveRecord::Base.connection.adapter_name == "Mysql2" elsif ActiveRecord::Base.connection.adapter_name == "Mysql2"
/`people`.`name` NOT LIKE '%\\\\%\\\\._\\\\\\\\%'/ /`people`.`name` NOT LIKE '%\\\\%\\\\._\\\\\\\\%'/
else else
/"people"."name" NOT LIKE '%%._\\%'/ /"people"."name" NOT LIKE '%%._\\%'/
end end) do
) do
subject { @s } subject { @s }
end end

View File

@ -89,13 +89,11 @@ module Ransack
it 'accepts "attributes" hashes for conditions' do it 'accepts "attributes" hashes for conditions' do
search = Search.new(Person, search = Search.new(Person,
c: { :c => {
'0' => { a: ['name'], p: 'eq', v: ['Ernie'] }, '0' => { :a => ['name'], :p => 'eq', :v => ['Ernie'] },
'1' => { '1' => { :a => ['children_name', 'parent_name'],
a: ['children_name', 'parent_name'], :p => 'eq', :v => ['Ernie'], :m => 'or' }
p: 'eq', v: ['Ernie'], m: 'or' }
}
}
) )
conditions = search.base.conditions conditions = search.base.conditions
conditions.should have(2).items conditions.should have(2).items
@ -105,8 +103,7 @@ module Ransack
it 'creates conditions for custom predicates that take arrays' do it 'creates conditions for custom predicates that take arrays' do
Ransack.configure do |config| Ransack.configure do |config|
config.add_predicate 'ary_pred', config.add_predicate 'ary_pred', :wants_array => true
wants_array: true
end end
search = Search.new(Person, name_ary_pred: ['Ernie', 'Bert']) search = Search.new(Person, name_ary_pred: ['Ernie', 'Bert'])
@ -183,18 +180,42 @@ module Ransack
second.should match /#{children_people_name_field} = 'Bert'/ second.should match /#{children_people_name_field} = 'Bert'/
end end
it 'returns distinct records when passed distinct: true' do if ::ActiveRecord::VERSION::STRING >= "4"
search = Search.new( it 'returns distinct records when passed distinct: true' do
Person, g: [ search = Search.new(
{ m: 'or', Person, g: [
comments_body_cont: 'e', { m: 'or',
articles_comments_body_cont: 'e' comments_body_cont: 'e',
} articles_comments_body_cont: 'e'
] }
) ]
search.result.load.should have(9000).items )
search.result(distinct: true).should have(10).items search.result.load.should have(9000).items
search.result.load.uniq.should eq search.result(distinct: true).load search.result(distinct: true).should have(10).items
search.result.load.uniq.should eq search.result(distinct: true).load
end
else
it 'returns distinct records when passed :distinct => true' do
search = Search.new(
Person, :g => [
{ :m => 'or',
:comments_body_cont => 'e',
:articles_comments_body_cont => 'e'
}
]
)
if ActiveRecord::VERSION::MAJOR == 3
all_or_load, uniq_or_distinct = :all, :uniq
else
all_or_load, uniq_or_distinct = :load, :distinct
end
search.result.send(all_or_load).
should have(920).items
search.result(:distinct => true).
should have(330).items
search.result.send(all_or_load).send(uniq_or_distinct).
should eq search.result(:distinct => true).send(all_or_load)
end
end end
end end

View File

@ -6,9 +6,7 @@ module Ransack
describe '.attribute' do describe '.attribute' do
it 'translate namespaced attribute like AR does' do it 'translate namespaced attribute like AR does' do
ar_translation = ::Namespace::Article.human_attribute_name(:title) ar_translation = ::Namespace::Article.human_attribute_name(:title)
ransack_translation = Ransack::Translate.attribute( ransack_translation = Ransack::Translate.attribute(:title, :context => ::Namespace::Article.search.context)
:title, :context => ::Namespace::Article.search.context
)
ransack_translation.should eq ar_translation ransack_translation.should eq ar_translation
end end
end end

View File

@ -23,7 +23,11 @@ else
end end
class Person < ActiveRecord::Base class Person < ActiveRecord::Base
default_scope { order(id: :desc) } if ActiveRecord::VERSION::MAJOR == 3
default_scope order('id DESC')
else
default_scope { order(id: :desc) }
end
belongs_to :parent, class_name: 'Person', foreign_key: :parent_id belongs_to :parent, class_name: 'Person', foreign_key: :parent_id
has_many :children, class_name: 'Person', foreign_key: :parent_id has_many :children, class_name: 'Person', foreign_key: :parent_id
has_many :articles has_many :articles
@ -57,6 +61,22 @@ class Person < ActiveRecord::Base
column_names + _ransackers.keys - ['only_search', 'only_admin'] column_names + _ransackers.keys - ['only_search', 'only_admin']
end end
end 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 end
class Article < ActiveRecord::Base class Article < ActiveRecord::Base
@ -72,6 +92,12 @@ module Namespace
end end
end end
module Namespace
class Article < ::Article
end
end
class Comment < ActiveRecord::Base class Comment < ActiveRecord::Base
belongs_to :article belongs_to :article
belongs_to :person belongs_to :person
@ -128,6 +154,7 @@ module Schema
t.string :notable_type t.string :notable_type
t.string :note t.string :note
end end
end end
10.times do 10.times do
@ -148,6 +175,5 @@ module Schema
Comment.make( Comment.make(
body: 'First post!', article: Article.make(title: 'Hello, world!') body: 'First post!', article: Article.make(title: 'Hello, world!')
) )
end
end
end end