Class: Spider::Model::Condition

Inherits:
Hash
  • Object
show all
Defined in:
lib/spiderfw/model/condition.rb

Overview

The Condition behaves like a ModelHash, and as such contains key-value pairs: a simple equality condition can be set with

condition[:element_name] = value

The Condition object also holds comparisons: a comparison different from equality can be set with

condition.set(:element_name, '>', value)

Finally, it contains subconditions, which can be added with

conditions << subcondition

Subconditions will be created automatically when using #set twice on the same element. If you want to change the condition, use #delete and set it again.

The Condition object, like the Request, doesn’t hold a reference to a model; so no check will be made that the conditions elements are meaningful.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*params, &proc) ⇒ Condition

Instantiates a new Condition, with :and conjunction. If given a Hash, will set all keys = values. If given multiple params, will convert each to a Condition if needed, and append them to the returned instance. If a block is given, it will be processed by #parse_block



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/spiderfw/model/condition.rb', line 84

def initialize(*params, &proc)
    @conjunction = :and
    @comparisons = {}
    @subconditions = []
    params.reject!{ |p| p.nil? }
    if (params.length == 1 && params[0].is_a?(Hash) && !params[0].is_a?(Condition))
        params[0].each do |k, v|
            set(k, '=', v)
        end
    else
        # FIXME: must have an instantiate method
        params.each{ |item| self << (item.is_a?(self.class) ? item : self.class.new(item)) } 
    end
    parse_block(&proc) if (block_given?)
end

Instance Attribute Details

#comparisonsObject (readonly)

An hash of comparisons for each element name



24
25
26
# File 'lib/spiderfw/model/condition.rb', line 24

def comparisons
  @comparisons
end

#conjunctObject

:nodoc: a hack to keep track of which is the last condition in blocks



27
28
29
# File 'lib/spiderfw/model/condition.rb', line 27

def conjunct
  @conjunct
end

#conjunctionObject

The top level conjunction for the Condition (:or or :and; new Conditions are initialized with :or)



20
21
22
# File 'lib/spiderfw/model/condition.rb', line 20

def conjunction
  @conjunction
end

#polymorphObject

Polymorph model: used to tell the mapper the condition is on a subclass of the queried model.



22
23
24
# File 'lib/spiderfw/model/condition.rb', line 22

def polymorph
  @polymorph
end

#subconditionsObject (readonly)

An Array of subconditions



26
27
28
# File 'lib/spiderfw/model/condition.rb', line 26

def subconditions
  @subconditions
end

Class Method Details

.and(*params, &proc) ⇒ Object

Instantiates a Condition with :and conjunction. See #new for arguments.



58
59
60
61
62
# File 'lib/spiderfw/model/condition.rb', line 58

def self.and(*params, &proc)
    c = self.new(*params, &proc)
    c.conjunction = :and
    return c
end

.comparison_operators_regexpObject

Regexp to parse comparison operators



44
45
46
# File 'lib/spiderfw/model/condition.rb', line 44

def self.comparison_operators_regexp # :nodoc:
    @comparison_operators_regexp
end

.conj(conjunction, a, b) ⇒ Object

Used by and and or methods



49
50
51
52
53
54
# File 'lib/spiderfw/model/condition.rb', line 49

def self.conj(conjunction, a, b) # :nodoc:
    c = Condition.new
    c.conjunction = conjunction
    c << a
    c << b
end

.no_conjunction(*params, &proc) ⇒ Object

Instantiates a Condition with no conjunction.



73
74
75
76
77
# File 'lib/spiderfw/model/condition.rb', line 73

def self.no_conjunction(*params, &proc) # :nodoc:
    c = self.new(*params, &proc)
    c.conjunction = nil
    return c
end

.or(*params, &proc) ⇒ Object

Instantiates a Condition with :or conjunction. See #new for arguments.



66
67
68
69
70
# File 'lib/spiderfw/model/condition.rb', line 66

