Add :only/:compound options to predicate_select, refactor ands/ors -> groupings
This commit is contained in:
parent
cec4e3d64f
commit
1c41a3ea2c
|
@ -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
|
||||
|
|
|
@ -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'
|
|
@ -1,8 +0,0 @@
|
|||
require 'ransack/nodes/grouping'
|
||||
|
||||
module Ransack
|
||||
module Nodes
|
||||
class And < Grouping
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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)
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
require 'ransack/nodes/grouping'
|
||||
|
||||
module Ransack
|
||||
module Nodes
|
||||
class Or < Grouping
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module Ransack
|
||||
VERSION = "0.3.0"
|
||||
VERSION = "0.4.0"
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue