Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ When you include ```has_closure_tree``` in your model, you can provide a hash to
* ```:order``` used to set up [deterministic ordering](#deterministic-ordering)
* ```:scope``` restricts root nodes and sibling ordering to specific columns. Can be a single symbol or an array of symbols. Example: ```scope: :user_id``` or ```scope: [:user_id, :group_id]```. This ensures that root nodes and siblings are scoped correctly when reordering. See [Ordering Roots](#ordering-roots) for more details.
* ```:touch``` delegates to the `belongs_to` annotation for the parent, so `touch`ing cascades to all children (the performance of this for deep trees isn't currently optimal).
* ```:advisory_lock_timeout_seconds``` Raises an error when the advisory lock cannot be acquired within the timeout period

## Accessing Data

Expand Down
1 change: 1 addition & 0 deletions lib/closure_tree/has_closure_tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def has_closure_tree(options = {})
:touch,
:with_advisory_lock,
:advisory_lock_name,
:advisory_lock_timeout_seconds,
:scope
)

Expand Down
11 changes: 9 additions & 2 deletions lib/closure_tree/support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def initialize(model_class, options)
dependent: :nullify, # or :destroy, :delete_all, or :adopt -- see the README
name_column: 'name',
with_advisory_lock: true, # This will be overridden by adapter support
advisory_lock_timeout_seconds: nil,
numeric_order: false
}.merge(options)
raise ArgumentError, "name_column can't be 'path'" if options[:name_column] == 'path'
Expand All @@ -30,6 +31,10 @@ def initialize(model_class, options)
end
end

if !options[:with_advisory_lock] && options[:advisory_lock_timeout_seconds].present?
raise ArgumentError, "advisory_lock_timeout_seconds can't be specified when advisory_lock is disabled"
end

return unless order_is_numeric?

extend NumericOrderSupport.adapter_for_connection(connection)
Expand Down Expand Up @@ -153,8 +158,10 @@ def build_scope_where_clause(scope_conditions)
end

def with_advisory_lock(&block)
if options[:with_advisory_lock] && connection.supports_advisory_locks? && model_class.respond_to?(:with_advisory_lock)
model_class.with_advisory_lock(advisory_lock_name) do
if options[:with_advisory_lock] && connection.supports_advisory_locks? && model_class.respond_to?(:with_advisory_lock!)
lock_method = options[:advisory_lock_timeout_seconds].present? ? :with_advisory_lock! : :with_advisory_lock

model_class.public_send(lock_method, advisory_lock_name, advisory_lock_options) do
transaction(&block)
end
else
Expand Down
4 changes: 4 additions & 0 deletions lib/closure_tree/support_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ def advisory_lock_name
end
end

def advisory_lock_options
{ timeout_seconds: options[:advisory_lock_timeout_seconds] }.compact
end

def quoted_table_name
connection.quote_table_name(table_name)
end
Expand Down
2 changes: 1 addition & 1 deletion test/closure_tree/parallel_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def run_workers(worker_class = FindOrCreateWorker)
skip('unsupported') unless run_parallel_tests?

# disable with_advisory_lock:
Tag.stub(:with_advisory_lock, ->(_lock_name, &block) { block.call }) do
Tag.stub(:with_advisory_lock, ->(_lock_name, _lock_options, &block) { block.call }) do
run_workers
# duplication from at least one iteration:
assert Tag.where(name: @names).size > @iterations
Expand Down
39 changes: 38 additions & 1 deletion test/closure_tree/support_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
require 'test_helper'

describe ClosureTree::Support do
let(:sut) { Tag._ct }
let(:model) { Tag }
let(:sut) { model._ct }

it 'passes through table names without prefix and suffix' do
expected = 'some_random_table_name'
Expand All @@ -15,4 +16,40 @@
tn = ActiveRecord::Base.table_name_prefix + expected + ActiveRecord::Base.table_name_suffix
assert_equal expected, sut.remove_prefix_and_suffix(tn)
end

it 'initializes without error when with_advisory_lock is false' do
assert ClosureTree::Support.new(model, { with_advisory_lock: false })
end

it 'initializes without error when with_advisory_lock is true and advisory_lock_timeout_seconds is set' do
assert ClosureTree::Support.new(model, { with_advisory_lock: true, advisory_lock_timeout_seconds: 10 })
end

it 'calls :with_advisory_lock! when with_advisory_lock is true and timeout is 10' do
options = sut.options.merge(with_advisory_lock: true, advisory_lock_timeout_seconds: 10)
called = false
sut.stub(:options, options) do
model.stub(:with_advisory_lock!, ->(_lock_name, _options, &block) { block.call }) do
sut.with_advisory_lock { called = true }
end
end
assert called, 'block should have been called'
end

it 'calls :with_advisory_lock when with_advisory_lock is true and timeout is nil' do
called = false
model.stub(:with_advisory_lock, ->(_lock_name, _options, &block) { block.call }) do
sut.with_advisory_lock { called = true }
end
assert called, 'block should have been called'
end

it 'does not call advisory lock methods when with_advisory_lock is false' do
options = sut.options.merge(with_advisory_lock: false, advisory_lock_timeout_seconds: nil)
called = false
sut.stub(:options, options) do
sut.with_advisory_lock { called = true }
end
assert called, 'block should have been called'
end
end