de 0.0.1
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 +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
|