Class: SmartTuple

Inherits:
Object
  • Object
show all
Defined in:
lib/smart_tuple.rb

Overview

SQL condition builder.

tup = SmartTuple.new(" AND ")
tup << {:brand => "Nokia"}
tup << ["min_price >= ?", 75]
tup.compile   # => ["brand = ? AND min_price >= ?", "Nokia", 75]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(glue, attrs = {}) ⇒ SmartTuple

Initializer.

new(" AND ")
new(" OR ", :brackets => true)


26
27
28
29
30
# File 'lib/smart_tuple.rb', line 26

def initialize(glue, attrs = {})
  @glue = glue
  clear
  attrs.each {|k, v| send("#{k}=", v)}
end

Instance Attribute Details

#argsObject (readonly)

Array of SQL argument parts.



9
10
11
# File 'lib/smart_tuple.rb', line 9

def args
  @args
end

#bracketsObject

Put brackets around statements. true, false or :auto. Default:

:auto


14
15
16
# File 'lib/smart_tuple.rb', line 14

def brackets
  @brackets
end

#glueObject

String to glue statements together.



17
18
19
# File 'lib/smart_tuple.rb', line 17

def glue
  @glue
end

#statementsObject (readonly)

Array of SQL statement parts.



20
21
22
# File 'lib/smart_tuple.rb', line 20

def statements
  @statements
end

Instance Method Details

#+(arg) ⇒ Object

Add a statement, return new object. See #<<.

SmartTuple.new(" AND ") + {:brand => "Nokia"} + ["max_price <= ?", 300]


41
42
43
44
# File 'lib/smart_tuple.rb', line 41

def +(arg)
  # Since #<< supports chaining, it boils down to this.
  dup << arg
end

#<<(arg) ⇒ Object

Add a statement, return self.

# Array.
tup << ["brand = ?", "Nokia"]
tup << ["brand = ? AND color = ?", "Nokia", "Black"]

# Hash.
tup << {:brand => "Nokia"}
tup << {:brand => "Nokia", :color => "Black"}

# Another SmartTuple.
tup << other_tuple

# String. Generally NOT recommended.
tup << "min_price >= 75"

Adding anything empty or blank (where appropriate) has no effect on the receiver:

tup << nil
tup << []
tup << {}
tup << another_empty_tuple
tup << ""
tup << "  "     # Will be treated as blank if ActiveSupport is on.


70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/smart_tuple.rb', line 70

def <<(arg)
  # NOTE: Autobracketing is placing `[value]` instead of `value` into `@statements`. #compile understands it.

  # Chop off everything empty first time.
  if arg.nil? or (arg.empty? rescue false) or (arg.blank? rescue false)
  elsif arg.is_a? String or (arg.acts_like? :string rescue false)
    @statements << arg.to_s
  elsif arg.is_a? Array
    # NOTE: If arg == [], the execution won't get here.
    #       So, we've got at least one element. Therefore stmt will be scalar, and args -- DEFINITELY an array.
    stmt, args = arg[0], arg[1..-1]
    if not (stmt.nil? or (stmt.empty? rescue false) or (stmt.blank? rescue false))
      # Help do autobracketing later. Here we can ONLY judge by number of passed arguments.
      @statements << (args.size > 1 ? [stmt] : stmt)
      @args += args
    end
  elsif arg.is_a? Hash
    arg.each do |k, v|
      if v.nil?
        # NOTE: AR supports it for Hashes only. ["kk = ?", nil] will not be converted.
        @statements << "#{k} IS NULL"
      else
        @statements << "#{k} = ?"
        @args << v
      end
    end
  elsif arg.is_a? self.class
    # NOTE: If arg is empty, the execution won't get here.

    # Autobrackets here are smarter, than in Array processing case.
    stmt = arg.compile[0]
    @statements << ((arg.size > 1 or arg.args.size > 1) ? [stmt] : stmt)
    @args += arg.args
  else
    raise ArgumentError, "Invalid statement #{arg.inspect}"
  end

  # Return self, it's IMPORTANT to make chaining possible.
  self
end

#add_each(collection, &block) ⇒ Object

Iterate over collection and add block’s result to self once per each record.

add_each(brands) do |v|
  ["brand = ?", v]
end

Can be conditional:

tup.add_each(["Nokia", "Motorola"]) do |v|
  ["brand = ?", v] if v =~ /^Moto/
end

Raises:

  • (ArgumentError)


122
123
124
125
126
127
128
129
130
131
# File 'lib/smart_tuple.rb', line 122

def add_each(collection, &block)
  raise ArgumentError, "Code block expected" if not block

  collection.each do |v|
    self << yield(v)
  end

  # This is IMPORTANT.
  self
end

#clearObject

Clear self.



144
145
146
147
148
149
150
151
# File 'lib/smart_tuple.rb', line 144

def clear
  @statements = []
  @args = []
  @brackets = :auto

  # `Array` does it like this. We do either.
  self
end

#compileObject Also known as: to_a

Compile self into an array. Empty self yields empty array.

compile   # => []
compile   # => ["brand = ? AND min_price >= ?", "Nokia", 75]


157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/smart_tuple.rb', line 157

def compile
  return [] if empty?

  # Build "bracketed" statements.
  bsta = @statements.map do |s|
    auto_brackets, scalar_s = s.is_a?(Array) ? [true, s[0]] : [false, s]

    # Logic:
    #   brackets  | auto  | result
    #   ----------|-------|-------
    #   true      | *     | true
    #   false     | *     | false
    #   :auto     | true  | true
    #   :auto     | false | false

    brackets = if @statements.size < 2
      # If there are no neighboring statements, there WILL BE NO brackets in any case.
      false
    elsif @brackets == true or @brackets == false
      @brackets
    elsif @brackets == :auto
      auto_brackets
    else
      raise "Unknown @brackets value #{@brackets.inspect}, SE"
    end

    if brackets
      ["(", scalar_s, ")"].join
    else
      scalar_s
    end
  end

  [bsta.join(glue)] + @args
end

#empty?Boolean

Return true if self is empty.

tup = SmartTuple.new(" AND ")
tup.empty?    # => true

Returns:

  • (Boolean)


198
199
200
# File 'lib/smart_tuple.rb', line 198

def empty?
  @statements.empty?
end

#initialize_copy(src) ⇒ Object

Service initializer for dup.



33
34
35
36
# File 'lib/smart_tuple.rb', line 33

def initialize_copy(src)    #:nodoc:
  @statements = src.statements.dup
  @args = src.args.dup
end

#sizeObject

Get number of sub-statements.



203
204
205
# File 'lib/smart_tuple.rb', line 203

def size
  @statements.size
end