card/lib/card/query/join.rb
class Card
module Query
# object representation of Card::Query joins
class Join
JOIN_OPT_KEYS = %i[side conditions
from from_table from_alias from_field
to to_table to_alias to_field].freeze
attr_accessor(*JOIN_OPT_KEYS)
# These two manage hierarchy of nested joins
attr_accessor :superjoin, :subjoins
# This example join clause:
#
# cards c LEFT JOIN card_actions ca on c.id = ca.card_id and ca.draft is null
#
# ...would translate into the following instance variables on the Join object:
#
# @side = "left"
# @from_table = "cards"
# @from_alias = "c"
# @from_field = "id"
# @to_table = "card_actions"
# @to_alias = "ca"
# @to_field = "card_id"
# @conditions = "ca.draft is null"
#
# all of the above can be set directly via opts using the keys with the same name.
#
# Join.new side: "left", from_table: "cards"...
#
# The from and to fields can also be set via :from and :to keys.
# (see #interpret_from_and_to)
#
# You can generally use Symbols in place of Strings where applicable.
#
def initialize opts={}
interpret_from_and_to opts
convert_opts_to_instance_variables opts
@conditions = Array(@conditions).compact
@subjoins = []
register_superjoin
end
def side
if !@side.nil?
@side.to_s.upcase
else
@side = inside_or? ? "LEFT" : nil
end
end
def left?
side == "LEFT"
end
private
def inside_or?
from.is_a?(Card::Query) && from.mods[:conj] == "or"
end
# the options :to and :from can be translated into the full table/alias/field trio.
#
# - An Array is interpreted in that order (table, alias, field)
# - A Hash expects the keys :table, :alias, and (optionally) :field
# - A table and alias can be inferred from Card::Query or Card::Query::Reference
# objects.
# - They can also be inferred from a Join object, but only as a :from value
#
# In all cases, if the field is not specified, it is assumed to be :id
def interpret_from_and_to opts
%i[from to].each do |side|
directional_hash_for_object(side, opts[side]).map do |key, value|
opts[:"#{side}_#{key}"] ||= value
end
end
end
def directional_hash_for_object side, object
case object
when nil then nil
when Hash then object
when Array then dir_hash(*object)
when AbstractQuery then dir_hash_for_query object
when Join then dir_hash_for_join side, object
else dir_error(side, object)
end
end
def dir_hash table, table_alias, field=nil
hash = { table: table, alias: table_alias }
hash[:field] = field || :id
hash
end
def dir_hash_for_query query
dir_hash query.table, query.table_alias
end
def dir_hash_for_join side, object
raise "to: cannot be Join" if side == :to
dir_hash object.to_table, object.to_alias
end
def dir_error side, object
raise Card::Error::BadQuery, "invalid #{side} option: #{object}"
end
def convert_opts_to_instance_variables opts
opts.each do |key, value|
send "#{key}=", value if value.present? && JOIN_OPT_KEYS.member?(key)
end
end
def register_superjoin
return unless @from.is_a? Join
@superjoin = @from
@superjoin.subjoins << self
end
end
end
end