Class: BazaModels::Query

Inherits:
Object
  • Object
show all
Includes:
Pagination
Defined in:
lib/baza_models/query.rb

Defined Under Namespace

Modules: Pagination Classes: Inspector, Not, SqlGenerator

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Pagination

#current_page, #out_of_bounds?, #page, #paginated?, #per, #total_entries, #total_pages

Constructor Details

#initialize(args) ⇒ Query

Returns a new instance of Query.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/baza_models/query.rb', line 10

def initialize(args)
  @args = args
  @model = @args[:model]
  @db = @model.db

  raise "No database?" unless @db

  @selects = args[:selects] || []
  @wheres = args[:wheres] || []
  @includes = args[:includes] || []
  @joins = args[:joins] || []
  @groups = args[:groups] || []
  @offset = args[:offset]
  @orders = args[:orders] || []
  @page = args[:page]
  @per = args[:per]
  @previous_model = args[:previous_model]
  @limit = args[:limit]

  @joins_tracker = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &blk) ⇒ Object (private)



480
481
482
483
484
485
486
487
488
# File 'lib/baza_models/query.rb', line 480

def method_missing(method_name, *args, &blk)
  return super unless @model

  scopes = @model.instance_variable_get(:@scopes)
  return super if !scopes || !scopes.key?(method_name)

  block = scopes.fetch(method_name).fetch(:blk)
  instance_exec(*args, &block)
end

Instance Attribute Details

#previous_modelObject

Returns the value of attribute previous_model.



8
9
10
# File 'lib/baza_models/query.rb', line 8

def previous_model
  @previous_model
end

#relationObject

Returns the value of attribute relation.



8
9
10
# File 'lib/baza_models/query.rb', line 8

def relation
  @relation
end

Instance Method Details

#<<(model) ⇒ Object



355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/baza_models/query.rb', line 355

def <<(model)
  raise "No previous model set" unless @previous_model
  raise "No relation" unless @relation

  if model.persisted?
    model.update_attributes!(@relation.fetch(:foreign_key) => @previous_model.id)
  else
    autoloaded_cache_or_create << model
  end

  self
end

#<=(_other) ⇒ Object



373
374
375
# File 'lib/baza_models/query.rb', line 373

def <=(_other)
  false
end

#accessible_by(ability, action = :index) ⇒ Object

CanCan supports



369
370
371
# File 'lib/baza_models/query.rb', line 369

def accessible_by(ability, action = :index)
  ability.model_adapter(self, action).database_records
end

#allObject



32
33
34
# File 'lib/baza_models/query.rb', line 32

def all
  self
end

#any?Boolean

Returns:

  • (Boolean)


36
37
38
39
40
41
42
# File 'lib/baza_models/query.rb', line 36

def any?
  if @db.query(clone.select(:id).limit(1).to_sql).fetch
    true
  else
    false
  end
end

#average(column_name) ⇒ Object



44
45
46
47
# File 'lib/baza_models/query.rb', line 44

def average(column_name)
  query = select("AVG(#{table_sql}.#{column_sql(column_name)}) AS average")
  @db.query(query.to_sql).fetch.fetch(:average).to_f
end

#countObject



71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/baza_models/query.rb', line 71

def count
  if @previous_model && @previous_model.new_record?
    autoloaded_cache_or_create.length
  else
    query = clone(selects: [])
      .select("COUNT(*) AS count")
      .limit(nil)
      .offset(nil)

    @db.query(query.to_sql).fetch.fetch(:count)
  end
end

#destroy_allObject



343
344
345
# File 'lib/baza_models/query.rb', line 343

def destroy_all
  each(&:destroy!)
end

#eachObject



303
304
305
306
307
# File 'lib/baza_models/query.rb', line 303

def each
  to_enum.each do |model|
    yield model
  end
end

#empty?Boolean

Returns:

  • (Boolean)


49
50
51
# File 'lib/baza_models/query.rb', line 49

def empty?
  !any?
end

#find(id) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/baza_models/query.rb', line 128

def find(id)
  model = clone.where(id: id).limit(1).to_enum.first

  if model
    model.__send__(:fire_callbacks, :after_find)
  else
    raise BazaModels::Errors::RecordNotFound
  end

  model
end

#find_by(args) ⇒ Object



140
141
142
# File 'lib/baza_models/query.rb', line 140

def find_by(args)
  clone.where(args).limit(1).to_enum.first
end

#find_eachObject



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/baza_models/query.rb', line 309