def self.or(*params, &proc)
    c = self.new(*params, &proc)
    c.conjunction = :or
    return c
end

Instance Method Details

#+(condition) ⇒ Object

Returns the result of merging the condition with another one (does not modify the original condition).



149
150
151
152
153
154
155
156
# File 'lib/spiderfw/model/condition.rb', line 149

def +(condition)
    res = self.clone
    @subconditions += condition.subconditions
    condition.each_with_comparison do |k, v, c|
        res.set(k, v, c)
    end
    return res
end

#<<(condition) ⇒ Object

Adds a subcondtion.



159
160
161
162
163
164
165
166
167
168
# File 'lib/spiderfw/model/condition.rb', line 159

def <<(condition)
    if (condition.class == self.class)
        @subconditions << condition
    elsif (condition.is_a?(Hash))
        @subconditions << self.class.new(condition)
    elsif (condition.class == String)
        key, val, comparison = parse_comparison(condition)
        set(key, val, comparison)
    end
end

#==(other) ⇒ Object



322
323
324
325
326
327
328
329
330
# File 'lib/spiderfw/model/condition.rb', line 322

def ==(other)
    return false unless other.class == self.class
    return false unless super
    return false unless @subconditions == other.subconditions
    return false unless @comparisons == other.comparisons
    return false unless @polymorph == other.polymorph
    return false unless @conjunction == other.conjunction
    return true
end

#[](key) ⇒ Object



209
210
211
212
213
214
# File 'lib/spiderfw/model/condition.rb', line 209

def [](key)
    # TODO: deep
    key = key.name if key.is_a?(Element)
    key = key.to_sym if key.respond_to?(:to_sym) # might be a QueryFunc
    super(key)
end

#[]=(key, value) ⇒ Object

Sets an equality comparison.



205
206
207
# File 'lib/spiderfw/model/condition.rb', line 205

def []=(key, value)
    set(key, '=', value)
end

#all_each_with_comparisonObject

Yields each key, value and comparison, for this condition and its subconditions



141
142
143
144
145
146
# File 'lib/spiderfw/model/condition.rb', line 141

def all_each_with_comparison
    self.each_with_comparison{ |k, v, c| yield k, v, c }
    @subconditions.each do |sub|
        sub.all_each_with_comparison{ |k, v, c| yield k, v, c }
    end
end

#and(other = nil, &proc) ⇒ Object Also known as: &, AND

Joins the condition to another with an “and” conjunction. See #conj.



294
295
296
# File 'lib/spiderfw/model/condition.rb', line 294

def and(other=nil, &proc)
    return conj(:and, other, &proc)
end

#cloneObject

Returns a deep copy.



348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/spiderfw/model/condition.rb', line 348

def clone
    c = self.class.new
    c.conjunction = @conjunction
    c.polymorph = @polymorph
    self.each_with_comparison do |key, val, comparison|
        c.set(key, comparison, val)
    end
    @subconditions.each do |sub|
        c << sub.clone
    end
    return c
end

#conditions_arrayObject



125
126
127
128
129
130
# File 'lib/spiderfw/model/condition.rb', line 125

def conditions_array
    self.hash_clone.map do |k, v|
        k = k.to_sym if k.respond_to?(:to_sym)
        [k, v, (@comparisons[k] || '==')]
    end
end

#conditions_for(*element_names) ⇒ Object

Returns, from self and subconditions, all those who define a condition for one of the given element names.



378
379
380
381
382
383
384
385
386
387
388
# File 'lib/spiderfw/model/condition.rb', line 378

def conditions_for(*element_names)
    conds = []
    element_names.each do |el|
        if self.key?(el)
            conds << self
            break
        end
    end
    @subconditions.map{ |s| s.conditions_for(*element_names) }.each{ |c| conds += c }
    conds
end

#conj(conjunction, other = nil, &proc) ⇒ Object

