class ActiveRecord::HierarchicalQuery::Query
Constants
- CHILD_SCOPE_METHODS
@api private
- ORDERING_COLUMN_NAME
@api private
Attributes
@api private
@api private
@api private
@api private
@api private
@api private
@api private
@api private
@api private
Public Class Methods
# File lib/active_record/hierarchical_query/query.rb, line 29 def initialize(klass) @klass = klass # start with :all @start_with_value = klass.__send__(HierarchicalQuery::DELEGATOR_SCOPE) @connect_by_value = nil @child_scope_value = klass.__send__(HierarchicalQuery::DELEGATOR_SCOPE) @limit_value = nil @offset_value = nil @nocycle_value = false @order_values = [] @distinct_value = false end
Public Instance Methods
Specify relationship between parent rows and child rows of the hierarchy. It can be specified with Hash where keys are parent columns names and values are child columns names, or with block (see example below).
@example Specify relationship with Hash (traverse descendants)
MyModel.join_recursive do |hierarchy| # join child rows with condition `parent.id = child.parent_id` hierarchy.connect_by(id: :parent_id) end
@example Specify relationship with block (traverse descendants)
MyModel.join_recursive do |hierarchy| hierarchy.connect_by { |parent, child| parent[:id].eq(child[:parent_id]) } end
@param [Hash, nil] conditions (optional) relationship between parent rows and
child rows map, where keys are parent columns names and values are child columns names.
@yield [parent, child] Yields both parent and child tables. @yieldparam [Arel::Table] parent parent rows table instance. @yieldparam [Arel::Table] child child rows table instance. @yieldreturn [Arel::Nodes::Node] relationship condition expressed as Arel
node. @return [ActiveRecord::HierarchicalQuery::Query] self
# File lib/active_record/hierarchical_query/query.rb, line 133 def connect_by(conditions = nil, &block) # convert hash to block which returns Arel node if conditions block = conditions_to_proc(conditions) end raise ArgumentError, 'CONNECT BY: Conditions hash or block expected, none given' unless block @connect_by_value = block self end
Turn on select distinct option in the CTE
.
@return [ActiveRecord::HierarchicalQuery::Query] self
# File lib/active_record/hierarchical_query/query.rb, line 277 def distinct @distinct_value = true self end
@return [Arel::Nodes::Node] @api private
# File lib/active_record/hierarchical_query/query.rb, line 284 def join_conditions connect_by_value.call(recursive_table, table) end
Builds recursive query and joins it to given relation
.
@api private @param [ActiveRecord::Relation] relation @param [Hash] join_options @option join_options [#to_s] :as joined table alias @api private
# File lib/active_record/hierarchical_query/query.rb, line 306 def join_to(relation, join_options = {}) raise 'Recursive query requires CONNECT BY clause, please use #connect_by method' unless connect_by_value table_alias = join_options.fetch(:as, "#{normalized_table_name}__recursive") JoinBuilder.new(self, relation, table_alias, join_options).build end
Specifies a limit for the number of records to retrieve.
@param [Fixnum] value @return [ActiveRecord::HierarchicalQuery::Query] self
# File lib/active_record/hierarchical_query/query.rb, line 193 def limit(value) @limit_value = value self end
Turn on/off cycles detection. This option can prevent endless loops if your tree could contain cycles.
@param [true, false] value @return [ActiveRecord::HierarchicalQuery::Query] self
# File lib/active_record/hierarchical_query/query.rb, line 237 def nocycle(value = true) @nocycle_value = value self end
Specifies the number of rows to skip before returning row
@param [Fixnum] value @return [ActiveRecord::HierarchicalQuery::Query] self
# File lib/active_record/hierarchical_query/query.rb, line 203 def offset(value) @offset_value = value self end
Specifies hierarchical order of the recursive query results.
@example
MyModel.join_recursive do |hierarchy| hierarchy.connect_by(id: :parent_id) .order_siblings(:name) end
@example
MyModel.join_recursive do |hierarchy| hierarchy.connect_by(id: :parent_id) .order_siblings('name DESC, created_at ASC') end
@param [<Symbol, String, Arel::Nodes::Node, Arel::Attributes::Attribute>] columns @return [ActiveRecord::HierarchicalQuery::Query] self
# File lib/active_record/hierarchical_query/query.rb, line 225 def order_siblings(*columns) @order_values += columns self end
@api private
# File lib/active_record/hierarchical_query/query.rb, line 295 def ordering_column_name ORDERING_COLUMN_NAME end
@return [ActiveRecord::HierarchicalQuery::Orderings] @api private
# File lib/active_record/hierarchical_query/query.rb, line 290 def orderings @orderings ||= Orderings.new(order_values, table) end
Returns object representing parent rows table, so it could be used in complex WHEREs.
@example
MyModel.join_recursive do |hierarchy| hierarchy.connect_by(id: :parent_id) .start_with(parent_id: nil) { select(:depth) } .select(hierarchy.table[:depth]) .where(hierarchy.prior[:depth].lteq 1) end
@return [Arel::Table]
# File lib/active_record/hierarchical_query/query.rb, line 254 def prior @recursive_table ||= Arel::Table.new("#{normalized_table_name}__recursive") end
Specify which columns should be selected in addition to primary key, CONNECT BY columns and ORDER SIBLINGS columns.
@param [Array<Symbol, String, Arel::Attributes::Attribute, Arel::Nodes::Node>] columns @option columns [true, false] :start_with include given columns to START WITH clause (true by default) @return [ActiveRecord::HierarchicalQuery::Query] self
# File lib/active_record/hierarchical_query/query.rb, line 152 def select(*columns) options = columns.extract_options! columns = columns.flatten.map do |column| column.is_a?(Symbol) ? table[column] : column end # TODO: detect if column already present in START WITH clause and skip it if options.fetch(:start_with, true) start_with { |scope| scope.select(columns) } end @child_scope_value = @child_scope_value.select(columns) self end
Specify root scope of the hierarchy.
@example When scope given
MyModel.join_recursive do |hierarchy| hierarchy.start_with(MyModel.where(parent_id: nil)) .connect_by(id: :parent_id) end
@example When Hash given
MyModel.join_recursive do |hierarchy| hierarchy.start_with(parent_id: nil) .connect_by(id: :parent_id) end
@example When String given
MyModel.join_recursive do |hierarchy| hierararchy.start_with('parent_id = ?', 1) .connect_by(id: :parent_id) end
@example When block given
MyModel.join_recursive do |hierarchy| hierarchy.start_with { |root| root.where(parent_id: nil) } .connect_by(id: :parent_id) end
@example When block with arity=0 given
MyModel.join_recursive do |hierarchy| hierarchy.start_with { where(parent_id: nil) } .connect_by(id: :parent_id) end
@example Specify columns for root relation (PostgreSQL-specific)
MyModel.join_recursive do |hierarchy| hierarchy.start_with { select('ARRAY[id] AS _path') } .connect_by(id: :parent_id) .select('_path || id', start_with: false) # `start_with: false` tells not to include this expression into START WITH clause end
@param [ActiveRecord::Relation, Hash, String, nil] scope root scope (optional). @return [ActiveRecord::HierarchicalQuery::Query] self
# File lib/active_record/hierarchical_query/query.rb, line 84 def start_with(scope = nil, *arguments, &block) raise ArgumentError, 'START WITH: scope or block expected, none given' unless scope || block case scope when Hash, String @start_with_value = klass.where(scope, *arguments) when ActiveRecord::Relation @start_with_value = scope else # do nothing if something weird given end if block object = @start_with_value || @klass @start_with_value = if block.arity == 0 object.instance_eval(&block) else block.call(object) end end self end
Returns object representing child rows table, so it could be used in complex WHEREs.
@example
MyModel.join_recursive do |hierarchy| hierarchy.connect_by(id: :parent_id) .start_with(parent_id: nil) { select(:depth) } .select(hierarchy.table[:depth]) .where(hierarchy.prior[:depth].lteq 1) end
# File lib/active_record/hierarchical_query/query.rb, line 270 def table @klass.arel_table end
Private Instance Methods
converts conditions given as a hash to proc
# File lib/active_record/hierarchical_query/query.rb, line 317 def conditions_to_proc(conditions) proc do |parent, child| conditions.map do |parent_expression, child_expression| parent_expression = parent[parent_expression] if parent_expression.is_a?(Symbol) child_expression = child[child_expression] if child_expression.is_a?(Symbol) Arel::Nodes::Equality.new(parent_expression, child_expression) end.reduce(:and) end end
# File lib/active_record/hierarchical_query/query.rb, line 328 def normalized_table_name table.name.gsub('.', '_') end