def find_each
  query = clone
  query.instance_variable_set(:@order, [])
  query.instance_variable_set(:@limit, nil)
  query = query.order(:id)

  offset = 0

  loop do
    query = query.offset(offset, 1000)
    offset += 1000

    count = 0
    query.each do |model|
      yield model
      count += 1
    end

    break if count == 0
  end
end

#find_first(args) ⇒ Object



331
332
333
# File 'lib/baza_models/query.rb', line 331

def find_first(args)
  clone.where(args).first
end

#firstObject



144
145
146
147
148
149
150
151
152
153
# File 'lib/baza_models/query.rb', line 144

def first
  return autoloaded_cache.first if should_use_autoload?

  query = clone.limit(1)

  orders = query.instance_variable_get(:@orders)
  query = query.order(:id) if orders.empty?

  query.to_enum.first
end

#group(name) ⇒ Object



247
248
249
250
251
252
253
254
255
# File 'lib/baza_models/query.rb', line 247

def group(name)
  if name.is_a?(Symbol)
    clone(groups: @groups + ["`#{@model.table_name}`.`#{name}`"])
  elsif name.is_a?(String)
    clone(groups: @groups + [name])
  else
    raise "Didn't know how to group by that argument: #{name}"
  end
end

#idsObject



53
54
55
# File 'lib/baza_models/query.rb', line 53

def ids
  pluck(:id)
end

#includes(*names) ⇒ Object



184
185
186
# File 'lib/baza_models/query.rb', line 184

def includes(*names)
  clone(includes: @includes + names)
end

#inspectObject



351
352
353
# File 'lib/baza_models/query.rb', line 351

def inspect
  to_s
end

#joins(*arguments) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
# File 'lib/baza_models/query.rb', line 235

def joins(*arguments)
  BazaModels::Query::Inspector.new(
    query: self,
    model: @model,
    argument: arguments,
    joins: @joins,
    joins_tracker: @joins_tracker
  ).execute

  self
end

#lastObject



155
156
157
158
159
160
161
162
163
164
# File 'lib/baza_models/query.rb', line 155

def last
  return autoloaded_cache.last if should_use_autoload?

  query = clone.limit(1)

  orders = query.instance_variable_get(:@orders)
  query = query.order(:id) if orders.empty?

  query.reverse_order.to_enum.first
end

#lengthObject



84
85
86
87
88
89
90
# File 'lib/baza_models/query.rb', line 84

def length
  if @previous_model && !any_wheres_other_than_relation? && @previous_model.autoloads[@relation.fetch(:relation_name)]
    @previous_model.autoloads[@relation.fetch(:relation_name)].length
  else
    count
  end
end

#limit(limit) ⇒ Object



180
181
182
# File 'lib/baza_models/query.rb', line 180

def limit(limit)
  clone(limit: limit)
end

#map(&blk) ⇒ Object



272
273
274
# File 'lib/baza_models/query.rb', line 272

def map(&blk)
  to_enum.map(&blk)
end

#maximum(column_name) ⇒ Object



57
58
59
60
# File 'lib/baza_models/query.rb', line 57

def maximum(column_name)
  query = select("MAX(#{table_sql}.#{column_sql(column_name)}) AS maximum")
  @db.query(query.to_sql).fetch.fetch(:maximum).to_f
end

#minimum(column_name) ⇒ Object



62
63
64
65
# File 'lib/baza_models/query.rb', line 62

def minimum(column_name)
  query = select("MIN(#{table_sql}.#{column_sql(column_name)}) AS minimum")
  @db.query(query.to_sql).fetch.fetch(:minimum).to_f
end

#new(attributes) ⇒ Object



117
118
119
120
121
122
123
124
125
126
# File 'lib/baza_models/query.rb', line 117

def new(attributes)
  raise "No previous model" unless @previous_model
  raise "No relation" unless @relation

  new_sub_model = @model.new(@relation.fetch(:foreign_key) => @previous_model.id)
  new_sub_model.assign_attributes(attributes)
  autoloaded_cache_or_create << new_sub_model

  new_sub_model
end

#none?Boolean

Returns:

  • (Boolean)


67
68
69
# File 'lib/baza_models/query.rb', line 67

def none?
  !any?
end

#offset(offset) ⇒ Object



176
177
178
# File 'lib/baza_models/query.rb', line 176

def offset(offset)
  clone(offset: offset)
end

#order(name) ⇒ Object



257
258
259
260
261
262
263
264
265
# File 'lib/baza_models/query.rb', line 257

def order(name)
  if name.is_a?(Symbol)
    clone(orders: @orders + ["`#{@model.table_name}`.`#{name}`"])
  elsif name.is_a?(String)
    clone(orders: @orders + [name])
  else
    raise "Didn't know how to order by that argument: #{name}"
  end
