Class: ActiveRecord::HierarchicalQuery::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/active_record/hierarchical_query/builder.rb

Constant Summary collapse

CHILD_SCOPE_METHODS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

:where, :joins, :group, :having

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(klass) ⇒ Builder

Returns a new instance of Builder.



24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/active_record/hierarchical_query/builder.rb', line 24

def initialize(klass)
  @klass = klass
  @query = CTE::Query.new(self)

  @start_with_value = nil
  @connect_by_value = nil
  @child_scope_value = klass
  @limit_value = nil
  @offset_value = nil
  @nocycle_value = false
  @order_values = []
end

Instance Attribute Details

#child_scope_valueObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



12
13
14
# File 'lib/active_record/hierarchical_query/builder.rb', line 12

def child_scope_value
  @child_scope_value
end

#connect_by_valueObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



12
13
14
# File 'lib/active_record/hierarchical_query/builder.rb', line 12

def connect_by_value
  @connect_by_value
end

#klassObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



12
13
14
# File 'lib/active_record/hierarchical_query/builder.rb', line 12

def klass
  @klass
end

#limit_valueObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



12
13
14
# File 'lib/active_record/hierarchical_query/builder.rb', line 12

def limit_value
  @limit_value
end

#nocycle_valueObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



12
13
14
# File 'lib/active_record/hierarchical_query/builder.rb', line 12

def nocycle_value
  @nocycle_value
end

#offset_valueObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



12
13
14
# File 'lib/active_record/hierarchical_query/builder.rb', line 12

def offset_value
  @offset_value
end

#order_valuesObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



12
13
14
# File 'lib/active_record/hierarchical_query/builder.rb', line 12

def order_values
  @order_values
end

#start_with_valueObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



12
13
14
# File 'lib/active_record/hierarchical_query/builder.rb', line 12

def start_with_value
  @start_with_value
end

Instance Method Details

#connect_by(conditions = nil) {|parent, child| ... } ⇒ ActiveRecord::HierarchicalQuery::Builder

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).

Examples:

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

Specify relationship with block (traverse descendants)

MyModel.join_recursive do |hierarchy|
  hierarchy.connect_by { |parent, child| parent[:id].eq(child[:parent_id]) }
end

Parameters:

  • conditions (Hash, nil) (defaults to: nil)

    (optional) relationship between parent rows and child rows map, where keys are parent columns names and values are child columns names.

Yields:

  • (parent, child)

    Yields both parent and child tables.

Yield Parameters:

  • parent (Arel::Table)

    parent rows table instance.

  • child (Arel::Table)

    child rows table instance.

Yield Returns:

  • (Arel::Nodes::Node)

    relationship condition expressed as Arel node.

Returns:

Raises:

  • (ArgumentError)


121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/active_record/hierarchical_query/builder.rb', line 121

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

#group(*values) ⇒ Object

Generate methods that apply filters to child scope, such as where or group.

Examples:

Filter child nodes by certain condition

MyModel.join_recursive do |hierarchy|
  hierarchy.where('depth < 5')
end


169
170
171
172
173
174
175
# File 'lib/active_record/hierarchical_query/builder.rb', line 169

CHILD_SCOPE_METHODS.each do |method|
  define_method(method) do |*args|
    @child_scope_value = @child_scope_value.public_send(method, *args)

    self
  end
end

#having(*conditions) ⇒ Object

Generate methods that apply filters to child scope, such as where or group.

Examples:

Filter child nodes by certain condition

MyModel.join_recursive do |hierarchy|
  hierarchy.where('depth < 5')
end


169
170
171
172
173
174
175
# File 'lib/active_record/hierarchical_query/builder.rb', line 169

CHILD_SCOPE_METHODS.each do |method|
  define_method(method) do |*args|
    @child_scope_value = @child_scope_value.public_send(method, *args)

    self
  end
end

#join_to(relation, join_options = {}) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Builds recursive query and joins it to given relation.

Parameters:

  • relation (ActiveRecord::Relation)
  • join_options (Hash) (defaults to: {})

Options Hash (join_options):

  • :as (#to_s)

    joined table alias



267
268
269
270
271
272
273
274
# File 'lib/active_record/hierarchical_query/builder.rb', line 267

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, "#{table.name}__recursive")

  JoinBuilder.new(@query, relation, table_alias).build
end

#joins(*tables) ⇒ Object

Generate methods that apply filters to child scope, such as where or group.

Examples:

Filter child nodes by certain condition

MyModel.join_recursive do |hierarchy|
  hierarchy.where('depth < 5')
end


169
170
171
172
173
174
175
# File 'lib/active_record/hierarchical_query/builder.rb', line 169

CHILD_SCOPE_METHODS.each do |method|
  define_method(method) do |*args|
    @child_scope_value = @child_scope_value.public_send(method, *args)

    self
  end
