ransack 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|