lib/torque/postgresql/auxiliary_statement.rb in torque-postgresql-2.3.0 vs lib/torque/postgresql/auxiliary_statement.rb in torque-postgresql-2.4.0
- old
+ new
@@ -1,42 +1,46 @@
# frozen_string_literal: true
require_relative 'auxiliary_statement/settings'
+require_relative 'auxiliary_statement/recursive'
module Torque
module PostgreSQL
class AuxiliaryStatement
TABLE_COLUMN_AS_STRING = /\A(?:"?(\w+)"?\.)?"?(\w+)"?\z/.freeze
class << self
- attr_reader :config
+ attr_reader :config, :table_name
# Find or create the class that will handle statement
def lookup(name, base)
const = name.to_s.camelize << '_' << self.name.demodulize
return base.const_get(const, false) if base.const_defined?(const, false)
- base.const_set(const, Class.new(AuxiliaryStatement))
+
+ base.const_set(const, Class.new(self)).tap do |klass|
+ klass.instance_variable_set(:@table_name, name.to_s)
+ end
end
# Create a new instance of an auxiliary statement
- def instantiate(statement, base, options = nil)
+ def instantiate(statement, base, **options)
klass = while base < ActiveRecord::Base
list = base.auxiliary_statements_list
break list[statement] if list.present? && list.key?(statement)
base = base.superclass
end
- return klass.new(options) unless klass.nil?
+ return klass.new(**options) unless klass.nil?
raise ArgumentError, <<-MSG.squish
There's no '#{statement}' auxiliary statement defined for #{base.class.name}.
MSG
end
# Fast access to statement build
- def build(statement, base, options = nil, bound_attributes = [], join_sources = [])
- klass = instantiate(statement, base, options)
+ def build(statement, base, bound_attributes = [], join_sources = [], **options)
+ klass = instantiate(statement, base, **options)
result = klass.build(base)
bound_attributes.concat(klass.bound_attributes)
join_sources.concat(klass.join_sources)
result
@@ -54,11 +58,11 @@
end
# A way to create auxiliary statements outside of models configurations,
# being able to use on extensions
def create(table_or_settings, &block)
- klass = Class.new(AuxiliaryStatement)
+ klass = Class.new(self)
if block_given?
klass.instance_variable_set(:@table_name, table_or_settings)
klass.configurator(block)
elsif relation_query?(table_or_settings)
@@ -87,41 +91,36 @@
# Run a configuration block or get the static configuration
def configure(base, instance)
return @config unless @config.respond_to?(:call)
- settings = Settings.new(base, instance)
+ recursive = self < AuxiliaryStatement::Recursive
+ settings = Settings.new(base, instance, recursive)
settings.instance_exec(settings, &@config)
settings
end
# Get the arel version of the statement table
def table
@table ||= ::Arel::Table.new(table_name)
end
-
- # Get the name of the table of the configurated statement
- def table_name
- @table_name ||= self.name.demodulize.split('_').first.underscore
- end
end
delegate :config, :table, :table_name, :relation, :configure, :relation_query?,
to: :class
attr_reader :bound_attributes, :join_sources
# Start a new auxiliary statement giving extra options
- def initialize(*args)
- options = args.extract_options!
+ def initialize(*, **options)
args_key = Torque::PostgreSQL.config.auxiliary_statement.send_arguments_key
@join = options.fetch(:join, {})
@args = options.fetch(args_key, {})
@where = options.fetch(:where, {})
@select = options.fetch(:select, {})
- @join_type = options.fetch(:join_type, nil)
+ @join_type = options[:join_type]
@bound_attributes = []
@join_sources = []
end
@@ -129,48 +128,46 @@
def build(base)
@bound_attributes.clear
@join_sources.clear
# Prepare all the data for the statement
- prepare(base)
+ prepare(base, configure(base, self))
# Add the join condition to the list
@join_sources << build_join(base)
# Return the statement with its dependencies
[@dependencies, ::Arel::Nodes::As.new(table, build_query(base))]
end
private
# Setup the statement using the class configuration
- def prepare(base)
- settings = configure(base, self)
+ def prepare(base, settings)
requires = Array.wrap(settings.requires).flatten.compact
@dependencies = ensure_dependencies(requires, base).flatten.compact
@join_type ||= settings.join_type || :inner
@query = settings.query
# Call a proc to get the real query
- if @query.methods.include?(:call)
+ if @query.respond_to?(:call)
call_args = @query.try(:arity) === 0 ? [] : [OpenStruct.new(@args)]
@query = @query.call(*call_args)
@args = []
end
- # Manually set the query table when it's not an relation query
- @query_table = settings.query_table unless relation_query?(@query)
+ # Merge select attributes provided on the instance creation
@select = settings.attributes.merge(@select) if settings.attributes.present?
# Merge join settings
if settings.join.present?
@join = settings.join.merge(@join)
elsif settings.through.present?
@association = settings.through.to_s
elsif relation_query?(@query)
@association = base.reflections.find do |name, reflection|
- break name if @query.klass.eql? reflection.klass
+ break name if @query.klass.eql?(reflection.klass)
end
end
end
# Build the string or arel query
@@ -232,19 +229,10 @@
raise ArgumentError, <<-MSG.squish if conditions.children.empty?
You must provide the join columns when using '#{@query.class.name}'
as a query object on #{self.class.name}.
MSG
- # Expose join columns
- if relation_query?(@query)
- query_table = @query.arel_table
- conditions.children.each do |item|
- @query.select_values += [query_table[item.left.name]] \
- if item.left.relation.eql?(table)
- end
- end
-
# Build the join based on the join type
arel_join.new(table, table.create_on(conditions))
end
# Get the class of the join on arel
@@ -261,32 +249,43 @@
end
end
# Mount the list of selected attributes
def expose_columns(base, query_table = nil)
- # Add select columns to the query and get exposed columns
- @select.map do |left, right|
- base.select_extra_values += [table[right.to_s]]
- project(left, query_table).as(right.to_s) if query_table
+ # Add the columns necessary for the join
+ list = @join_sources.each_with_object(@select) do |join, hash|
+ join.right.expr.children.each do |item|
+ hash[item.left.name] = nil if item.left.relation.eql?(table)
+ end
end
+
+ # Add select columns to the query and get exposed columns
+ list.map do |left, right|
+ base.select_extra_values += [table[right.to_s]] unless right.nil?
+ next unless query_table
+
+ col = project(left, query_table)
+ right.nil? ? col : col.as(right.to_s)
+ end.compact
end
# Ensure that all the dependencies are loaded in the base relation
def ensure_dependencies(list, base)
with_options = list.extract_options!.to_a
- (list + with_options).map do |dependent, options|
- dependent_klass = base.model.auxiliary_statements_list[dependent]
+ (list + with_options).map do |name, options|
+ dependent_klass = base.model.auxiliary_statements_list[name]
raise ArgumentError, <<-MSG.squish if dependent_klass.nil?
- The '#{dependent}' auxiliary statement dependency can't found on
+ The '#{name}' auxiliary statement dependency can't found on
#{self.class.name}.
MSG
next if base.auxiliary_statements_values.any? do |cte|
cte.is_a?(dependent_klass)
end
- AuxiliaryStatement.build(dependent, base, options, bound_attributes, join_sources)
+ options ||= {}
+ AuxiliaryStatement.build(name, base, bound_attributes, join_sources, **options)
end
end
# Project a column on a given table, or use the column table
def project(column, arel_table = nil)