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.

Conditions also support an SQL-like block syntax for setting conditions:

books_condition = Condition.new{ |book| 
 ((book.year < 1970) | (book.price > 1000)) & (book.author.name .like 'John')  
}

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



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

def initialize(*params, &proc)
    @conjunction = :and
    @comparisons = {}
    @subconditions = []
    params.reject!{ |p| p.nil? }
    if params[0].is_a?(Proc)
        params[0] = params[0].call
    end
    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

#comparisonsHash (readonly)

An hash of comparisons for each element name

Returns:

  • (Hash)


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

def comparisons
  @comparisons
end

#conjunctCondition

A pointer to the last Condition used in condition blocks

Returns:



39
40
41
# File 'lib/spiderfw/model/condition.rb', line 39

def conjunct
  @conjunct
end

#conjunctionSymbol

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

Returns:



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

def conjunction
  @conjunction
end

#polymorphClass<BaseModel]

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

Returns:



29
30
31
# File 'lib/spiderfw/model/condition.rb', line 29

def polymorph
  @polymorph
end

#subconditionsArray (readonly)

An Array of subconditions

Returns:

  • (Array)


35
36
37
# File 'lib/spiderfw/model/condition.rb', line 35

def subconditions
  @subconditions
end

Class Method Details

.and(*params, &proc) ⇒ Condition

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

Returns:



79
80
81
82
83
# File 'lib/spiderfw/model/condition.rb', line 79

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

.comparison_operators_regexpRegexp

Regexp to parse comparison operators

Returns:

  • (Regexp)


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

def self.comparison_operators_regexp
    @comparison_operators_regexp
end

.conj(conjunction, a, b) ⇒ Object

Used by and and or methods

Parameters:



69
70
71
72
73
74
# File 'lib/spiderfw/model/condition.rb', line 69

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

.no_conjunction(*params, &proc) ⇒ Condition

Instantiates a Condition with no conjunction. See Condition.new for arguments.

Returns:



97
98
99
100
101
# File 'lib/spiderfw/model/condition.rb', line 97

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

.or(*params, &proc) ⇒ Condition

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

Returns:



88
89
90
91
92
# File 'lib/spiderfw/model/condition.rb', line 88

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

Instance Method Details

#+(condition) ⇒ Condition

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

Parameters:

Returns:



189
190
191
192
193
194
195
196
# File 'lib/spiderfw/model/condition.rb', line 189

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) ⇒ void

This method returns an undefined value.

Adds a subcondtion.

Parameters:



201
202
203
204
205
206
207
208
209
210
# File 'lib/spiderfw/model/condition.rb', line 201

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

#==(other) ⇒ bool

Returns True if the two conditions have the same comparisions and conjunction.

Parameters:

Returns:

  • (bool)

    True if the two conditions have the same comparisions and conjunction



389
390
391
392
393
394
395
396
397
# File 'lib/spiderfw/model/condition.rb', line 389

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

Gets the value of a condition

Parameters:

Returns:



263
264
265
266
267
268
# File 'lib/spiderfw/model/condition.rb', line 263

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) ⇒ self

Sets an equality comparison.

Equivalent to #set(key, ‘=’, value)

Parameters:

Returns:

  • (self)


256
257
258
# File 'lib/spiderfw/model/condition.rb', line 256

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

#all_each_with_comparisonvoid

This method returns an undefined value.

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



173
174
175
176
177
178
# File 'lib/spiderfw/model/condition.rb', line 173

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) ⇒ Condition Also known as: &, AND

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

Parameters:

  • other (Condition) (defaults to: nil)

    Condition to conjuct to this

  • proc (Proc)

    Block to create the other condition

Returns:

  • (Condition)

    The resulting Condition (self, or the new Condition created)



354
355
356
# File 'lib/spiderfw/model/condition.rb', line 354

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

#cloneCondition

Returns a deep copy.

Returns:



421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/spiderfw/model/condition.rb', line 421

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_arrayArray

Returns An array of all conditions, expressed as [key, value, comparison].

Returns:

  • (Array)

    An array of all conditions, expressed as [key, value, comparison]



155
156
157
158
159
160
# File 'lib/spiderfw/model/condition.rb', line 155

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) ⇒ Array

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

Parameters:

  • element_names (*Symbol)

    A list of element names

Returns:

  • (Array)

    An array of matching conditions



455
456
457
458
459
460
461
462
463
464
465
# File 'lib/spiderfw/model/condition.rb', line 455

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) ⇒ Condition

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.

