class BazaModels::Query

Attributes

previous_model[RW]
relation[RW]

Public Class Methods

new(args) click to toggle source
# 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

Public Instance Methods

<<(model) click to toggle source
# 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) click to toggle source
# File lib/baza_models/query.rb, line 372
def <=(_other)
  false
end
accessible_by(ability, action = :index) click to toggle source

CanCan supports

# File lib/baza_models/query.rb, line 368
def accessible_by(ability, action = :index)
  ability.model_adapter(self, action).database_records
end
all() click to toggle source
# File lib/baza_models/query.rb, line 32
def all
  self
end
any?() click to toggle source
# 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) click to toggle source
# 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
count() click to toggle source
# 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_all() click to toggle source
# File lib/baza_models/query.rb, line 342
def destroy_all
  each(&:destroy!)
end
each(&blk) click to toggle source
# File lib/baza_models/query.rb, line 304
def each(&blk)
  to_enum.each(&blk)
end
empty?() click to toggle source
# File lib/baza_models/query.rb, line 49
def empty?
  !any?
end
find(id) click to toggle source
# 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) click to toggle source
# File lib/baza_models/query.rb, line 141
def find_by(args)
  clone.where(args).limit(1).to_enum.first
end
find_each() { |model| ... } click to toggle source
# 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) click to toggle source
# File lib/baza_models/query.rb, line 330
def find_first(args)
  clone.where(args).first
end
first() click to toggle source
# 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) click to toggle source
# 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
ids() click to toggle source
# File lib/baza_models/query.rb, line 53
def ids
  pluck(:id)
end
includes(*names) click to toggle source
# File lib/baza_models/query.rb, line 185
def includes(*names)
  clone(includes: @includes + names)
end
inspect() click to toggle source
# File lib/baza_models/query.rb, line 350
def inspect
  to_s
end
joins(*arguments) click to toggle source
# 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
last() click to toggle source
# 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
length() click to toggle source
# 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) click to toggle source
# File lib/baza_models/query.rb, line 181
def limit(limit)
  clone(limit: limit)
end
map(&blk) click to toggle source
# File lib/baza_models/query.rb, line 273
def map(&blk)
  to_enum.map(&blk)
end
maximum(column_name) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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?() click to toggle source
# File lib/baza_models/query.rb, line 67
def none?
  !any?
end
offset(offset) click to toggle source
# File lib/baza_models/query.rb, line 177
def offset(offset)
  clone(offset: offset)
end
order(name) click to toggle source
# 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) click to toggle source
# 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 = {}) click to toggle source
# 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_order() click to toggle source
# File lib/baza_models/query.rb, line 268
def reverse_order
  @reverse_order = true
  self
end
sanitize_sql(value) click to toggle source
# 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) click to toggle source
# 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
size() click to toggle source
# File lib/baza_models/query.rb, line 93
def size
  # TODO: This should also take counter caching into account
  length
end
sum(column_name) click to toggle source
# 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_a() click to toggle source
# File lib/baza_models/query.rb, line 334
def to_a
  to_enum.to_a
end
to_enum() click to toggle source
# 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_s() click to toggle source
# File lib/baza_models/query.rb, line 346
def to_s
  "#<BazaModels::Query class=#{@model.name} wheres=#{@wheres}>"
end
to_sql() click to toggle source
# File lib/baza_models/query.rb, line 338
def to_sql
  BazaModels::Query::SqlGenerator.new(query: self).to_sql
end
where(*args) click to toggle source
# 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

Private Instance Methods

any_mods?() click to toggle source
# File lib/baza_models/query.rb, line 401
def any_mods?
  @groups.any? || @includes.any? || @orders.any? || @joins.any? || any_wheres_other_than_relation?
end
any_wheres_other_than_relation?() click to toggle source
# File lib/baza_models/query.rb, line 405
def any_wheres_other_than_relation?
  if @previous_model && @relation && @wheres.length == 1
    looks_like = "`#{@relation.fetch(:table_name)}`.`#{@relation.fetch(:foreign_key)}` = #{@previous_model.id}"

    return false if @wheres.first == looks_like
  end

  true
end
autoloaded_cache() click to toggle source
# File lib/baza_models/query.rb, line 397
def autoloaded_cache
  @previous_model.autoloads.fetch(@relation.fetch(:relation_name))
end
autoloaded_cache_or_create() click to toggle source
# File lib/baza_models/query.rb, line 392
def autoloaded_cache_or_create
  @previous_model.autoloads[@relation.fetch(:relation_name)] ||= []
  autoloaded_cache
end
autoloaded_on_previous_model?() click to toggle source
# File lib/baza_models/query.rb, line 415
def autoloaded_on_previous_model?
  return true if @previous_model && @relation && @previous_model.autoloads.include?(@relation.fetch(:relation_name))

  false
end
clone(args = {}) click to toggle source
# File lib/baza_models/query.rb, line 421
def clone(args = {})
  BazaModels::Query.new({
    model: @model,
    selects: @selects,
    wheres: @wheres,
    joins: @joins.dup,
    includes: @includes,
    groups: @groups,
    offset: @offset,
    orders: @orders,
    page: @page,
    per: @per,
    previous_model: @previous_model,
    limit: @limit
  }.merge(args))
end
column_sql(column_name) click to toggle source
# File lib/baza_models/query.rb, line 478
def column_sql(column_name)
  "#{@db.sep_col}#{@db.escape_column(column_name)}#{@db.sep_col}"
end
key_convert(key, value) click to toggle source
# File lib/baza_models/query.rb, line 438
def key_convert(key, value)
  return "#{key}_id" if value.is_a?(BazaModels::Model)

  key
end
method_missing(method_name, *args, &blk) click to toggle source
Calls superclass method
# 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
should_use_autoload?() click to toggle source
# File lib/baza_models/query.rb, line 388
def should_use_autoload?
  !any_mods? && autoloaded_on_previous_model?
end
table_sql() click to toggle source
# File lib/baza_models/query.rb, line 474
def table_sql
  @table_sql ||= "#{@db.sep_table}#{@db.escape_table(@model.table_name)}#{@db.sep_table}"
end
value_convert(value) click to toggle source
# File lib/baza_models/query.rb, line 444
def value_convert(value)
  return value.id if value.is_a?(BazaModels::Model)

  value
end
value_with_mode(value) click to toggle source
# File lib/baza_models/query.rb, line 450
def value_with_mode(value)
  if value.is_a?(Array)
    sql = "IN ("

    first = true
    value.each do |val_i|
      if first
        first = false
      else
        sql << ", " unless first
      end

      sql << @db.quote_value(val_i)
    end

    sql << ")"
    sql
  elsif value == nil
    "IS NULL"
  else
    "= #{@db.quote_value(value)}"
  end
end