de 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README +28 -0
- data/Rakefile +1 -0
- data/de.gemspec +26 -0
- data/lib/de/boolean/operand.rb +46 -0
- data/lib/de/boolean/operator.rb +134 -0
- data/lib/de/boolean.rb +2 -0
- data/lib/de/de.rb +210 -0
- data/lib/de/error.rb +6 -0
- data/lib/de/sunspot_solr/operand.rb +273 -0
- data/lib/de/sunspot_solr/operator.rb +131 -0
- data/lib/de/sunspot_solr/search.rb +179 -0
- data/lib/de/sunspot_solr.rb +9 -0
- data/lib/de/symmetric_operator.rb +31 -0
- data/lib/de/version.rb +3 -0
- data/lib/de.rb +3 -0
- data/test/de_boolean_and_test.rb +51 -0
- data/test/de_boolean_not_test.rb +39 -0
- data/test/de_boolean_operand_test.rb +30 -0
- data/test/de_boolean_operator_test.rb +34 -0
- data/test/de_boolean_or_test.rb +51 -0
- data/test/de_expression_test.rb +35 -0
- data/test/de_operand_test.rb +29 -0
- data/test/de_operator_test.rb +48 -0
- data/test/de_sunspot_solr_and_test.rb +113 -0
- data/test/de_sunspot_solr_not_test.rb +49 -0
- data/test/de_sunspot_solr_or_test.rb +61 -0
- data/test/de_sunspot_solr_search_test.rb +251 -0
- data/test/de_sunspot_solr_sunspot_operand_test.rb +509 -0
- metadata +145 -0
@@ -0,0 +1,273 @@
|
|
1
|
+
#
|
2
|
+
# De::SunspotSolr operand classes.
|
3
|
+
# This file defines:
|
4
|
+
# - basic De::SunspotSolr::SunspotOperand class
|
5
|
+
# - De::SunspotSolr::IntervalSunspotOperand extending SunspotOperand for case of time intervals
|
6
|
+
# - A number of classes extending mentioned below and giving convenient interface for concrete sunspot query operations:
|
7
|
+
# - EqualTo, Without, GreaterThan, LessThan, Between, AnyOf, GreaterThanOrEqual, LessThanOrEqual
|
8
|
+
# - IntervalFromNowEqualTo, IntervalFromNowWithout, IntervalFromNowGreaterThan, IntervalFromNowLessThan, IntervalFromNowBetween,
|
9
|
+
# IntervalFromNowAnyOf, IntervalFromNowGreaterThanOrEqual, IntervalFromNowLessThanOrEqual
|
10
|
+
#
|
11
|
+
# De::SunspotSolr module provides engine to build, validate and evaluate dynamic sunspot query to solr
|
12
|
+
# based on some model.
|
13
|
+
# It is built as extension of De module
|
14
|
+
#
|
15
|
+
# SunspotSolr expression example:
|
16
|
+
#
|
17
|
+
# Sunspot.search(Product) do
|
18
|
+
# any_of do
|
19
|
+
# with(:client_id).equal_to(43)
|
20
|
+
# with(:name).equal_to('Name to trace')
|
21
|
+
# end
|
22
|
+
# with(:update_time).greater_then('2011-03-15 15:00:00')
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
|
26
|
+
#require 'active_support/inflector'
|
27
|
+
require 'active_support/all'
|
28
|
+
|
29
|
+
module De
|
30
|
+
module SunspotSolr
|
31
|
+
|
32
|
+
# Marker module included to all module classes
|
33
|
+
# to be able to check that class is inside module
|
34
|
+
module SP; end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Basic class representing sunspot search expression operand
|
38
|
+
#
|
39
|
+
# Operand representation in Sunspot query examples:
|
40
|
+
# - with(:client_id).equal_to(43)
|
41
|
+
# - with(:update_time).greater_then('2011-03-15 15:00:00')
|
42
|
+
#
|
43
|
+
#
|
44
|
+
class SunspotOperand < Operand
|
45
|
+
include De::SunspotSolr::SP
|
46
|
+
|
47
|
+
attr_reader :value, :operand
|
48
|
+
|
49
|
+
#
|
50
|
+
# Constructor for SunspotOperand
|
51
|
+
# Stores name and property as TreeNode's @name and @content correspondingly
|
52
|
+
#
|
53
|
+
# === Input
|
54
|
+
#
|
55
|
+
# name<String|Symbol>:: arbitrary object name
|
56
|
+
# property<String|Symbol>:: property examined by sunspot query
|
57
|
+
# (for example, :client_id in condition with(:client_id).equal_to(43)
|
58
|
+
# Stored as TreeNode's @content field
|
59
|
+
# operand<Symbol>:: operand type
|
60
|
+
# Examples: :equal_to, :without, :greater_than...
|
61
|
+
# value<Object>:: value to compare
|
62
|
+
# Example: 43 in condition with(:client_id).equal_to(43)
|
63
|
+
#
|
64
|
+
def initialize(name, property, operand, value)
|
65
|
+
super(name.to_s, property.to_s)
|
66
|
+
@value = value
|
67
|
+
@operand = operand
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Validation
|
72
|
+
#
|
73
|
+
# Operand is valid in case
|
74
|
+
# - its operand is included to valid_operands list
|
75
|
+
# - it is added to De::SunspotSolr::Search object and property is registered in this root object
|
76
|
+
# Last one is needed to choose appropriate condition representation according to property type and static/dynamic nature
|
77
|
+
#
|
78
|
+
def valid?
|
79
|
+
return false unless self.class.valid_operands.include?(@operand)
|
80
|
+
return false unless root.is_a?(Search)
|
81
|
+
return false unless root.options[:properties].key?(@content.to_sym)
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Evaluation
|
87
|
+
#
|
88
|
+
# Result depends on property type and static/dynamic nature
|
89
|
+
#
|
90
|
+
# === Output
|
91
|
+
#
|
92
|
+
# <String> - string representation of sunspot condition
|
93
|
+
#
|
94
|
+
def evaluate
|
95
|
+
super
|
96
|
+
if root.options[:properties][@content.to_sym][:dynamic]
|
97
|
+
"dynamic(:#{root.options[:dynamics][root.options[:properties][@content.to_sym][:type]]}) do
|
98
|
+
#{simple_evaluate}
|
99
|
+
end"
|
100
|
+
else
|
101
|
+
simple_evaluate
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Equal operand override
|
107
|
+
# Checks objects class and coincidence of +operand+, +value+ and +property+ (stored in +content+ filed)
|
108
|
+
#
|
109
|
+
# === Input
|
110
|
+
#
|
111
|
+
# obj<SunspotOperator>:: object to compare with
|
112
|
+
#
|
113
|
+
def ==(obj)
|
114
|
+
obj.is_a?(SunspotOperand) && obj.operand == @operand && obj.content == @content && obj.value == @value
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# Define hash function to give the same result for equal @operand, @content and @value
|
119
|
+
# (@name is not important for equal operands)
|
120
|
+
#
|
121
|
+
def hash
|
122
|
+
[@operand, @content, @value].hash
|
123
|
+
end
|
124
|
+
|
125
|
+
class << self
|
126
|
+
|
127
|
+
#
|
128
|
+
# Supported operands list
|
129
|
+
#
|
130
|
+
# === Output
|
131
|
+
#
|
132
|
+
# <Array> of symbols
|
133
|
+
#
|
134
|
+
def valid_operands
|
135
|
+
[:equal_to, :greater_than, :less_than, :between, :any_of]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
protected
|
140
|
+
|
141
|
+
def simple_evaluate
|
142
|
+
"with(:#{@content}).#{@operand}(#{value_to_compare})"
|
143
|
+
end
|
144
|
+
|
145
|
+
def value_to_compare
|
146
|
+
if @value.is_a?(Array)
|
147
|
+
output = "[#{@value.map {|element| atomic_value(element) }.join(',')}]"
|
148
|
+
elsif @value.is_a?(Range)
|
149
|
+
output = @value.exclude_end? ? atomic_value(@value.first)...atomic_value(@value.last) : atomic_value(@value.first)..atomic_value(@value.last)
|
150
|
+
else
|
151
|
+
output = atomic_value(@value)
|
152
|
+
end
|
153
|
+
|
154
|
+
output
|
155
|
+
end
|
156
|
+
|
157
|
+
def atomic_value(val)
|
158
|
+
case root.options[:properties][@content.to_sym][:type]
|
159
|
+
when :text, :string
|
160
|
+
"'#{val.escape_apos}'"
|
161
|
+
when :time
|
162
|
+
"'#{val.to_s(:db)}'"
|
163
|
+
when :float
|
164
|
+
val.to_f
|
165
|
+
else
|
166
|
+
val
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# Sunspot interval operand class
|
173
|
+
#
|
174
|
+
# It expends SunspotOperand class for properties of type :time
|
175
|
+
# and evaluates their values compared to given by interval from now.
|
176
|
+
# Interval is considered in days
|
177
|
+
#
|
178
|
+
# For example
|
179
|
+
#
|
180
|
+
# IntervalSunspotOperand.new('op1', :start_date, :less_then, 3)
|
181
|
+
#
|
182
|
+
# gives condition
|
183
|
+
#
|
184
|
+
# with(:start_date).less_than(Time.now + 3.days)
|
185
|
+
#
|
186
|
+
class IntervalSunspotOperand < SunspotOperand
|
187
|
+
|
188
|
+
def valid?
|
189
|
+
super && root.options[:properties][@content.to_sym] && root.options[:properties][@content.to_sym][:type] == :time
|
190
|
+
end
|
191
|
+
|
192
|
+
protected
|
193
|
+
|
194
|
+
def value_to_compare
|
195
|
+
now = Time.now
|
196
|
+
if @value.is_a?(Array)
|
197
|
+
shifted_value = "[#{@value.map {|element| atomic_value(now + element.days) }.join(',')}]"
|
198
|
+
elsif @value.is_a?(Range)
|
199
|
+
shifted_value = @value.exclude_end? ?
|
200
|
+
"#{atomic_value(now + @value.first.days)}...#{atomic_value( now + @value.last.days)}" :
|
201
|
+
"#{atomic_value(now + @value.first.days)}..#{atomic_value( now + @value.last.days)}"
|
202
|
+
else
|
203
|
+
shifted_value = atomic_value(now + @value.days)
|
204
|
+
end
|
205
|
+
|
206
|
+
shifted_value
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
available_operands = SunspotOperand.valid_operands | [:greater_than_or_equal, :less_than_or_equal, :without]
|
211
|
+
available_operands.each do |operand|
|
212
|
+
module_eval "
|
213
|
+
class #{operand.to_s.camelize} < SunspotOperand
|
214
|
+
def initialize(property, value)
|
215
|
+
super(\"\#\{property\}-\#\{rand(1000)\}\", property, :#{operand}, value)
|
216
|
+
end
|
217
|
+
|
218
|
+
def self.valid_operands
|
219
|
+
[:#{operand}]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
class IntervalFromNow#{operand.to_s.camelize} < IntervalSunspotOperand
|
224
|
+
def initialize(property, value)
|
225
|
+
super(\"\#\{property\}-\#\{rand(1000)\}\", property, :#{operand}, value)
|
226
|
+
end
|
227
|
+
|
228
|
+
def self.valid_operands
|
229
|
+
[:#{operand}]
|
230
|
+
end
|
231
|
+
end
|
232
|
+
"
|
233
|
+
end
|
234
|
+
|
235
|
+
[:greater_than_or_equal, :less_than_or_equal].each do |operand|
|
236
|
+
module_eval %{
|
237
|
+
class #{operand.to_s.camelize} < SunspotOperand
|
238
|
+
|
239
|
+
def simple_evaluate
|
240
|
+
"any_of do
|
241
|
+
with(:\#\{@content\}).#{operand.to_s.gsub(/_or_equal/, '')}(\#\{value_to_compare\})
|
242
|
+
with(:\#\{@content\}, \#\{value_to_compare\})
|
243
|
+
end"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
class IntervalFromNow#{operand.to_s.camelize} < IntervalSunspotOperand
|
248
|
+
|
249
|
+
def simple_evaluate
|
250
|
+
"any_of do
|
251
|
+
with(:\#\{@content\}).#{operand.to_s.gsub(/_or_equal/, '')}(\#\{value_to_compare\})
|
252
|
+
with(:\#\{@content\}, \#\{value_to_compare\})
|
253
|
+
end"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
}
|
257
|
+
end
|
258
|
+
|
259
|
+
class Without < SunspotOperand
|
260
|
+
|
261
|
+
def simple_evaluate
|
262
|
+
"without(:#{@content}, #{value_to_compare})"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
class IntervalFromNowWithout < IntervalSunspotOperand
|
267
|
+
|
268
|
+
def simple_evaluate
|
269
|
+
"without(:#{@content}, #{value_to_compare})"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
#
|
2
|
+
# De::SunspotSolr operator classes: And and Or
|
3
|
+
#
|
4
|
+
# De::SunspotSolr module provides engine to build, validate and evaluate dynamic sunspot query to solr
|
5
|
+
# based on some model.
|
6
|
+
# It is built as extension of De module
|
7
|
+
#
|
8
|
+
# SunspotSolr expression example:
|
9
|
+
#
|
10
|
+
# Sunspot.search(Product) do
|
11
|
+
# any_of do
|
12
|
+
# with(:client_id).equal_to(43)
|
13
|
+
# with(:name).equal_to('Name to trace')
|
14
|
+
# end
|
15
|
+
# with(:update_time).greater_then('2011-03-15 00:15:00')
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'de/symmetric_operator'
|
20
|
+
|
21
|
+
module De
|
22
|
+
module SunspotSolr
|
23
|
+
|
24
|
+
# Marker module included to all module classes
|
25
|
+
# to be able to check that class is inside module
|
26
|
+
module SP; end
|
27
|
+
|
28
|
+
class SunspotOperator < Operator
|
29
|
+
include De::SunspotSolr::SP
|
30
|
+
include De::SymmetricOperator
|
31
|
+
|
32
|
+
def initialize(operator, operands = nil)
|
33
|
+
@operator = operator
|
34
|
+
super("#{operator}-#{rand(1000)}", operands)
|
35
|
+
end
|
36
|
+
|
37
|
+
def evaluate
|
38
|
+
super
|
39
|
+
"#{@operator} do
|
40
|
+
#{children.map {|child| child.evaluate + "\n" } }
|
41
|
+
end"
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Adds operator or operand as a child in case equal one doesn't exist already
|
46
|
+
# otherwize old one is returned
|
47
|
+
#
|
48
|
+
# === Input
|
49
|
+
#
|
50
|
+
# obj<SP>:: object to be added as child one
|
51
|
+
#
|
52
|
+
# === Output
|
53
|
+
#
|
54
|
+
# <SP>:: child object
|
55
|
+
#
|
56
|
+
def <<(obj)
|
57
|
+
raise Error::TypeError unless obj.is_a?(SP)
|
58
|
+
children.include?(obj) ? children[children.index(obj)] : super(obj)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Class representing AND operator for SunspotSolr expression.
|
64
|
+
# In resulting Sunspot query it is reflected as all_of block
|
65
|
+
#
|
66
|
+
class And < SunspotOperator
|
67
|
+
|
68
|
+
#
|
69
|
+
# Creates And object.
|
70
|
+
# Name includes word 'all_of' with random number to avoid TreeNode problem
|
71
|
+
# when trying to add children with the same name to a node
|
72
|
+
#
|
73
|
+
# === Input
|
74
|
+
#
|
75
|
+
# operands<Array>:: (optional) array of Operand objects.
|
76
|
+
# If given they are added as children to current operator
|
77
|
+
#
|
78
|
+
def initialize(operands = nil)
|
79
|
+
super("all_of", operands)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Or < SunspotOperator
|
84
|
+
|
85
|
+
#
|
86
|
+
# Creates Or object.
|
87
|
+
# Name includes word 'any_of' with random number to avoid TreeNode problem
|
88
|
+
# when trying to add children with the same name to a node
|
89
|
+
#
|
90
|
+
# === Input
|
91
|
+
#
|
92
|
+
# operands<Array>:: (optional) array of Operand objects.
|
93
|
+
# If given they are added as children to current operator
|
94
|
+
#
|
95
|
+
def initialize(operands = nil)
|
96
|
+
super("any_of", operands)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Not < SunspotOperator
|
101
|
+
|
102
|
+
#
|
103
|
+
# Creates Or object.
|
104
|
+
# Name includes word 'any_of' with random number to avoid TreeNode problem
|
105
|
+
# when trying to add children with the same name to a node
|
106
|
+
#
|
107
|
+
# === Input
|
108
|
+
#
|
109
|
+
# operands<Array>:: (optional) array of Operand objects.
|
110
|
+
# If given they are added as children to current operator
|
111
|
+
#
|
112
|
+
def initialize(operand = nil)
|
113
|
+
super("not", operand ? [operand] : nil)
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Adds sunspot operand as a child to not operator
|
118
|
+
#
|
119
|
+
def <<(obj)
|
120
|
+
raise Error::TypeError unless obj.is_a?(De::SunspotSolr::SunspotOperand)
|
121
|
+
raise Error::ArgumentNumerError if has_children?
|
122
|
+
super(obj)
|
123
|
+
end
|
124
|
+
|
125
|
+
def evaluate
|
126
|
+
super
|
127
|
+
first_child.evaluate.gsub(/with\(/, 'without(')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
#
|
2
|
+
# De::SunspotSolr::Search class
|
3
|
+
#
|
4
|
+
# De::SunspotSolr module provides engine to build, validate and evaluate dynamic sunspot query to solr
|
5
|
+
# based on some model.
|
6
|
+
# It is built as extension of De module
|
7
|
+
#
|
8
|
+
# SunspotSolr expression example:
|
9
|
+
#
|
10
|
+
# Sunspot.search(Product) do
|
11
|
+
# any_of do
|
12
|
+
# with(:client_id).equal_to(43)
|
13
|
+
# with(:name).equal_to('Name to trace')
|
14
|
+
# end
|
15
|
+
# with(:update_time).greater_then('15/03/11')
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'de/symmetric_operator'
|
20
|
+
|
21
|
+
module De
|
22
|
+
module SunspotSolr
|
23
|
+
|
24
|
+
# Marker module included to all module classes
|
25
|
+
# to be able to check that class is inside module
|
26
|
+
module SP; end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Search class
|
30
|
+
#
|
31
|
+
# Expression extention representing sunspot search
|
32
|
+
# Its evaluation returns Sunspot Search object
|
33
|
+
# built on its operators and operands tree
|
34
|
+
#
|
35
|
+
class Search < Expression
|
36
|
+
include SP
|
37
|
+
include De::SymmetricOperator
|
38
|
+
|
39
|
+
attr_reader :klass, :options
|
40
|
+
|
41
|
+
#
|
42
|
+
# Constructor
|
43
|
+
# Stores object properties. Adds operands to expression tree
|
44
|
+
#
|
45
|
+
# === Input
|
46
|
+
#
|
47
|
+
# name<String>:: arbitrary object name
|
48
|
+
# klass<String>:: name of model search is built on
|
49
|
+
# options<Hash>:: options specifying model fields properties as they are registered in Solr
|
50
|
+
# Options are used by operands in order to build proper sunspot condition according to property type and static/dynamic nature
|
51
|
+
#
|
52
|
+
# Hash has following structure
|
53
|
+
# {
|
54
|
+
# :properties => { <property> => {:type => <type>, :dynamic => <dynamic>}, ... },
|
55
|
+
# :dynamics => { <type> => <dynamic property name>, ... }
|
56
|
+
# }
|
57
|
+
#
|
58
|
+
# <type> - type name (symbol)
|
59
|
+
# <dynamic> - true|false
|
60
|
+
# <dynamic property name> - dynamic solr field name (symbol)
|
61
|
+
#
|
62
|
+
# operands<Array>:: (optional) array of Operand objects.
|
63
|
+
# If given they are added as children to current Search object
|
64
|
+
#
|
65
|
+
# === Exmaple
|
66
|
+
# search = De::SunspotSolr::Search.new('search_product', 'Product', {
|
67
|
+
# :properties => {
|
68
|
+
# :client_id => {:type => :integer, :dynamic => false},
|
69
|
+
# :name => {:type => :string, :dynamic => false},
|
70
|
+
# :price => {:type => :integer, :dynamic => true}
|
71
|
+
# },
|
72
|
+
# :dynamics => {:integer => :int_params, :string => :string_params, :time => :time_params, :text => :string_params}
|
73
|
+
# })
|
74
|
+
#
|
75
|
+
def initialize(name, klass, options = {}, operands = nil)
|
76
|
+
super(name, nil)
|
77
|
+
@klass = klass
|
78
|
+
@options = options
|
79
|
+
|
80
|
+
unless operands.nil?
|
81
|
+
raise Error::TypeError unless operands.is_a?(Array)
|
82
|
+
operands.each { |operand| self << operand }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Adds operator or operand as a child to Search.
|
88
|
+
# Prevents addition of invalid type Object
|
89
|
+
#
|
90
|
+
# === Input
|
91
|
+
#
|
92
|
+
# obj<SunspotSolr::Operator|SunspotSolr::Operand>:: object to be added as child one
|
93
|
+
#
|
94
|
+
# === Output
|
95
|
+
#
|
96
|
+
# <SunspotSolr::Operator|SunspotSolr::Operand>:: child object
|
97
|
+
#
|
98
|
+
def <<(obj)
|
99
|
+
raise Error::TypeError unless (obj.is_a?(SP))
|
100
|
+
children.include?(obj) ? children[children.index(obj)] : super(obj)
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Equal override
|
105
|
+
# Checks objects class, +klass+ property and children equality. Children order is NOT important
|
106
|
+
#
|
107
|
+
# === Input
|
108
|
+
#
|
109
|
+
# obj<SunspotSolr::Search>:: object to compare with
|
110
|
+
#
|
111
|
+
def ==(obj)
|
112
|
+
obj.is_a?(Search) && obj.klass == @klass && ((children | obj.children) - (children & obj.children)).length == 0
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Intersection operator. Reterns new Search object representing intersection of results
|
117
|
+
# given by current and input search results
|
118
|
+
#
|
119
|
+
# === Input
|
120
|
+
#
|
121
|
+
# obj<Search>:: search to find intersection with
|
122
|
+
#
|
123
|
+
# === Output
|
124
|
+
#
|
125
|
+
# Search object
|
126
|
+
#
|
127
|
+
def &(obj)
|
128
|
+
Search.new("#{@name}+#{obj.name}", @klass, @options, [self.children, obj.children].flatten)
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Union operator. Reterns new Search object representing unin of results
|
133
|
+
# given by current and input search results
|
134
|
+
#
|
135
|
+
# === Input
|
136
|
+
#
|
137
|
+
# obj<Search>:: search to find union with
|
138
|
+
#
|
139
|
+
# === Output
|
140
|
+
#
|
141
|
+
# Search object
|
142
|
+
#
|
143
|
+
def |(obj)
|
144
|
+
expression_content = []
|
145
|
+
expression_content << SunspotSolr::And.new(self.children) if self.has_children?
|
146
|
+
expression_content << SunspotSolr::And.new(obj.children) if obj.has_children?
|
147
|
+
|
148
|
+
Search.new("#{@name}+#{obj.name}", @klass, @options, expression_content.length > 0 ? [SunspotSolr::Or.new(expression_content)] : nil)
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# Validator
|
153
|
+
#
|
154
|
+
# Expression tree is valid in case their children are valid if any
|
155
|
+
#
|
156
|
+
def valid?
|
157
|
+
children.inject(true) {|result, el| result && el.valid?}
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
# Evaluator
|
162
|
+
#
|
163
|
+
# === Output
|
164
|
+
#
|
165
|
+
# Sunspot Search object
|
166
|
+
# built on its current tree operators and operands
|
167
|
+
#
|
168
|
+
def evaluate
|
169
|
+
super
|
170
|
+
|
171
|
+
search_string = "Sunspot.search(#{Kernel.const_get(@klass)}) do
|
172
|
+
#{children.map {|child| child.evaluate + "\n" } }
|
173
|
+
end"
|
174
|
+
|
175
|
+
instance_eval search_string
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#
|
2
|
+
# Symmetric operator module defines properties for operator with unimportant order of children (operands)
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
module De
|
8
|
+
module SymmetricOperator
|
9
|
+
|
10
|
+
#
|
11
|
+
# Equal operator override
|
12
|
+
# Checks objects class and children equality. Operands order is NOT important
|
13
|
+
#
|
14
|
+
# === Input
|
15
|
+
#
|
16
|
+
# obj<Expression>:: object to compare with
|
17
|
+
#
|
18
|
+
def ==(obj)
|
19
|
+
self.class.name == obj.class.name && ((children | obj.children) - (children & obj.children)).length == 0
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Define hash function to get equal results for operators from the same class and with the equal children.
|
24
|
+
# Children order is not important
|
25
|
+
#
|
26
|
+
def hash
|
27
|
+
[self.class.name, Set.new(children.map { |el| el.hash})].hash
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/de/version.rb
ADDED
data/lib/de.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'de'
|
3
|
+
require 'de/boolean'
|
4
|
+
|
5
|
+
class DeBooleanAndTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_constructor
|
8
|
+
assert_nothing_raised(De::Error::AbstractClassObjectCreationError) { De::Boolean::And.new }
|
9
|
+
assert_nothing_raised(De::Error::AbstractClassObjectCreationError) { De::Boolean::And.new([De::Boolean::Operand.new('some name', true)]) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_add
|
13
|
+
and1 = De::Boolean::And.new
|
14
|
+
operand1 = De::Boolean::Operand.new('some name', true)
|
15
|
+
|
16
|
+
and1 << operand1
|
17
|
+
assert_equal(2, and1.size)
|
18
|
+
|
19
|
+
and2 = De::Boolean::And.new
|
20
|
+
and1 << and2
|
21
|
+
assert_equal(3, and1.size)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_evaluate
|
25
|
+
and1 = De::Boolean::And.new
|
26
|
+
operand_true1 = De::Boolean::Operand.new('some name', true)
|
27
|
+
operand_true2 = De::Boolean::Operand.new('second name', true)
|
28
|
+
operand_false1 = De::Boolean::Operand.new('third name', false)
|
29
|
+
operand_false2 = De::Boolean::Operand.new('fourth name', false)
|
30
|
+
|
31
|
+
and1 << operand_true1
|
32
|
+
and1 << operand_true2
|
33
|
+
assert_equal(true, and1.evaluate)
|
34
|
+
|
35
|
+
and2 = De::Boolean::And.new
|
36
|
+
and2 << operand_true1
|
37
|
+
and2 << operand_false1
|
38
|
+
assert_equal(false, and2.evaluate)
|
39
|
+
|
40
|
+
and3 = De::Boolean::And.new([operand_false1, operand_false2])
|
41
|
+
assert_equal(false, and3.evaluate)
|
42
|
+
|
43
|
+
and1 << and3
|
44
|
+
assert_equal(false, and1.evaluate)
|
45
|
+
|
46
|
+
and4 = De::Boolean::And.new([operand_true1, operand_true2])
|
47
|
+
and1 << and4
|
48
|
+
assert_equal(false, and1.evaluate)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|