Parameters:

  • conjunction (Symbol)

    :and | :or

  • other (Condition) (defaults to: nil)

    Condition to conjuct to this

  • proc (Proc)

    Block to create the other condition

Returns:

  • (Condition)

    The resulting Condition (self, or the new Condition created)



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/spiderfw/model/condition.rb', line 322

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) ⇒ Array

Deletes a field from the Condition.

Parameters:

Returns:

  • (Array)

    A pair containing the deleted value and comparison



285
286
287
288
289
290
291
292
# File 'lib/spiderfw/model/condition.rb', line 285

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.

Returns:

  • void



164
165
166
167
168
169
# File 'lib/spiderfw/model/condition.rb', line 164

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?bool

True if there are no comparisons and no subconditions.

Returns:

  • (bool)

    True if the Condition is empty, false otherwise



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

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

#eql?(other) ⇒ bool

Returns See #==.

Returns:

  • (bool)

    See #==



400
401
402
# File 'lib/spiderfw/model/condition.rb', line 400

def eql?(other)
    self == other
end

#get_deep_objCondition

See #ModelHash.get_deep_obj

Returns:



45
46
47
48
49
# File 'lib/spiderfw/model/condition.rb', line 45

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

#hashString

Returns Keying hash.

Returns:



405
406
407
# File 'lib/spiderfw/model/condition.rb', line 405

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

#hash_cloneObject

Alias for Hash#clone



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

alias :hash_clone :clone

#hash_empty?Object

:nodoc:



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

alias :hash_empty? :empty?

#hash_replaceObject

Alias to the original Hash#replace



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

alias :hash_replace :replace

#hash_setObject

Original hash value assignment



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

alias :hash_set :[]=

#inspectString

Returns A String representation of the condition.

Returns:

  • (String)

    A String representation of the condition



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/spiderfw/model/condition.rb', line 295

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.inspect} #{cond}"
    end
    #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 }.reject{ |sub| sub.empty? }.join(' '+@conjunction.to_s+' ')
    end
    str = "(#{str})" if cnt + @subconditions.length > 1
    return str
end

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

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

Parameters:

  • other (Condition) (defaults to: nil)

    Condition to conjuct to this

  • proc (Proc)

    Block to create the other condition

Returns:

  • (Condition)

    The resulting Condition (self, or the new Condition created)



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

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.

Parameters:

  • proc (Proc)

    The condition block



139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/spiderfw/model/condition.rb', line 139

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

#polymorphsArray

Returns An array of polymorphic conditions.

Returns:

  • (Array)

    An array of polymorphic conditions



446
447
448
449
450
# File 'lib/spiderfw/model/condition.rb', line 446

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

#primary_keys_only?(model) ⇒ bool

Returns True if the condition has only primary keys for the given model.

Parameters:

  • Class<BaseModel] (Class<BaseModel] The model on which the Condition is based on.)

    The model on which the Condition is based on.

Returns:

  • (bool)

    True if the condition has only primary keys for the given model



182
183
184
# File 'lib/spiderfw/model/condition.rb', line 182

def primary_keys_only?(model)
    self.select{ |key, value| !model.elements[key] || !model.elements[key].primary_key? }.empty?
end

#range(field, lower, upper) ⇒ self

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

Parameters:

Returns:

  • (self)


275
276
277
278
279
280
# File 'lib/spiderfw/model/condition.rb', line 275

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

#replace(other) ⇒ self

Replace all the content of this Condition with another one.

Parameters:

Returns:

  • (self)


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

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

#set(field, comparison, value) ⇒ self

Adds a single condition for an element

Parameters:

Returns:

  • (self)


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/spiderfw/model/condition.rb', line 217

def set(field, comparison, value)
    if value.is_a?(QuerySet)
        value = value.to_a
    end
    if value.is_a?(Array) && comparison != 'between'
        multi_cond = comparison == '<>' ? self.class.and : self.class.or
        value.uniq.each do |v|
            multi_cond.set(field, comparison, v)
        end
        @subconditions << multi_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

#simplifyself

Traverses the tree removing useless conditions.

Returns:

  • (self)


436
437
438
439
440
441
442
443
# File 'lib/spiderfw/model/condition.rb', line 436

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.

Returns:

  • self



411
412
413
414
# File 'lib/spiderfw/model/condition.rb', line 411

def uniq!
    @subconditions.uniq!
    self
end