end

#limit(value) ⇒ ActiveRecord::HierarchicalQuery::Builder

Specifies a limit for the number of records to retrieve.

Parameters:

  • value (Fixnum)

Returns:



181
182
183
184
185
# File 'lib/active_record/hierarchical_query/builder.rb', line 181

def limit(value)
  @limit_value = value

  self
end

#nocycle(value = true) ⇒ ActiveRecord::HierarchicalQuery::Builder

Turn on/off cycles detection. This option can prevent endless loops if your tree could contain cycles.

Parameters:

  • value (true, false) (defaults to: true)

Returns:



225
226
227
228
# File 'lib/active_record/hierarchical_query/builder.rb', line 225

def nocycle(value = true)
  @nocycle_value = value
  self
end

#offset(value) ⇒ ActiveRecord::HierarchicalQuery::Builder

Specifies the number of rows to skip before returning row

Parameters:

  • value (Fixnum)

Returns:



191
192
193
194
195
# File 'lib/active_record/hierarchical_query/builder.rb', line 191

def offset(value)
  @offset_value = value

  self
end

#order_siblings(*columns) ⇒ ActiveRecord::HierarchicalQuery::Builder Also known as: order

Specifies hierarchical order of the recursive query results.

Examples:

MyModel.join_recursive do |hierarchy|
  hierarchy.connect_by(:id => :parent_id)
           .order_siblings(:name)
end
MyModel.join_recursive do |hierarchy|
  hierarchy.connect_by(:id => :parent_id)
           .order_siblings('name DESC, created_at ASC')
end

Parameters:

  • columns (<Symbol, String, Arel::Nodes::Node, Arel::Attributes::Attribute>)

Returns:



213
214
215
216
217
# File 'lib/active_record/hierarchical_query/builder.rb', line 213

def order_siblings(*columns)
  @order_values += columns

  self
end

#priorArel::Table Also known as: previous

Returns object representing parent rows table, so it could be used in complex WHEREs.

Examples:

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

Returns:

  • (Arel::Table)


242
243
244
# File 'lib/active_record/hierarchical_query/builder.rb', line 242

def prior
  @query.recursive_table
end

#select(*columns) ⇒ ActiveRecord::HierarchicalQuery::Builder

Specify which columns should be selected in addition to primary key, CONNECT BY columns and ORDER SIBLINGS columns.

Parameters:

  • columns (Array<Symbol, String, Arel::Attributes::Attribute, Arel::Nodes::Node>)

Options Hash (*columns):

  • :start_with (true, false)

    include given columns to START WITH clause (true by default)

Returns:



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/active_record/hierarchical_query/builder.rb', line 140

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

#start_with(scope = nil, &block) ⇒ ActiveRecord::HierarchicalQuery::Builder

Specify root scope of the hierarchy.

Examples:

When scope given

MyModel.join_recursive do |hierarchy|
  hierarchy.start_with(MyModel.where(:parent_id => nil))
           .connect_by(:id => :parent_id)
end

When Hash given

MyModel.join_recursive do |hierarchy|
  hierarchy.start_with(:parent_id => nil)
           .connect_by(:id => :parent_id)
end

When block given

MyModel.join_recursive do |hierarchy|
  hierarchy.start_with { |root| root.where(:parent_id => nil) }
           .connect_by(:id => :parent_id)
end

When block with arity=0 given

MyModel.join_recursive do |hierarchy|
  hierarchy.start_with { where(:parent_id => nil) }
           .connect_by(:id => :parent_id)
end

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

Parameters:

  • scope (ActiveRecord::Relation, Hash, nil) (defaults to: nil)

    root scope (optional).

Returns:

Raises:

  • (ArgumentError)


72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/active_record/hierarchical_query/builder.rb', line 72

def start_with(scope = nil, &block)
  raise ArgumentError, 'START WITH: scope or block expected, none given' unless scope || block

  case scope
    when Hash
      @start_with_value = klass.where(scope)

    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

#tableObject

Returns object representing child rows table, so it could be used in complex WHEREs.

Examples:

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


257
258
259
# File 'lib/active_record/hierarchical_query/builder.rb', line 257

def table
  @klass.arel_table
end

#where(*conditions) ⇒ Object

Generate methods that apply filters to child scope, such as where or group.

Examples:

Filter child nodes by certain condition

MyModel.join_recursive do |hierarchy|
  hierarchy.where('depth < 5')
end


169
170
171
172
173
174
175
# File 'lib/active_record/hierarchical_query/builder.rb', line 169

CHILD_SCOPE_METHODS.each do |method|
  define_method(method) do |*args|
    @child_scope_value = @child_scope_value.public_send(method, *args)

    self
  end
end