Returns the conjunction with another condition. If this condition already has the required conjunction, the other will be added as a subcondition; otherwise, a new condition will be created and both will be added to it.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/spiderfw/model/condition.rb', line 268

def conj(conjunction, other=nil, &proc)
    self.conjunction = conjunction if (!self.conjunction)
    if (self.conjunction == conjunction)
        c = self
    else
        c = Condition.new
        c.conjunction = conjunction
        c << self
    end
    if (!other && proc)
        other = Condition.new(&proc)
    end
    c << other
    other.conjunct = true
    return c
end

#delete(field) ⇒ Object

Deletes a field from the Condition.



225
226
227
228
229
230
231
232
# File 'lib/spiderfw/model/condition.rb', line 225

def delete(field)
    field = field.to_sym
    return nil unless self[field] || @comparisons[field]
    cur = [self[field], @comparisons[field]]
    super
    @comparisons.delete(field)
    cur
end

#each_with_comparisonObject

Yields each key, value and comparison.



133
134
135
136
137
138
# File 'lib/spiderfw/model/condition.rb', line 133

def each_with_comparison
    self.each do |k, v|
        k = k.to_sym if k.respond_to?(:to_sym)
        yield k, v, @comparisons[k] || '='
    end
end

#empty?Boolean

True if there are no comparisons and no subconditions.

Returns:

  • (Boolean)


303
304
305
306
307
308
309
# File 'lib/spiderfw/model/condition.rb', line 303

def empty?
    return false unless super
    @subconditions.each do |sub|
        return false unless sub.empty?
    end
    return true
end

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


332
333
334
# File 'lib/spiderfw/model/condition.rb', line 332

def eql?(other)
    self == other
end

#get_deep_objObject

See #ModelHash.get_deep_obj



31
32
33
34
35
# File 'lib/spiderfw/model/condition.rb', line 31

def get_deep_obj # :nodoc:
    c = self.class.new
    c.conjunction = @conjunction
    return c
end

#hashObject



336
337
338
# File 'lib/spiderfw/model/condition.rb', line 336

def hash
    ([self.keys, self.values, @comparisons.values, @polymorph] + @subconditions.map{ |s| s.hash}).hash
end

#hash_cloneObject



345
# File 'lib/spiderfw/model/condition.rb', line 345

alias :hash_clone :clone

#hash_empty?Object

:nodoc:



300
# File 'lib/spiderfw/model/condition.rb', line 300

alias :hash_empty? :empty?

#hash_replaceObject

:nodoc:



311
# File 'lib/spiderfw/model/condition.rb', line 311

alias :hash_replace :replace

#hash_setObject

:nodoc:



28
# File 'lib/spiderfw/model/condition.rb', line 28

alias :hash_set :[]=

#inspectObject



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/spiderfw/model/condition.rb', line 245

def inspect
    str = ""
    cnt = 0
    each do |key, value|
        str += " #{@conjunction} " if cnt > 0
        cnt += 1
        comparison = @comparisons[key] || '='
        cond = "#{comparison} #{value.inspect}"
        str += "#{key} #{cond}"
    end
    str = '(' + str + ')' if str.length > 0
    #str += ' [raw:'+raw.inspect+']' unless raw.empty?
    first = true
    if @subconditions.length > 0
        str += ' '+@conjunction.to_s+' ' if str.length > 0
        str += @subconditions.map{ |sub| sub.inspect }.join(' '+@conjunction.to_s+' ')
    end
    return str
end

#or(other = nil, &proc) ⇒ Object Also known as: |, OR

Joins the condition to another with an “or” conjunction. See #conj.



287
288
289
# File 'lib/spiderfw/model/condition.rb', line 287

def or(other=nil, &proc)
    return conj(:or, other, &proc)
end

#parse_block(&proc) ⇒ Object

Parses a condition block. Inside the block, an SQL-like language can be used.

Example:

condition.parse_block{ (element1 == val1) & ( (element2 > 'some string') | (element3 .not nil) ) }

