Add :only/:compound options to predicate_select, refactor ands/ors -> groupings

This commit is contained in:
Ernie Miller 2011-06-05 14:29:54 -04:00
parent cec4e3d64f
commit 1c41a3ea2c
11 changed files with 158 additions and 145 deletions

View File

@ -77,12 +77,8 @@ module Ransack
search_fields(:c, args, block)
end
def and_fields(*args, &block)
search_fields(:n, args, block)
end
def or_fields(*args, &block)
search_fields(:o, args, block)
def grouping_fields(*args, &block)
search_fields(:g, args, block)
end
def attribute_fields(*args, &block)
@ -114,15 +110,27 @@ module Ransack
end
def predicate_select(options = {}, html_options = {})
options[:compounds] = true if options[:compounds].nil?
keys = options[:compounds] ? Ransack.predicate_keys : Ransack.predicate_keys.reject {|k| k.match(/_(any|all)$/)}
if only = options[:only]
if only.respond_to? :call
keys = keys.select {|k| only.call(k)}
else
only = Array.wrap(only).map(&:to_s)
keys = keys.select {|k| only.include? k.sub(/_(any|all)$/, '')}
end
end
@template.collection_select(
@object_name, :p, Predicate.collection, :first, :last,
@object_name, :p, keys.map {|k| [k, Translate.predicate(k)]}, :first, :last,
objectify_options(options), @default_options.merge(html_options)
)
end
def combinator_select(options = {}, html_options = {})
choices = Nodes::Condition === object ? [:or, :and] : [:and, :or]
@template.collection_select(
@object_name, :m, [['or', Translate.word(:or)], ['and', Translate.word(:and)]], :first, :last,
@object_name, :m, choices.map {|o| [o.to_s, Translate.word(o)]}, :first, :last,
objectify_options(options), @default_options.merge(html_options)
)
end

View File

@ -4,5 +4,4 @@ require 'ransack/nodes/attribute'
require 'ransack/nodes/value'
require 'ransack/nodes/condition'
require 'ransack/nodes/sort'
require 'ransack/nodes/and'
require 'ransack/nodes/or'
require 'ransack/nodes/grouping'

View File

@ -1,8 +0,0 @@
require 'ransack/nodes/grouping'
module Ransack
module Nodes
class And < Grouping
end
end
end

View File

@ -2,11 +2,20 @@ module Ransack
module Nodes
class Grouping < Node
attr_reader :conditions
attr_accessor :combinator
alias :m :combinator
alias :m= :combinator=
i18n_word :condition, :and, :or
i18n_alias :c => :condition, :n => :and, :o => :or
delegate :each, :to => :values
def initialize(context, combinator = nil)
super(context)
self.combinator = combinator.to_s if combinator
end
def persisted?
false
end
@ -52,7 +61,7 @@ module Ransack
end
def values
conditions + ors + ands
conditions + groupings
end
def respond_to?(method_id)
@ -79,51 +88,28 @@ module Ransack
condition
end
def ands
@ands ||= []
def groupings
@groupings ||= []
end
alias :n :ands
alias :g :groupings
def ands=(ands)
case ands
def groupings=(groupings)
case groupings
when Array
ands.each do |attrs|
and_object = And.new(@context).build(attrs)
self.ands << and_object if and_object.values.any?
groupings.each do |attrs|
grouping_object = new_grouping(attrs)
self.groupings << grouping_object if grouping_object.values.any?
end
when Hash
ands.each do |index, attrs|
and_object = And.new(@context).build(attrs)
self.ands << and_object if and_object.values.any?
groupings.each do |index, attrs|
grouping_object = new_grouping(attrs)
self.groupings << grouping_object if grouping_object.values.any?
end
else
raise ArgumentError, "Invalid argument (#{ands.class}) supplied to ands="
raise ArgumentError, "Invalid argument (#{groupings.class}) supplied to groupings="
end
end
alias :n= :ands=
def ors
@ors ||= []
end
alias :o :ors
def ors=(ors)
case ors
when Array
ors.each do |attrs|
or_object = Or.new(@context).build(attrs)
self.ors << or_object if or_object.values.any?
end
when Hash
ors.each do |index, attrs|
or_object = Or.new(@context).build(attrs)
self.ors << or_object if or_object.values.any?
end
else
raise ArgumentError, "Invalid argument (#{ors.class}) supplied to ors="
end
end
alias :o= :ors=
alias :g= :groupings=
def method_missing(method_id, *args)
method_name = method_id.to_s
@ -138,39 +124,28 @@ module Ransack
def attribute_method?(name)
name = strip_predicate_and_index(name)
case name
when /^(n|o|c|ands|ors|conditions)=?$/
when /^(g|c|m|groupings|conditions|combinator)=?$/
true
else
name.split(/_and_|_or_/).select {|n| !@context.attribute_method?(n)}.empty?
end
end
def build_and(params = {})
def build_grouping(params = {})
params ||= {}
new_and(params).tap do |new_and|
self.ands << new_and
new_grouping(params).tap do |new_grouping|
self.groupings << new_grouping
end
end
def new_and(params = {})
And.new(@context).build(params)
end
def build_or(params = {})
params ||= {}
new_or(params).tap do |new_or|
self.ors << new_or
end
end
def new_or(params = {})
Or.new(@context).build(params)
def new_grouping(params = {})
Grouping.new(@context).build(params)
end
def build(params)
params.with_indifferent_access.each do |key, value|
case key
when /^(n|o|c)$/
when /^(g|c|m)$/
self.send("#{key}=", value)
else
write_attribute(key.to_s, value)

