ransack 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +11 -0
- data/LICENSE +20 -0
- data/README.rdoc +5 -0
- data/Rakefile +19 -0
- data/lib/ransack.rb +24 -0
- data/lib/ransack/adapters/active_record.rb +2 -0
- data/lib/ransack/adapters/active_record/base.rb +17 -0
- data/lib/ransack/adapters/active_record/context.rb +153 -0
- data/lib/ransack/configuration.rb +39 -0
- data/lib/ransack/constants.rb +23 -0
- data/lib/ransack/context.rb +152 -0
- data/lib/ransack/helpers.rb +2 -0
- data/lib/ransack/helpers/form_builder.rb +172 -0
- data/lib/ransack/helpers/form_helper.rb +27 -0
- data/lib/ransack/locale/en.yml +67 -0
- data/lib/ransack/naming.rb +53 -0
- data/lib/ransack/nodes.rb +7 -0
- data/lib/ransack/nodes/and.rb +8 -0
- data/lib/ransack/nodes/attribute.rb +36 -0
- data/lib/ransack/nodes/condition.rb +209 -0
- data/lib/ransack/nodes/grouping.rb +207 -0
- data/lib/ransack/nodes/node.rb +34 -0
- data/lib/ransack/nodes/or.rb +8 -0
- data/lib/ransack/nodes/sort.rb +39 -0
- data/lib/ransack/nodes/value.rb +120 -0
- data/lib/ransack/predicate.rb +57 -0
- data/lib/ransack/search.rb +114 -0
- data/lib/ransack/translate.rb +92 -0
- data/lib/ransack/version.rb +3 -0
- data/ransack.gemspec +29 -0
- data/spec/blueprints/articles.rb +5 -0
- data/spec/blueprints/comments.rb +5 -0
- data/spec/blueprints/notes.rb +3 -0
- data/spec/blueprints/people.rb +4 -0
- data/spec/blueprints/tags.rb +3 -0
- data/spec/console.rb +22 -0
- data/spec/helpers/ransack_helper.rb +2 -0
- data/spec/playground.rb +37 -0
- data/spec/ransack/adapters/active_record/base_spec.rb +30 -0
- data/spec/ransack/adapters/active_record/context_spec.rb +29 -0
- data/spec/ransack/configuration_spec.rb +11 -0
- data/spec/ransack/helpers/form_builder_spec.rb +39 -0
- data/spec/ransack/nodes/compound_condition_spec.rb +0 -0
- data/spec/ransack/nodes/condition_spec.rb +0 -0
- data/spec/ransack/nodes/grouping_spec.rb +13 -0
- data/spec/ransack/predicate_spec.rb +25 -0
- data/spec/ransack/search_spec.rb +182 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/schema.rb +102 -0
- metadata +200 -0
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
|
3
|
+
module Ransack
|
4
|
+
module Helpers
|
5
|
+
class FormBuilder < ::ActionView::Helpers::FormBuilder
|
6
|
+
def label(method, *args, &block)
|
7
|
+
options = args.extract_options!
|
8
|
+
text = args.first
|
9
|
+
i18n = options[:i18n] || {}
|
10
|
+
text ||= object.translate(method, i18n.reverse_merge(:include_associations => true)) if object.respond_to? :translate
|
11
|
+
super(method, text, options, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def attribute_select(options = {}, html_options = {})
|
15
|
+
raise ArgumentError, "attribute_select must be called inside a search FormBuilder!" unless object.respond_to?(:context)
|
16
|
+
options[:include_blank] = true unless options.has_key?(:include_blank)
|
17
|
+
bases = [''] + association_array(options[:associations])
|
18
|
+
if bases.size > 1
|
19
|
+
collection = bases.map do |base|
|
20
|
+
[
|
21
|
+
Translate.association(base, :context => object.context),
|
22
|
+
object.context.searchable_columns(base).map do |c|
|
23
|
+
[
|
24
|
+
attr_from_base_and_column(base, c),
|
25
|
+
Translate.attribute(attr_from_base_and_column(base, c), :context => object.context)
|
26
|
+
]
|
27
|
+
end
|
28
|
+
]
|
29
|
+
end
|
30
|
+
@template.grouped_collection_select(
|
31
|
+
@object_name, :name, collection, :last, :first, :first, :last,
|
32
|
+
objectify_options(options), @default_options.merge(html_options)
|
33
|
+
)
|
34
|
+
else
|
35
|
+
collection = object.context.searchable_columns(bases.first).map do |c|
|
36
|
+
[
|
37
|
+
attr_from_base_and_column(bases.first, c),
|
38
|
+
Translate.attribute(attr_from_base_and_column(bases.first, c), :context => object.context)
|
39
|
+
]
|
40
|
+
end
|
41
|
+
@template.collection_select(
|
42
|
+
@object_name, :name, collection, :first, :last,
|
43
|
+
objectify_options(options), @default_options.merge(html_options)
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def sort_select(options = {}, html_options = {})
|
49
|
+
raise ArgumentError, "sort_select must be called inside a search FormBuilder!" unless object.respond_to?(:context)
|
50
|
+
options[:include_blank] = true unless options.has_key?(:include_blank)
|
51
|
+
bases = [''] + association_array(options[:associations])
|
52
|
+
if bases.any?
|
53
|
+
collection = bases.map do |base|
|
54
|
+
[
|
55
|
+
Translate.association(base, :context => object.context),
|
56
|
+
object.context.searchable_columns(base).map do |c|
|
57
|
+
[
|
58
|
+
attr_from_base_and_column(base, c),
|
59
|
+
Translate.attribute(attr_from_base_and_column(base, c), :context => object.context)
|
60
|
+
]
|
61
|
+
end
|
62
|
+
]
|
63
|
+
end
|
64
|
+
@template.grouped_collection_select(
|
65
|
+
@object_name, :name, collection, :last, :first, :first, :last,
|
66
|
+
objectify_options(options), @default_options.merge(html_options)
|
67
|
+
) + @template.collection_select(
|
68
|
+
@object_name, :dir, [['asc', object.translate('asc')], ['desc', object.translate('desc')]], :first, :last,
|
69
|
+
objectify_options(options), @default_options.merge(html_options)
|
70
|
+
)
|
71
|
+
else
|
72
|
+
collection = object.context.searchable_columns(bases.first).map do |c|
|
73
|
+
[
|
74
|
+
attr_from_base_and_column(bases.first, c),
|
75
|
+
Translate.attribute(attr_from_base_and_column(bases.first, c), :context => object.context)
|
76
|
+
]
|
77
|
+
end
|
78
|
+
@template.collection_select(
|
79
|
+
@object_name, :name, collection, :first, :last,
|
80
|
+
objectify_options(options), @default_options.merge(html_options)
|
81
|
+
) + @template.collection_select(
|
82
|
+
@object_name, :dir, [['asc', object.translate('asc')], ['desc', object.translate('desc')]], :first, :last,
|
83
|
+
objectify_options(options), @default_options.merge(html_options)
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def sort_fields(*args, &block)
|
89
|
+
search_fields(:s, args, block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def condition_fields(*args, &block)
|
93
|
+
search_fields(:c, args, block)
|
94
|
+
end
|
95
|
+
|
96
|
+
def and_fields(*args, &block)
|
97
|
+
search_fields(:n, args, block)
|
98
|
+
end
|
99
|
+
|
100
|
+
def or_fields(*args, &block)
|
101
|
+
search_fields(:o, args, block)
|
102
|
+
end
|
103
|
+
|
104
|
+
def attribute_fields(*args, &block)
|
105
|
+
search_fields(:a, args, block)
|
106
|
+
end
|
107
|
+
|
108
|
+
def predicate_fields(*args, &block)
|
109
|
+
search_fields(:p, args, block)
|
110
|
+
end
|
111
|
+
|
112
|
+
def value_fields(*args, &block)
|
113
|
+
search_fields(:v, args, block)
|
114
|
+
end
|
115
|
+
|
116
|
+
def search_fields(name, args, block)
|
117
|
+
args << {} unless args.last.is_a?(Hash)
|
118
|
+
args.last[:builder] ||= options[:builder]
|
119
|
+
args.last[:parent_builder] = self
|
120
|
+
options = args.extract_options!
|
121
|
+
objects = args.shift
|
122
|
+
objects ||= @object.send(name)
|
123
|
+
objects = [objects] unless Array === objects
|
124
|
+
name = "#{options[:object_name] || object_name}[#{name}]"
|
125
|
+
output = ActiveSupport::SafeBuffer.new
|
126
|
+
objects.each do |child|
|
127
|
+
output << @template.fields_for("#{name}[#{options[:child_index] || nested_child_index(name)}]", child, options, &block)
|
128
|
+
end
|
129
|
+
output
|
130
|
+
end
|
131
|
+
|
132
|
+
def predicate_select(options = {}, html_options = {})
|
133
|
+
@template.collection_select(
|
134
|
+
@object_name, :p, Predicate.collection, :first, :last,
|
135
|
+
objectify_options(options), @default_options.merge(html_options)
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
def combinator_select(options = {}, html_options = {})
|
140
|
+
@template.collection_select(
|
141
|
+
@object_name, :m, [['or', Translate.word(:or)], ['and', Translate.word(:and)]], :first, :last,
|
142
|
+
objectify_options(options), @default_options.merge(html_options)
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def association_array(obj, prefix = nil)
|
149
|
+
([prefix] + case obj
|
150
|
+
when Array
|
151
|
+
obj
|
152
|
+
when Hash
|
153
|
+
obj.map do |key, value|
|
154
|
+
case value
|
155
|
+
when Array, Hash
|
156
|
+
bases_array(value, key.to_s)
|
157
|
+
else
|
158
|
+
[key.to_s, [key, value].join('_')]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
else
|
162
|
+
[obj]
|
163
|
+
end).compact.flatten.map {|v| [prefix, v].compact.join('_')}
|
164
|
+
end
|
165
|
+
|
166
|
+
def attr_from_base_and_column(base, column)
|
167
|
+
[base, column].reject {|v| v.blank?}.join('_')
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Helpers
|
3
|
+
module FormHelper
|
4
|
+
def search_form_for(record, options = {}, &proc)
|
5
|
+
if record.is_a?(Ransack::Search)
|
6
|
+
search = record
|
7
|
+
options[:url] ||= polymorphic_path(search.klass)
|
8
|
+
elsif record.is_a?(Array) && (search = record.detect {|o| o.is_a?(Ransack::Search)})
|
9
|
+
options[:url] ||= polymorphic_path(record.map {|o| o.is_a?(Ransack::Search) ? o.klass : o})
|
10
|
+
else
|
11
|
+
raise ArgumentError, "No Ransack::Search object was provided to search_form_for!"
|
12
|
+
end
|
13
|
+
options[:html] ||= {}
|
14
|
+
html_options = {
|
15
|
+
:class => options[:as] ? "#{options[:as]}_search" : "#{search.klass.to_s.underscore}_search",
|
16
|
+
:id => options[:as] ? "#{options[:as]}_search" : "#{search.klass.to_s.underscore}_search",
|
17
|
+
:method => :get
|
18
|
+
}
|
19
|
+
options[:html].reverse_merge!(html_options)
|
20
|
+
options[:builder] ||= FormBuilder
|
21
|
+
|
22
|
+
form_for(record, options, &proc)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
en:
|
2
|
+
ransack:
|
3
|
+
predicate: "predicate"
|
4
|
+
and: "and"
|
5
|
+
or: "or"
|
6
|
+
combinator: "combinator"
|
7
|
+
attribute: "attribute"
|
8
|
+
value: "value"
|
9
|
+
condition: "condition"
|
10
|
+
sort: "sort"
|
11
|
+
asc: "ascending"
|
12
|
+
desc: "descending"
|
13
|
+
predicates:
|
14
|
+
eq: "equals"
|
15
|
+
eq_any: "equals any"
|
16
|
+
eq_all: "equals all"
|
17
|
+
not_eq: "not equal to"
|
18
|
+
not_eq_any: "not equal to any"
|
19
|
+
not_eq_all: "not equal to all"
|
20
|
+
matches: "matches"
|
21
|
+
matches_any: "matches_any"
|
22
|
+
matches_all: "matches all"
|
23
|
+
does_not_match: "doesn't match"
|
24
|
+
does_not_match_any: "doesn't match any"
|
25
|
+
does_not_match_all: "doesn't match all"
|
26
|
+
lt: "less than"
|
27
|
+
lt_any: "less than any"
|
28
|
+
lt_all: "less than all"
|
29
|
+
lteq: "less than or equal to"
|
30
|
+
lteq_any: "less than or equal to any"
|
31
|
+
lteq_all: "less than or equal to all"
|
32
|
+
gt: "greater than"
|
33
|
+
gt_any: "greater than any"
|
34
|
+
gt_all: "greater than all"
|
35
|
+
gteq: "greater than or equal to"
|
36
|
+
gteq_any: "greater than or equal to any"
|
37
|
+
gteq_all: "greater than or equal to all"
|
38
|
+
in: "in"
|
39
|
+
in_any: "in any"
|
40
|
+
in_all: "in all"
|
41
|
+
not_in: "not in"
|
42
|
+
not_in_any: "not in any"
|
43
|
+
not_in_all: "not in all"
|
44
|
+
cont: "contains"
|
45
|
+
cont_any: "contains any"
|
46
|
+
cont_all: "contains all"
|
47
|
+
not_cont: "doesn't contain"
|
48
|
+
not_cont_any: "doesn't contain any"
|
49
|
+
not_cont_all: "doesn't contain all"
|
50
|
+
start: "starts with"
|
51
|
+
start_any: "starts with any"
|
52
|
+
start_all: "starts with all"
|
53
|
+
not_start: "doesn't start with"
|
54
|
+
not_start_any: "doesn't start with any"
|
55
|
+
not_start_all: "doesn't start with all"
|
56
|
+
end: "ends with"
|
57
|
+
end_any: "ends with any"
|
58
|
+
end_all: "ends with all"
|
59
|
+
not_end: "doesn't end with"
|
60
|
+
not_end_any: "doesn't end with any"
|
61
|
+
not_end_all: "doesn't end with all"
|
62
|
+
true: "is true"
|
63
|
+
false: "is false"
|
64
|
+
present: "is present"
|
65
|
+
blank: "is blank"
|
66
|
+
null: "is null"
|
67
|
+
not_null: "is not null"
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Naming
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
def persisted?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_key
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_param
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_model
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Name < String
|
26
|
+
attr_reader :singular, :plural, :element, :collection, :partial_path, :human, :param_key, :route_key, :i18n_key
|
27
|
+
alias_method :cache_key, :collection
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
super("Search")
|
31
|
+
@singular = "search".freeze
|
32
|
+
@plural = "searches".freeze
|
33
|
+
@element = "search".freeze
|
34
|
+
@human = "Search".freeze
|
35
|
+
@collection = "ransack/searches".freeze
|
36
|
+
@partial_path = "#{@collection}/#{@element}".freeze
|
37
|
+
@param_key = "q".freeze
|
38
|
+
@route_key = "searches".freeze
|
39
|
+
@i18n_key = :ransack
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
def model_name
|
45
|
+
@_model_name ||= Name.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def i18n_scope
|
49
|
+
:ransack
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Nodes
|
3
|
+
class Attribute < Node
|
4
|
+
attr_reader :name, :attr
|
5
|
+
delegate :blank?, :==, :to => :name
|
6
|
+
|
7
|
+
def initialize(context, name = nil)
|
8
|
+
super(context)
|
9
|
+
self.name = name unless name.blank?
|
10
|
+
end
|
11
|
+
|
12
|
+
def name=(name)
|
13
|
+
@name = name
|
14
|
+
@attr = contextualize(name) unless name.blank?
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid?
|
18
|
+
@attr
|
19
|
+
end
|
20
|
+
|
21
|
+
def eql?(other)
|
22
|
+
self.class == other.class &&
|
23
|
+
self.name == other.name
|
24
|
+
end
|
25
|
+
alias :== :eql?
|
26
|
+
|
27
|
+
def hash
|
28
|
+
self.name.hash
|
29
|
+
end
|
30
|
+
|
31
|
+
def persisted?
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Nodes
|
3
|
+
class Condition < Node
|
4
|
+
i18n_word :attribute, :predicate, :combinator, :value
|
5
|
+
i18n_alias :a => :attribute, :p => :predicate, :m => :combinator, :v => :value
|
6
|
+
|
7
|
+
attr_reader :predicate
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def extract(context, key, values)
|
11
|
+
attributes, predicate = extract_attributes_and_predicate(key)
|
12
|
+
if attributes.size > 0
|
13
|
+
combinator = key.match(/_(or|and)_/) ? $1 : nil
|
14
|
+
condition = self.new(context)
|
15
|
+
condition.build(
|
16
|
+
:a => attributes,
|
17
|
+
:p => predicate.name,
|
18
|
+
:m => combinator,
|
19
|
+
:v => [values]
|
20
|
+
)
|
21
|
+
predicate.validate(condition.values) ? condition : nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def extract_attributes_and_predicate(key)
|
28
|
+
str = key.dup
|
29
|
+
name = Ransack::Configuration.predicate_keys.detect {|p| str.sub!(/_#{p}$/, '')}
|
30
|
+
predicate = Predicate.named(name)
|
31
|
+
raise ArgumentError, "No valid predicate for #{key}" unless predicate
|
32
|
+
attributes = str.split(/_and_|_or_/)
|
33
|
+
[attributes, predicate]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def valid?
|
38
|
+
attributes.detect(&:valid?) && predicate && valid_arity? && predicate.validate(values) && valid_combinator?
|
39
|
+
end
|
40
|
+
|
41
|
+
def valid_arity?
|
42
|
+
values.size <= 1 || predicate.compound || %w(in not_in).include?(predicate.name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def attributes
|
46
|
+
@attributes ||= []
|
47
|
+
end
|
48
|
+
alias :a :attributes
|
49
|
+
|
50
|
+
def attributes=(args)
|
51
|
+
case args
|
52
|
+
when Array
|
53
|
+
args.each do |attr|
|
54
|
+
attr = Attribute.new(@context, attr)
|
55
|
+
self.attributes << attr if attr.valid?
|
56
|
+
end
|
57
|
+
when Hash
|
58
|
+
args.each do |index, attrs|
|
59
|
+
attr = Attribute.new(@context, attrs[:name])
|
60
|
+
self.attributes << attr if attr.valid?
|
61
|
+
end
|
62
|
+
else
|
63
|
+
raise ArgumentError, "Invalid argument (#{args.class}) supplied to attributes="
|
64
|
+
end
|
65
|
+
end
|
66
|
+
alias :a= :attributes=
|
67
|
+
|
68
|
+
def values
|
69
|
+
@values ||= []
|
70
|
+
end
|
71
|
+
alias :v :values
|
72
|
+
|
73
|
+
def values=(args)
|
74
|
+
case args
|
75
|
+
when Array
|
76
|
+
args.each do |val|
|
77
|
+
val = Value.new(@context, val, current_type)
|
78
|
+
self.values << val
|
79
|
+
end
|
80
|
+
when Hash
|
81
|
+
args.each do |index, attrs|
|
82
|
+
val = Value.new(@context, attrs[:value], current_type)
|
83
|
+
self.values << val
|
84
|
+
end
|
85
|
+
else
|
86
|
+
raise ArgumentError, "Invalid argument (#{args.class}) supplied to values="
|
87
|
+
end
|
88
|
+
end
|
89
|
+
alias :v= :values=
|
90
|
+
|
91
|
+
def combinator
|
92
|
+
@attributes.size > 1 ? @combinator : nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def combinator=(val)
|
96
|
+
@combinator = ['and', 'or'].detect {|v| v == val.to_s} || nil
|
97
|
+
end
|
98
|
+
alias :m= :combinator=
|
99
|
+
alias :m :combinator
|
100
|
+
|
101
|
+
def build_attribute(name = nil)
|
102
|
+
Attribute.new(@context, name).tap do |attribute|
|
103
|
+
self.attributes << attribute
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def build_value(val = nil)
|
108
|
+
Value.new(@context, val, current_type).tap do |value|
|
109
|
+
self.values << value
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def value
|
114
|
+
predicate.compound ? values.map(&:value) : values.first.value
|
115
|
+
end
|
116
|
+
|
117
|
+
def build(params)
|
118
|
+
params.with_indifferent_access.each do |key, value|
|
119
|
+
if key.match(/^(a|v|p|m)$/)
|
120
|
+
self.send("#{key}=", value)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
set_value_types!
|
125
|
+
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
def persisted?
|
130
|
+
false
|
131
|
+
end
|
132
|
+
|
133
|
+
def key
|
134
|
+
@key ||= attributes.map(&:name).join("_#{combinator}_") + "_#{predicate.name}"
|
135
|
+
end
|
136
|
+
|
137
|
+
def eql?(other)
|
138
|
+
self.class == other.class &&
|
139
|
+
self.attributes == other.attributes &&
|
140
|
+
self.predicate == other.predicate &&
|
141
|
+
self.values == other.values &&
|
142
|
+
self.combinator == other.combinator
|
143
|
+
end
|
144
|
+
alias :== :eql?
|
145
|
+
|
146
|
+
def hash
|
147
|
+
[attributes, predicate, values, combinator].hash
|
148
|
+
end
|
149
|
+
|
150
|
+
def predicate_name=(name)
|
151
|
+
self.predicate = Predicate.named(name)
|
152
|
+
end
|
153
|
+
alias :p= :predicate_name=
|
154
|
+
|
155
|
+
def predicate=(predicate)
|
156
|
+
@predicate = predicate
|
157
|
+
predicate
|
158
|
+
end
|
159
|
+
|
160
|
+
def predicate_name
|
161
|
+
predicate.name if predicate
|
162
|
+
end
|
163
|
+
alias :p :predicate_name
|
164
|
+
|
165
|
+
def apply_predicate
|
166
|
+
attributes = arel_attributes.compact
|
167
|
+
|
168
|
+
if attributes.size > 1
|
169
|
+
case combinator
|
170
|
+
when 'and'
|
171
|
+
Arel::Nodes::Grouping.new(Arel::Nodes::And.new(
|
172
|
+
attributes.map {|a| a.send(predicate.arel_predicate, predicate.format(values))}
|
173
|
+
))
|
174
|
+
when 'or'
|
175
|
+
attributes.inject(attributes.shift.send(predicate.arel_predicate, predicate.format(values))) do |memo, a|
|
176
|
+
memo.or(a.send(predicate.arel_predicate, predicate.format(values)))
|
177
|
+
end
|
178
|
+
end
|
179
|
+
else
|
180
|
+
attributes.first.send(predicate.arel_predicate, predicate.format(values))
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def set_value_types!
|
187
|
+
self.values.each {|v| v.type = current_type}
|
188
|
+
end
|
189
|
+
|
190
|
+
def current_type
|
191
|
+
if predicate && predicate.type
|
192
|
+
predicate.type
|
193
|
+
elsif attributes.size > 0
|
194
|
+
@context.type_for(attributes.first.attr)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def valid_combinator?
|
199
|
+
attributes.size < 2 ||
|
200
|
+
['and', 'or'].include?(combinator)
|
201
|
+
end
|
202
|
+
|
203
|
+
def arel_attributes
|
204
|
+
attributes.map(&:attr)
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|