module AssociateJsonb::ArelExtensions::Visitors::PostgreSQL

Private Instance Methods

collect_hash_changes(original, updated, nesting = nil) click to toggle source
# File lib/associate_jsonb/arel_extensions/visitors/postgresql.rb, line 6
def collect_hash_changes(original, updated, nesting = nil)
  keys = original.keys.map(&:to_s)
  updated_keys = updated.keys.map(&:to_s)
  keys |= updated_keys
  original = original.with_indifferent_access
  updated = updated.with_indifferent_access
  added = []
  deleted = []
  finished = {}
  keys.each do |k|
    if updated[k].is_a?(Hash)
      finished[k], a, d = collect_hash_changes(original[k].is_a?(Hash) ? original[k] : {}, updated[k], nesting ? "#{nesting},#{k}" : k)
      a = [[(nesting ? "{#{nesting},#{k}}" : "{#{k}}"), {}]] if original[k].nil? && a.blank?
      added |= a
      deleted |= d
    elsif updated[k].nil?
        deleted << (nesting ? "{#{nesting},#{k}}" : "{#{k}}") if updated_keys.include?(k)
    elsif original[k] != updated[k]
      finished[k] = updated[k]
      added << [(nesting ? "{#{nesting},#{k}}" : "{#{k}}"), updated[k]]
    end
  end
  [ finished, added, deleted ]
end
is_hash?(type) click to toggle source
# File lib/associate_jsonb/arel_extensions/visitors/postgresql.rb, line 31
def is_hash?(type)
  AssociateJsonb.is_hash? type
end
is_insert?(collector) click to toggle source
# File lib/associate_jsonb/arel_extensions/visitors/postgresql.rb, line 42
def is_insert?(collector)
  collector &&
    Array(collector.value).any? {|v| v.is_a?(String) && (v =~ /INSERT INTO/) }
rescue
  false
end
is_update?(collector) click to toggle source
# File lib/associate_jsonb/arel_extensions/visitors/postgresql.rb, line 35
def is_update?(collector)
  collector &&
    Array(collector.value).any? {|v| v.is_a?(String) && (v =~ /UPDATE/) }
rescue
  false
end
visit_Arel_Nodes_BindParam(o, collector) click to toggle source
# File lib/associate_jsonb/arel_extensions/visitors/postgresql.rb, line 74
def visit_Arel_Nodes_BindParam(o, collector)
  catch(:nodes_bound) do
    if AssociateJsonb.jsonb_set_enabled
      catch(:not_hashable) do
        if is_hash?(o.value.type)
          if is_update?(collector)
            visit_BindHashChanges(o.value, collector)

            throw :nodes_bound, collector
          elsif is_insert?(collector)
            value = o.value

            value, _, _ =
              collect_hash_changes(
                {},
                value.value.presence || {}
              )
            throw :nodes_bound, collector.add_bind(o.value.with_cast_value(value)) { |i| "$#{i}"}
          else
            throw :not_hashable
          end
        else
          throw :not_hashable
        end
      end
    elsif AssociateJsonb.jsonb_delete_nil && is_hash?(o.value.type)
      value, _, _ =
        collect_hash_changes(
          {},
          o.value.value.presence || {}
        )
      throw :nodes_bound, collector.add_bind(o.value.with_cast_value(value)) { |i| "$#{i}" }
    end
    throw :nodes_bound, collector.add_bind(o.value) { |i| "$#{i}" }
  end
end
visit_BindHashChanges(t, collector) click to toggle source
# File lib/associate_jsonb/arel_extensions/visitors/postgresql.rb, line 49
def visit_BindHashChanges(t, collector)
  changes, additions, deletions =
    collect_hash_changes(
      t.original_value.presence || {},
      t.value.presence || {}
    )

  base_json = +"COALESCE(#{quote_column_name(t.name)}, '{}'::jsonb)"
  json = base_json

  deletions.each do |del|
    json = +"(#{json} #- '#{del}')"
  end

  coalesced_paths = []
  additions.sort.each do |add, value|
    collector.add_bind(t.with_value_from_user(value)) do |i|
      json = +"jsonb_nested_set(#{json},'#{add}', COALESCE($#{i}, '{}'::jsonb))"
      ''
    end
  end

  collector << json
end