All comparisons must be parenthesized; and/or conjunctions are expressed with a single &/|.

Available comparisions are: ==, >, <, >=, <=, .not, .like, .ilike (case insensitive like).

Note: for .like and .ilike comparisons, the SQL ‘%’ syntax must be used.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/spiderfw/model/condition.rb', line 110

def parse_block(&proc)
    res = nil
    if proc.arity == 1
        res = proc.call(ConditionContext.new)
    else
        context = eval "self", proc.binding
        res = context.dup.extend(ConditionMixin).instance_eval(&proc)
    end
    self.replace(res)
    @conjunction = res.conjunction
    @comparisons = res.comparisons
    @subconditions = res.subconditions
    @polymorph = res.polymorph
end

#parse_comparison(comparison) ⇒ Object

Parses a string comparison. TODO: remove?



236
237
238
239
240
241
242
243
# File 'lib/spiderfw/model/condition.rb', line 236

def parse_comparison(comparison) # :nodoc:
    if (comparison =~ Regexp.new("(.+)(#{self.class.comparison_operators_regexp})(.+)"))
        val = $3.strip
        # strip single and double quotes
        val = val[1..-2] if ((val[0] == ?' && val[-1] == ?') || (val[0] == ?" && val[-1] == ?") )
        return [$1.strip, $2.strip, val]
    end
end

#polymorphsObject



371
372
373
374
375
# File 'lib/spiderfw/model/condition.rb', line 371

def polymorphs
    pol = []
    pol << @polymorph if @polymorph
    return pol + @subconditions.inject([]){ |arr, s| arr += s.polymorphs }
end

#range(field, lower, upper) ⇒ Object

Adds a range condition. This creates a subcondition with >= and <= conditions.



217
218
219
220
221
222
# File 'lib/spiderfw/model/condition.rb', line 217

def range(field, lower, upper)
    c = self.class.and
    c.set(field, '>=', lower)
    c.set(field, '<=', upper)
    self << c
end

#replace(other) ⇒ Object

Replace the content of this Condition with another one.



314
315
316
317
318
319
320
# File 'lib/spiderfw/model/condition.rb', line 314

def replace(other)
    hash_replace(other)
    @subconditions = other.subconditions
    @conjunction = other.conjunction
    @polymorph = other.polymorph
    @comparisons = other.comparisons
end

#set(field, comparison, value) ⇒ Object

Sets a comparison.



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/spiderfw/model/condition.rb', line 171

def set(field, comparison, value)
    if value.is_a?(QuerySet)
        value = value.to_a
    end
    if value.is_a?(Array) && comparison != 'between'
        or_cond = self.class.or
        value.uniq.each do |v|
            or_cond.set(field, comparison, v)
        end
        @subconditions << or_cond
        return self
    end
    parts = []
    unless field.is_a?(Spider::QueryFuncs::Function)
        field = field.to_s
        parts = field.split('.', 2)
        parts[0] = parts[0].to_sym
        field = field.to_sym unless parts[1]
    end
    if (parts[1])
        hash_set(parts[0], get_deep_obj()) unless self[parts[0]]
        self[parts[0]].set(parts[1], comparison, value)
    elsif (self[field])
        c = Condition.new
        c.set(field, comparison, value)
        @subconditions << c
    else
        hash_set(field, value)
        @comparisons[field] = comparison
    end
    return self
end

#simplifyObject

Traverses the tree removing useless conditions.



362
363
364
365
366
367
368
369
# File 'lib/spiderfw/model/condition.rb', line 362

def simplify
    @subconditions.each{ |sub| sub.simplify }
    if (hash_empty? && @subconditions.length == 1)
        self.replace(@subconditions[0])
    end
    @subconditions.uniq!
    return self
end

#uniq!Object

Removes duplicate subcondtions.



341
342
343
# File 'lib/spiderfw/model/condition.rb', line 341

def uniq!
    @subconditions.uniq!
end