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)



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

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



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

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



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

def <=(_other)
  false
end

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

CanCan supports



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

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
83
# File 'lib/baza_models/query.rb', line 71

def count
  if
@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



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

def destroy_all
  each(&:destroy!)
end

#each(&blk) ⇒ Object



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

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

#empty?Boolean

Returns:

  • (Boolean)


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

def empty?
  !any?
end

#find(id) ⇒ Object



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

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



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

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

#find_eachObject



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

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



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

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

#firstObject



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

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



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

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



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

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

#inspectObject



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

def inspect
  to_s
end

#joins(*arguments) ⇒ Object



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

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

  self
end

#lastObject



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

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



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

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



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

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

#map(&blk) ⇒ Object



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

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



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

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



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

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

#order(name) ⇒ Object



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

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



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

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



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

def reverse_order
  @reverse_order = true
  self
end

#sanitize_sql(value) ⇒ Object



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

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



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

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



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

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

#sum(column_name) ⇒ Object



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

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



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

def to_a
  to_enum.to_a
end

#to_enumObject



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
302
# File 'lib/baza_models/query.rb', line 277

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

    array
  end
end

#to_sObject



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

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

#to_sqlObject



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

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

#where(*args) ⇒ Object



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
234
# File 'lib/baza_models/query.rb', line 189

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