end

#pluck(*column_names) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/baza_models/query.rb', line 97

def pluck(*column_names)
  results = @db.query(select(column_names).to_sql).to_a
  results.map do |result|
    if column_names.length == 1
      result.fetch(column_names.first)
    else
      new_result = []
      column_names.each do |column_name|
        new_result << result.fetch(column_name)
      end
      new_result
    end
  end
end

#ransack(params, args = {}) ⇒ Object



382
383
384
# File 'lib/baza_models/query.rb', line 382

def ransack(params, args = {})
  BazaModels::Ransacker.new(class: @model, params: params, query: self, args: args)
end

#reverse_orderObject



267
268
269
270
# File 'lib/baza_models/query.rb', line 267

def reverse_order
  @reverse_order = true
  self
end

#sanitize_sql(value) ⇒ Object



377
378
379
380
# File 'lib/baza_models/query.rb', line 377

def sanitize_sql(value)
  return value if value.is_a?(Array) || value.is_a?(Integer) || value.is_a?(Integer)
  "'#{@db.esc(value)}'"
end

#select(select = nil, &blk) ⇒ Object



166
167
168
169
170
171
172
173
174
# File 'lib/baza_models/query.rb', line 166

def select(select = nil, &blk)
  if !select && blk
    to_enum.select(&blk)
  elsif select.is_a?(Symbol)
    clone(selects: @selects + ["`#{@model.table_name}`.`#{select}`"])
  else
    clone(selects: @selects + [select])
  end
end

#sizeObject



92
93
94
95
# File 'lib/baza_models/query.rb', line 92

def size
  # TODO: This should also take counter caching into account
  length
end

#sum(column_name) ⇒ Object



112
113
114
115
# File 'lib/baza_models/query.rb', line 112

def sum(column_name)
  query = select("SUM(#{table_sql}.#{column_sql(column_name)}) AS sum")
  @db.query(query.to_sql).fetch.fetch(:sum).to_f
end

#to_aObject



335
336
337
# File 'lib/baza_models/query.rb', line 335

def to_a
  to_enum.to_a
end

#to_enumObject



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/baza_models/query.rb', line 276

def to_enum
  return autoloaded_cache if should_use_autoload?

  array_enum = ArrayEnumerator.new do |yielder|
    @db.query(to_sql).each do |data|
      yielder << @model.new(data, init: true)
    end
  end

  if @includes.empty?
    return array_enum
  else
    array = array_enum.to_a

    if @includes.any? && array.any?
      autoloader = BazaModels::Autoloader.new(
        models: array,
        autoloads: @includes,
        db: @db
      )
      autoloader.autoload
    end

    return array
  end
end

#to_sObject



347
348
349
# File 'lib/baza_models/query.rb', line 347

def to_s
  "#<BazaModels::Query class=#{@model.name} wheres=#{@wheres}>"
end

#to_sqlObject



339
340
341
# File 'lib/baza_models/query.rb', line 339

def to_sql
  BazaModels::Query::SqlGenerator.new(query: self).to_sql
end

#where(*args) ⇒ Object



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/baza_models/query.rb', line 188

def where(*args)
  first_arg = args.first
  new_wheres = @wheres.dup

  if first_arg.is_a?(String)
    new_where = "(#{args.shift})"

    args.each do |arg|
      new_where.sub!("?", @db.quote_value(arg))
    end

    new_wheres << new_where
  elsif first_arg.is_a?(Array)
    str = first_arg.shift

    first_arg.each do |arg|
      if arg.is_a?(Symbol)
        arg = "`#{@model.table_name}`.`#{@db.escape_column(arg)}`"
      elsif arg.is_a?(FalseClass)
        arg = "0"
      elsif arg.is_a?(TrueClass)
        arg = "1"
      else
        arg = @db.quote_value(arg)
      end

      str.sub!("?", arg)
    end

    new_wheres << "(#{str})"
  elsif first_arg == nil
    return Not.new(query: self)
  else
    first_arg.each do |key, value|
      if value.is_a?(Hash)
        value.each do |hash_key, hash_value|
          new_wheres << "`#{key}`.`#{key_convert(hash_key, hash_value)}` #{value_with_mode(value_convert(hash_value))}"
        end
      else
        new_wheres << "`#{@model.table_name}`.`#{key_convert(key, value)}` #{value_with_mode(value_convert(value))}"
      end
    end
  end

  clone(wheres: new_wheres)
end