Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to set :dependent option #32

Merged
merged 1 commit into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### NEXT
- Added :dependent option for setting explicit

### Version 3.4.0
- Rails 7.1 compatibility
- Added ar_next to test matrix
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,26 @@ class Node < ActiveRecord::Base
recursive_tree
end
```
That's it. This will assume that your model has a column named `parent_id` which will be used for traversal. If your column is something different, then you can specifiy it in the call to `recursive_tree`:
That's it. This will assume that your model has a column named `parent_id` which will be used for traversal. If your column is something different, then you can specify it in the call to `recursive_tree`:

```ruby
recursive_tree parent_key: :some_other_column
```

Some extra special stuff - if your parent relation is also polymorphic, the specify the polymorphic column:
Some extra special stuff - if your parent relation is also polymorphic, then specify the polymorphic column:

```ruby
recursive_tree parent_type_column: :some_other_type_column
```

Controlling deletion behaviour:

By default, it is up to the user code to delete all child nodes in a tree when a parent node gets deleted. This can be controlled by the `:dependent` option, which will be set on the `children` association (see [#has_many](https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many) in the Rails doc).

```ruby
recursive_tree dependent: :nullify # or :destroy, etc.
```

## Usage

After you set up a model for usage, there are now several methods you can use.
Expand Down
5 changes: 3 additions & 2 deletions lib/acts_as_recursive_tree/acts_macro.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ module ActsMacro
#
# * <tt>foreign_key</tt> - specifies the column name to use for tracking
# of the tree (default: +parent_id+)
def recursive_tree(parent_key: :parent_id, parent_type_column: nil)
def recursive_tree(parent_key: :parent_id, parent_type_column: nil, dependent: nil)
class_attribute(:_recursive_tree_config, instance_writer: false)

self._recursive_tree_config = Config.new(
model_class: self,
parent_key: parent_key.to_sym,
parent_type_column: parent_type_column.try(:to_sym)
parent_type_column: parent_type_column.try(:to_sym),
dependent: dependent
)

include ActsAsRecursiveTree::Model
Expand Down
3 changes: 2 additions & 1 deletion lib/acts_as_recursive_tree/associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ module Associations
has_many :children,
class_name: base_class.to_s,
foreign_key: _recursive_tree_config.parent_key,
inverse_of: :parent
inverse_of: :parent,
dependent: _recursive_tree_config.dependent

has_many :self_and_siblings,
through: :parent,
Expand Down
5 changes: 3 additions & 2 deletions lib/acts_as_recursive_tree/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ module ActsAsRecursiveTree
# Stores the configuration of one Model class
#
class Config
attr_reader :parent_key, :parent_type_column, :depth_column
attr_reader :parent_key, :parent_type_column, :depth_column, :dependent

def initialize(model_class:, parent_key:, parent_type_column:, depth_column: :recursive_depth)
def initialize(model_class:, parent_key:, parent_type_column:, depth_column: :recursive_depth, dependent: nil)
@model_class = model_class
@parent_key = parent_key
@parent_type_column = parent_type_column
@depth_column = depth_column
@dependent = dependent
end

#
Expand Down
8 changes: 4 additions & 4 deletions spec/acts_as_recursive_tree/builders/ancestors_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
require 'spec_helper'

RSpec.describe ActsAsRecursiveTree::Builders::Ancestors do
context 'basic' do
context 'without additional setup' do
it_behaves_like 'build recursive query'
it_behaves_like 'ancestor query'
include_context 'context with ordering'
include_context 'with ordering'
end

context 'with options' do
include_context 'setup with enforced ordering' do
it_behaves_like 'with ordering'
include_context 'with enforced ordering setup' do
it_behaves_like 'is adding ordering'
end
end
end
8 changes: 4 additions & 4 deletions spec/acts_as_recursive_tree/builders/descendants_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
require 'spec_helper'

RSpec.describe ActsAsRecursiveTree::Builders::Descendants do
context 'basic' do
context 'without additional setup' do
it_behaves_like 'build recursive query'
it_behaves_like 'descendant query'
include_context 'context without ordering'
include_context 'without ordering'
end

context 'with options' do
include_context 'setup with enforced ordering' do
include_context 'with enforced ordering setup' do
let(:ordering) { true }
it_behaves_like 'with ordering'
it_behaves_like 'is adding ordering'
end
end
end
8 changes: 4 additions & 4 deletions spec/acts_as_recursive_tree/builders/leaves_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
require 'spec_helper'

RSpec.describe ActsAsRecursiveTree::Builders::Leaves do
context 'basic' do
context 'without additional setup' do
it_behaves_like 'build recursive query'
it_behaves_like 'descendant query'
include_context 'context without ordering'
include_context 'without ordering'
end

context 'with options' do
include_context 'setup with enforced ordering' do
include_context 'with enforced ordering setup' do
let(:ordering) { true }
it_behaves_like 'without ordering'
it_behaves_like 'not adding ordering'
end
end
end
12 changes: 6 additions & 6 deletions spec/acts_as_recursive_tree/options/values_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
let(:table) { Arel::Table.new('test_table') }
let(:attribute) { table['test_attr'] }

context 'invalid agurment' do
context 'with invalid agurment' do
it 'raises exception' do
expect { described_class.create(nil) }.to raise_exception(/is not supported/)
end
end

context 'single value' do
context 'with single value' do
let(:single_value) { 3 }

it_behaves_like 'single values' do
Expand All @@ -38,8 +38,8 @@
end
end

context 'multi value' do
context 'Array' do
context 'with multi value' do
context 'with Array' do
subject(:value) { described_class.create(array) }

let(:array) { [1, 2, 3] }
Expand All @@ -55,7 +55,7 @@
end
end

context 'Range' do
context 'with Range' do
subject(:value) { described_class.create(range) }

let(:range) { 1..3 }
Expand All @@ -71,7 +71,7 @@
end
end

context 'Relation' do
context 'with Relation' do
subject(:value) { described_class.create(relation, double) }

let(:relation) { Node.where(name: 'test') }
Expand Down
38 changes: 20 additions & 18 deletions spec/support/shared_examples/builders.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
RSpec.shared_context 'setup with enforced ordering' do
# frozen_string_literal: true

RSpec.shared_context 'with enforced ordering setup' do
let(:ordering) { false }
include_context 'base_setup' do
include_context 'with base_setup' do
let(:proc) { ->(config) { config.ensure_ordering! } }
end
end

RSpec.shared_context 'base_setup' do
RSpec.shared_context 'with base_setup' do
subject(:query) { builder.build.to_sql }

let(:model_id) { 1 }
Expand All @@ -32,30 +34,30 @@
end

RSpec.shared_examples 'build recursive query' do
context 'simple id' do
context 'with simple id' do
context 'with simple class' do
include_context 'base_setup' do
include_context 'with base_setup' do
let(:model_class) { Node }
it_behaves_like 'basic recursive examples'
end
end

context 'with class with different parent key' do
include_context 'base_setup' do
include_context 'with base_setup' do
let(:model_class) { NodeWithOtherParentKey }
it_behaves_like 'basic recursive examples'
end
end

context 'with Subclass' do
include_context 'base_setup' do
include_context 'with base_setup' do
let(:model_class) { Floor }
it_behaves_like 'basic recursive examples'
end
end

context 'with polymorphic parent relation' do
include_context 'base_setup' do
include_context 'with base_setup' do
let(:model_class) { NodeWithPolymorphicParent }
it_behaves_like 'basic recursive examples'
end
Expand All @@ -64,34 +66,34 @@
end

RSpec.shared_examples 'ancestor query' do
include_context 'base_setup'
include_context 'with base_setup'

it { is_expected.to match(/"#{builder.travers_loc_table.name}"."#{model_class._recursive_tree_config.parent_key}" = "#{model_class.table_name}"."#{model_class.primary_key}"/) }
end

RSpec.shared_examples 'descendant query' do
include_context 'base_setup'
include_context 'with base_setup'

it { is_expected.to match(/"#{model_class.table_name}"."#{model_class._recursive_tree_config.parent_key}" = "#{builder.travers_loc_table.name}"."#{model_class.primary_key}"/) }
it { is_expected.to match(/#{Regexp.escape(builder.travers_loc_table.project(builder.travers_loc_table[model_class.primary_key]).to_sql)}/) }
end

RSpec.shared_context 'context with ordering' do
include_context 'base_setup' do
it_behaves_like 'with ordering'
RSpec.shared_context 'with ordering' do
include_context 'with base_setup' do
it_behaves_like 'is adding ordering'
end
end

RSpec.shared_context 'context without ordering' do
include_context 'base_setup' do
it_behaves_like 'without ordering'
RSpec.shared_context 'without ordering' do
include_context 'with base_setup' do
it_behaves_like 'not adding ordering'
end
end

RSpec.shared_examples 'with ordering' do
RSpec.shared_examples 'is adding ordering' do
it { is_expected.to match(/ORDER BY #{Regexp.escape(builder.recursive_temp_table[model_class._recursive_tree_config.depth_column].asc.to_sql)}/) }
end

RSpec.shared_examples 'without ordering' do
RSpec.shared_examples 'not adding ordering' do
it { is_expected.not_to match(/ORDER BY/) }
end
8 changes: 2 additions & 6 deletions spec/support/tree_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
# Helper methods for simple tree creation
module TreeMethods
def create_tree(max_level, current_level: 0, node: nil, create_node_info: false, stop_at: -1)
node = Node.create!(name: 'root') if node.nil?
node ||= Node.create!(name: 'root')

1.upto(max_level - current_level) do |index|
child = node.children.create!(
name: "child #{index} - level #{current_level}",
active: stop_at > current_level
)
child = node.children.create!(name: "child #{index} - level #{current_level}", active: stop_at > current_level)

child.create_node_info(status: stop_at > current_level ? 'foo' : 'bar') if create_node_info

Expand All @@ -21,7 +18,6 @@ def create_tree(max_level, current_level: 0, node: nil, create_node_info: false,
stop_at: stop_at
)
end

node
end
end
Loading