class ActiveRecord::QueryMethods::StoreChain

Base class for different store chains (hstore, jsonb, array). Provides containment queries methods. Provides basic methods.

Attributes

quoted_store_name[R]
store_name[R]

Public Class Methods

new(scope, store_name) click to toggle source
# File lib/pgrel/active_record/store_chain.rb, line 11
def initialize(scope, store_name)
  @scope = scope
  @store_name = store_name
  @inverted = false
  @quoted_store_name = "#{@scope.klass.quoted_table_name}.#{@scope.klass.connection.quote_column_name(store_name)}"
end

Public Instance Methods

contained(opts) click to toggle source

Whether the store is contained within provided store

Example

Model.create!(name: 'first', store: {b: 1})
Model.create!(name: 'second', store: {b: 1, c: 3})

data = {b: 1, c: 2}
Model.store(:store).contains(data).all #=> [Model(name: 'first', ...)]
# File lib/pgrel/active_record/store_chain.rb, line 38
def contained(opts)
  update_scope "#{quoted_store_name} <@ #{type_cast(opts)}"
end
contains(opts) click to toggle source

Whether the store contains provided store

Example

Model.create!(name: 'first', store: {a: 1, b: 2})
Model.create!(name: 'second', store: {b: 1, c: 3})

data = {a: 1}
Model.store(:store).contains(data).all #=> [Model(name: 'first', ...)]
# File lib/pgrel/active_record/store_chain.rb, line 26
def contains(opts)
  update_scope contains_clause(opts)
end
not(opts = :chain) click to toggle source

Add negation to condition.

Example

Model.create!(name: 'first', store: {b: 2})
Model.create!(name: 'second', store: {b: 1, c: 3})

Model.store(:store).not.contains({c: 3}).all #=> [Model(name: 'first')]

Model.store(:store).not(b: 2).all #=> [Model(name: 'second')]
# File lib/pgrel/active_record/store_chain.rb, line 51
def not(opts = :chain)
  @inverted = true
  return where(opts) unless opts == :chain
  self
end
where(*opts) click to toggle source

Query by store values. Supports array values.

NOTE: This method uses “@>” (contains) operator with logic (AND/OR) and not uses “->” (value-by-key). The use of “contains” operator allows us to use GIN index effectively.

Example

Model.create!(name: 'first', store: {b: 1, c: 2})
Model.create!(name: 'second', store: {b: 2, c: 3})

Model.store(:store, c: 2).all #=> [Model(name: 'first', ...)]
#=> (SQL) select * from ... where store @> '"c"=>"2"'::hstore

Model.store(:store, b: [1, 2]).size #=> 2
#=> (SQL) select * from ... where (store @> '"c"=>"1"'::hstore) or
                                  (store @> '"c"=>"2"'::hstore)
# File lib/pgrel/active_record/store_chain.rb, line 74
def where(*opts)
  opts.map! { |opt| opt.is_a?(Hash) ? opt : [opt] }

  update_scope(
    opts.map do |opt|
      opt.map do |k, v|
        case v
        when Array
          "(#{build_or_contains(k, v)})"
        else
          contains_clause(k => v)
        end
      end.join(" and ")
    end.join(" or ")
  )
  @scope
end

Protected Instance Methods

invert_arel(rel) click to toggle source
# File lib/pgrel/active_record/store_chain.rb, line 122
def invert_arel(rel)
  case rel
  when Arel::Nodes::In
    Arel::Nodes::NotIn.new(rel.left, rel.right)
  when Arel::Nodes::Equality
    Arel::Nodes::NotEqual.new(rel.left, rel.right)
  when String
    Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel))
  else
    Arel::Nodes::Not.new(rel)
  end
end
type_cast(value) click to toggle source
# File lib/pgrel/active_record/store_chain.rb, line 101
def type_cast(value)
  ActiveRecord::Base.connection.quote(
    @scope.klass.type_caster.type_cast_for_database(@store_name, value)
  )
end
update_scope(*opts) click to toggle source
# File lib/pgrel/active_record/store_chain.rb, line 95
def update_scope(*opts)
  where_clause = build_where_clause(opts)
  @scope.where_clause += @inverted ? where_clause.invert : where_clause
  @scope
end

Private Instance Methods

build_or_contains(k, vals) click to toggle source
# File lib/pgrel/active_record/store_chain.rb, line 142
def build_or_contains(k, vals)
  vals.map { |v| contains_clause(k => v) }.join(" or ")
end
build_where_clause(opts) click to toggle source
# File lib/pgrel/active_record/store_chain.rb, line 146
def build_where_clause(opts)
  if ActiveRecord.version.release >= Gem::Version.new("6.1.0")
    @scope.send(:build_where_clause, opts)
  else
    @scope.send(:where_clause_factory).build(opts, [])
  end
end
contains_clause(opts) click to toggle source
# File lib/pgrel/active_record/store_chain.rb, line 138
def contains_clause(opts)
  "#{quoted_store_name} @> #{type_cast(opts)}"
end