module ActiveRecordExtended::QueryMethods::Unionize

Constants

UNIONIZE_METHODS
UNION_RELATION_METHODS

Public Instance Methods

to_nice_union_sql(color = true) click to toggle source
# File lib/active_record_extended/query_methods/unionize.rb, line 136
def to_nice_union_sql(color = true)
  return to_union_sql unless defined?(::Niceql)

  ::Niceql::Prettifier.prettify_sql(to_union_sql, color)
end
to_union_sql() click to toggle source

Will construct Just the union SQL statement that was been built thus far

# File lib/active_record_extended/query_methods/unionize.rb, line 130
def to_union_sql
  return unless union_values?

  apply_union_ordering(build_union_nodes!(false)).to_sql
end
union(opts = :chain, *args) click to toggle source
# File lib/active_record_extended/query_methods/unionize.rb, line 109
def union(opts = :chain, *args)
  return UnionChain.new(spawn) if opts == :chain

  opts.nil? ? self : spawn.union!(opts, *args, chain_method: __callee__)
end
union!(opts = :chain, *args, chain_method: :union) click to toggle source
# File lib/active_record_extended/query_methods/unionize.rb, line 121
def union!(opts = :chain, *args, chain_method: :union)
  union_chain    = UnionChain.new(self)
  chain_method ||= :union
  return union_chain if opts == :chain

  union_chain.public_send(chain_method, *([opts] + args))
end
unionize_storage() click to toggle source
# File lib/active_record_extended/query_methods/unionize.rb, line 75
def unionize_storage
  @values.fetch(:unionize, {})
end
unionize_storage!() click to toggle source
# File lib/active_record_extended/query_methods/unionize.rb, line 79
def unionize_storage!
  @values[:unionize] ||= {
    union_values:          [],
    union_operations:      [],
    union_ordering_values: [],
    unionized_name:        nil
  }
end

Protected Instance Methods

apply_union_ordering(union_nodes) click to toggle source

Apply's the allowed ORDER BY to the end of the final union statement

Note: This will only apply at the very end of the union statements. Not nested ones.

(I guess you could double nest a union and apply it, but that would be dumb)

Example:

 User.union(User.select(:id).where(id: 8))
     .union(User.select(:id).where(id: 50))
     .union.order(id: :desc)
#=> [<#User id: 50>, <#User id: 8>]

 ```sql
 SELECT users.*
 FROM(
     (SELECT users.id FROM users WHERE id = 8)
     UNION
     (SELECT users.id FROM users WHERE id = 50)
     ORDER BY id DESC
  ) users;
 ```
# File lib/active_record_extended/query_methods/unionize.rb, line 221
def apply_union_ordering(union_nodes)
  return union_nodes unless union_ordering_values?

  UnionChain.new(self).inline_order_by(union_nodes, union_ordering_values)
end
build_union_nodes!(raise_error = true) click to toggle source

Builds a set of nested nodes that union each other's results

Note: Order of chained unions DOES matter

Example:

User.union(User.select(:id).where(id: 8))
    .union(User.select(:id).where(id: 50))
    .union.except(User.select(:id).where(id: 8))

#=> [<#User id: 50]]

```sql
SELECT users.*
FROM(
    (
      (SELECT users.id FROM users WHERE id = 8)
      UNION
      (SELECT users.id FROM users WHERE id = 50)
    )
    EXCEPT
    (SELECT users.id FROM users WHERE id = 8)
 ) users;
```
# File lib/active_record_extended/query_methods/unionize.rb, line 178
def build_union_nodes!(raise_error = true)
  unionize_error_or_warn!(raise_error)
  union_values.each_with_index.reduce(nil) do |union_node, (relation_node, index)|
    next resolve_relation_node(relation_node) if union_node.nil?

    operation = union_operations.fetch(index - 1, :union)
    left      = union_node
    right     = resolve_relation_node(relation_node)

    case operation
    when :union_all
      Arel::Nodes::UnionAll.new(left, right)
    when :except
      Arel::Nodes::Except.new(left, right)
    when :intersect
      Arel::Nodes::Intersect.new(left, right)
    else
      Arel::Nodes::Union.new(left, right)
    end
  end
end
build_unions(arel = @klass.arel_table) click to toggle source
# File lib/active_record_extended/query_methods/unionize.rb, line 144
def build_unions(arel = @klass.arel_table)
  return unless union_values?

  union_nodes      = apply_union_ordering(build_union_nodes!)
  table_name       = Arel.sql(unionized_name)
  table_alias      = arel.create_table_alias(arel.grouping(union_nodes), table_name)
  arel.from(table_alias)
end

Private Instance Methods

resolve_relation_node(relation_node) click to toggle source
# File lib/active_record_extended/query_methods/unionize.rb, line 237
def resolve_relation_node(relation_node)
  case relation_node
  when String
    Arel::Nodes::Grouping.new(Arel.sql(relation_node))
  else
    relation_node.arel
  end
end
unionize_error_or_warn!(raise_error = true) click to toggle source
# File lib/active_record_extended/query_methods/unionize.rb, line 229
def unionize_error_or_warn!(raise_error = true)
  if raise_error && union_values.size <= 1
    raise ArgumentError.new("You are required to provide 2 or more unions to join!")
  elsif !raise_error && union_values.size <= 1
    warn("Warning: You are required to provide 2 or more unions to join.")
  end
end