View File

@ -1,8 +0,0 @@
require 'ransack/nodes/grouping'
module Ransack
module Nodes
class Or < Grouping
end
end
end

View File

@ -4,15 +4,11 @@ module Ransack
class << self
def named(name)
Configuration.predicates[name.to_s]
Ransack.predicates[name.to_s]
end
def for_attribute_name(attribute_name)
self.named(Configuration.predicate_keys.detect {|p| attribute_name.to_s.match(/_#{p}$/)})
end
def collection
Configuration.predicates.map {|k, v| [k, Translate.predicate(k)]}
self.named(Ransack.predicate_keys.detect {|p| attribute_name.to_s.match(/_#{p}$/)})
end
end

View File

@ -9,15 +9,15 @@ module Ransack
attr_reader :base, :context
delegate :object, :klass, :to => :context
delegate :new_and, :new_or, :new_condition,
:build_and, :build_or, :build_condition,
delegate :new_grouping, :new_condition,
:build_grouping, :build_condition,
:translate, :to => :base
def initialize(object, params = {}, options = {})
params ||= {}
@context = Context.for(object)
@context.auth_object = options[:auth_object]
@base = Nodes::And.new(@context)
@base = Nodes::Grouping.new(@context, 'and')
build(params.with_indifferent_access)
end

View File

@ -1,3 +1,3 @@
module Ransack
VERSION = "0.3.0"
VERSION = "0.4.0"
end

View File

@ -17,7 +17,11 @@ module Ransack
object.arel_predicate if object.valid?
end
def visit_Ransack_Nodes_And(object)
def visit_Ransack_Nodes_Grouping(object)
object.combinator == 'or' ? visit_or(object) : visit_and(object)
end
def visit_and(object)
nodes = object.values.map {|o| accept(o)}.compact
return nil unless nodes.size > 0
@ -28,11 +32,7 @@ module Ransack
end
end
def visit_Ransack_Nodes_Sort(object)
object.attr.send(object.dir) if object.valid?
end
def visit_Ransack_Nodes_Or(object)
def visit_or(object)
nodes = object.values.map {|o| accept(o)}.compact
return nil unless nodes.size > 0
@ -43,6 +43,10 @@ module Ransack
end
end
def visit_Ransack_Nodes_Sort(object)
object.attr.send(object.dir) if object.valid?
end
def quoted?(object)
case object
when Arel::Nodes::SqlLiteral, Bignum, Fixnum

View File

@ -38,40 +38,82 @@ module Ransack
end
end
it 'localizes labels' do
html = @f.label :name_cont
html.should match /Full Name contains/
describe '#label' do
it 'localizes attribute names' do
html = @f.label :name_cont
html.should match /Full Name contains/
end
end
it 'localizes submit' do
html = @f.submit
html.should match /"Search"/
describe '#submit' do
it 'localizes :search when no default value given' do
html = @f.submit
html.should match /"Search"/
end
end
it 'returns ransackable attributes for attribute_select' do
html = @f.attribute_select
html.split(/\n/).should have(Person.ransackable_attributes.size + 1).lines
Person.ransackable_attributes.each do |attribute|
html.should match /<option value="#{attribute}">/
describe '#attribute_select' do
it 'returns ransackable attributes' do
html = @f.attribute_select
html.split(/\n/).should have(Person.ransackable_attributes.size + 1).lines
Person.ransackable_attributes.each do |attribute|
html.should match /<option value="#{attribute}">/
end
end
it 'returns ransackable attributes for associations with :associations' do
attributes = Person.ransackable_attributes + Article.ransackable_attributes.map {|a| "articles_#{a}"}
html = @f.attribute_select :associations => ['articles']
html.split(/\n/).should have(attributes.size).lines
attributes.each do |attribute|
html.should match /<option value="#{attribute}">/
end
end
it 'returns option groups for base and associations with :associations' do
html = @f.attribute_select :associations => ['articles']
[Person, Article].each do |model|
html.should match /<optgroup label="#{model}">/
end
end
end
describe '#predicate_select' do
it 'returns predicates with predicate_select' do
html = @f.predicate_select
Ransack.predicate_keys.each do |key|
html.should match /<option value="#{key}">/
end
end
it 'filters predicates with single-value :only' do
html = @f.predicate_select :only => 'eq'
Ransack.predicate_keys.reject {|k| k =~ /^eq/}.each do |key|
html.should_not match /<option value="#{key}">/
end
end
it 'filters predicates with multi-value :only' do
html = @f.predicate_select :only => [:eq, :lt]
Ransack.predicate_keys.reject {|k| k =~ /^(eq|lt)/}.each do |key|
html.should_not match /<option value="#{key}">/
end
end
it 'excludes compounds when :compounds => false' do
html = @f.predicate_select :compounds => false
Ransack.predicate_keys.select {|k| k =~ /_(any|all)$/}.each do |key|
html.should_not match /<option value="#{key}">/
end
end
end
it 'returns ransackable attributes for associations in attribute_select with associations' do
attributes = Person.ransackable_attributes + Article.ransackable_attributes.map {|a| "articles_#{a}"}
html = @f.attribute_select :associations => ['articles']
html.split(/\n/).should have(attributes.size).lines
attributes.each do |attribute|
html.should match /<option value="#{attribute}">/
end
end
it 'returns option groups for base and associations in attribute_select with associations' do
html = @f.attribute_select :associations => ['articles']
[Person, Article].each do |model|
html.should match /<optgroup label="#{model}">/
end
end
end
end
end

View File

@ -39,30 +39,34 @@ module Ransack
it 'accepts arrays of groupings' do
search = Search.new(Person,
:o => [
{:name_eq => 'Ernie', :children_name_eq => 'Ernie'},
{:name_eq => 'Bert', :children_name_eq => 'Bert'},
:g => [
{:m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie'},
{:m => 'or', :name_eq => 'Bert', :children_name_eq => 'Bert'},
]
)
ors = search.ors
ors = search.groupings
ors.should have(2).items
or1, or2 = ors
or1.should be_a Nodes::Or
or2.should be_a Nodes::Or
or1.should be_a Nodes::Grouping
or1.combinator.should eq 'or'
or2.should be_a Nodes::Grouping
or2.combinator.should eq 'or'
end
it 'accepts "attributes" hashes for groupings' do
search = Search.new(Person,
:o => {
'0' => {:name_eq => 'Ernie', :children_name_eq => 'Ernie'},
'1' => {:name_eq => 'Bert', :children_name_eq => 'Bert'},
:g => {
'0' => {:m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie'},
'1' => {:m => 'or', :name_eq => 'Bert', :children_name_eq => 'Bert'},
}
)
ors = search.ors
ors = search.groupings
ors.should have(2).items
or1, or2 = ors
or1.should be_a Nodes::Or
or2.should be_a Nodes::Or
or1.should be_a Nodes::Grouping
or1.combinator.should eq 'or'
or2.should be_a Nodes::Grouping
or2.combinator.should eq 'or'
end
it 'accepts "attributes" hashes for conditions' do
@ -102,7 +106,8 @@ module Ransack
it 'evaluates nested conditions' do
search = Search.new(Person, :children_name_eq => 'Ernie',
:o => [{
:g => [{
:m => 'or',
:name_eq => 'Ernie',
:children_children_name_eq => 'Ernie'
}]
@ -114,9 +119,9 @@ module Ransack
it 'evaluates arrays of groupings' do
search = Search.new(Person,
:o => [
{:name_eq => 'Ernie', :children_name_eq => 'Ernie'},
{:name_eq => 'Bert', :children_name_eq => 'Bert'},
:g => [
{:m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie'},
{:m => 'or', :name_eq => 'Bert', :children_name_eq => 'Bert'},
]
)
search.result.should be_an ActiveRecord::Relation
@ -125,7 +130,7 @@ module Ransack
end
it 'returns distinct records when passed :distinct => true' do
search = Search.new(Person, :o => [{:comments_body_cont => 'e', :articles_comments_body_cont => 'e'}])
search = Search.new(Person, :g => [{:m => 'or', :comments_body_cont => 'e', :articles_comments_body_cont => 'e'}])
search.result.should have(920).items
search.result(:distinct => true).should have(330).items
search.result.all.uniq.should eq search.result(:distinct => true).all
@ -195,9 +200,9 @@ module Ransack
end
it 'allows chaining to access nested conditions' do
@s.ors = [{:name_eq => 'Ernie', :children_name_eq => 'Ernie'}]
@s.ors.first.name_eq.should eq 'Ernie'
@s.ors.first.children_name_eq.should eq 'Ernie'
@s.groupings = [{:m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie'}]
@s.groupings.first.name_eq.should eq 'Ernie'
@s.groupings.first.children_name_eq.should eq 'Ernie'
end
end