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

acts_as_list_no_update #244

Merged
merged 5 commits into from
Jan 18, 2017
Merged
Show file tree
Hide file tree
Changes from 2 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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,17 @@ default: `1`. Use this option to define the top of the list. Use 0 to make the c
- `add_new_at`
default: `:bottom`. Use this option to specify whether objects get added to the `:top` or `:bottom` of the list. `nil` will result in new items not being added to the list on create, i.e, position will be kept nil after create.

## Disabling temporarily

If you need to temporarily disable `acts_as_list` during specific operations such as mass-update or imports:
```ruby
TodoItem.acts_as_list_no_update do
perform_mass_update
end
```
In an `acts_as_list_no_update` block, all callbacks are disabled, and positions are not updated. New records will be created with
the default value from the database. It is your responsibility to correctly manage `positions` values.

## Versions
As of version `0.7.5` Rails 5 is supported.

Expand Down
1 change: 1 addition & 0 deletions lib/acts_as_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
require "acts_as_list/active_record/acts/aux_method_definer"
require "acts_as_list/active_record/acts/callback_definer"
require 'acts_as_list/active_record/acts/list'
require 'acts_as_list/active_record/acts/no_update'
10 changes: 5 additions & 5 deletions lib/acts_as_list/active_record/acts/callback_definer.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
module ActiveRecord::Acts::List::CallbackDefiner #:nodoc:
def self.call(caller_class, add_new_at)
caller_class.class_eval do
before_validation :check_top_position
before_validation :check_top_position, unless: :act_as_list_no_update?

before_destroy :lock!
after_destroy :decrement_positions_on_lower_items
after_destroy :decrement_positions_on_lower_items, unless: :act_as_list_no_update?

before_update :check_scope
after_update :update_positions
before_update :check_scope, unless: :act_as_list_no_update?
after_update :update_positions, unless: :act_as_list_no_update?

after_commit :clear_scope_changed

if add_new_at.present?
before_create "add_to_list_#{add_new_at}".to_sym
before_create "add_to_list_#{add_new_at}".to_sym, unless: :act_as_list_no_update?
end
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/acts_as_list/active_record/acts/list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def acts_as_list(options = {})
ActiveRecord::Acts::List::CallbackDefiner.call(caller_class, configuration[:add_new_at])

include ActiveRecord::Acts::List::InstanceMethods
include ActiveRecord::Acts::List::NoUpdate
end
end

Expand Down
50 changes: 50 additions & 0 deletions lib/acts_as_list/active_record/acts/no_update.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module ActiveRecord
module Acts
module List
module NoUpdate
extend ActiveSupport::Concern

module ClassMethods
# Lets you selectively disable all act_as_list database updates
# for the duration of a block.
#
# ==== Examples
# ActiveRecord::Acts::List.acts_as_list_no_update do
# TodoList....
# end
#
# TodoList.acts_as_list_no_update do
# TodoList....
# end
#
def acts_as_list_no_update(&block)
NoUpdate.apply_to(self, &block)
end
end

class << self
def apply_to(klass)
klasses.push(klass)
yield
ensure
klasses.pop
end

def applied_to?(klass)
klasses.any? { |k| k >= klass }
end

private

def klasses
Thread.current[:act_as_list_no_update] ||= []
end
end

def act_as_list_no_update?
NoUpdate.applied_to?(self.class)
end
end
end
end
end
17 changes: 17 additions & 0 deletions test/shared_array_scope_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ def test_insert
assert !new.first?
assert new.last?

new = ArrayScopeListMixin.acts_as_list_no_update { ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass') }
assert_equal $default_position,new.pos
assert_equal $default_position.is_a?(Fixnum), new.first?
assert !new.last?

new = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass')
assert_equal 3, new.pos
assert !new.first?
Expand All @@ -81,6 +86,9 @@ def test_insert_at
new = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass')
assert_equal 3, new.pos

new_noup = ArrayScopeListMixin.acts_as_list_no_update { ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass') }
assert_equal $default_position,new_noup.pos

new4 = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass')
assert_equal 4, new4.pos

Expand All @@ -104,6 +112,9 @@ def test_insert_at

new4.reload
assert_equal 5, new4.pos

new_noup.reload
assert_equal $default_position, new_noup.pos
end

def test_delete_middle
Expand All @@ -123,6 +134,12 @@ def test_delete_middle

assert_equal 1, ArrayScopeListMixin.where(id: 3).first.pos
assert_equal 2, ArrayScopeListMixin.where(id: 4).first.pos

ArrayScopeListMixin.acts_as_list_no_update { ArrayScopeListMixin.where(id: 3).first.destroy }

assert_equal [4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id)

assert_equal 2, ArrayScopeListMixin.where(id: 4).first.pos
end

def test_remove_from_list_should_then_fail_in_list?
Expand Down
44 changes: 37 additions & 7 deletions test/shared_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ def test_insert
assert !new.first?
assert new.last?

new = ListMixin.acts_as_list_no_update { ListMixin.create(parent_id: 20) }
assert_equal $default_position, new.pos
assert_equal $default_position.is_a?(Fixnum), new.first?
assert !new.last?

new = ListMixin.create(parent_id: 20)
assert_equal 3, new.pos
assert !new.first?
Expand All @@ -81,6 +86,9 @@ def test_insert_at
new = ListMixin.create(parent_id: 20)
assert_equal 3, new.pos

new_noup = ListMixin.acts_as_list_no_update { ListMixin.create(parent_id: 20) }
assert_equal $default_position, new_noup.pos

new4 = ListMixin.create(parent_id: 20)
assert_equal 4, new4.pos

Expand All @@ -105,18 +113,24 @@ def test_insert_at
new4.reload
assert_equal 5, new4.pos

last1 = ListMixin.order('pos').last
last2 = ListMixin.order('pos').last
new_noup.reload
assert_equal $default_position, new_noup.pos

last1 = ListMixin.where('pos IS NOT NULL').order('pos').last
last2 = ListMixin.where('pos IS NOT NULL').order('pos').last
last1.insert_at(1)
last2.insert_at(1)
assert_equal [1, 2, 3, 4, 5], ListMixin.where(parent_id: 20).order('pos').map(&:pos)
if ENV['DB'] == 'postgresql' and $default_position.nil?
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use this: http://stackoverflow.com/a/20959470/129798

So instead of changing what's expected, augment the scope conditionally:

ListMixin.where(parent_id: 20).order('pos NULLS FIRST').map(&:pos)

expected = [1, 2, 3, 4, 5, nil] # postgreSQL sorts nil after numbers
else
expected = [$default_position, 1, 2, 3, 4, 5]
end
assert_equal expected, ListMixin.where(parent_id: 20).order('pos').map(&:pos)
end

def test_delete_middle
assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)



ListMixin.where(id: 2).first.destroy

assert_equal [1, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
Expand All @@ -131,6 +145,12 @@ def test_delete_middle

assert_equal 1, ListMixin.where(id: 3).first.pos
assert_equal 2, ListMixin.where(id: 4).first.pos

ListMixin.acts_as_list_no_update { ListMixin.where(id: 3).first.destroy }

assert_equal [4], ListMixin.where(parent_id: 5).order('pos').map(&:id)

assert_equal 2, ListMixin.where(id: 4).first.pos
end

def test_with_string_based_scope
Expand Down Expand Up @@ -241,14 +261,24 @@ def test_before_create_callback_adds_to_given_position

assert_equal [5, 1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)

new6 = ListMixin.new(parent_id: 5)
new6.pos = 3
new6.save!
assert_equal 3, new6.pos
assert !new6.first?
assert !new6.last?

assert_equal [5, 1, 6, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)

new = ListMixin.new(parent_id: 5)
new.pos = 3
new.save!
ListMixin.acts_as_list_no_update { new.save! }
assert_equal 3, new.pos
assert_equal 3, new6.pos
assert !new.first?
assert !new.last?

assert_equal [5, 1, 6, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
assert_equal [5, 1, 6, 7, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos, id').map(&:id)
end

def test_non_persisted_records_dont_get_lock_called
Expand Down
12 changes: 12 additions & 0 deletions test/shared_list_sub.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ def test_insert_at
new = ListMixinSub1.create("parent_id" => 20)
assert_equal 3, new.pos

new_noup = ListMixinSub1.acts_as_list_no_update { ListMixinSub1.create("parent_id" => 20) }
assert_equal $default_position, new_noup.pos

new4 = ListMixin.create("parent_id" => 20)
assert_equal 4, new4.pos

Expand All @@ -145,6 +148,9 @@ def test_insert_at

new4.reload
assert_equal 5, new4.pos

new_noup.reload
assert_equal $default_position, new_noup.pos
end

def test_delete_middle
Expand All @@ -164,6 +170,12 @@ def test_delete_middle

assert_equal 1, ListMixin.where(id: 3).first.pos
assert_equal 2, ListMixin.where(id: 4).first.pos

ListMixin.acts_as_list_no_update { ListMixin.where(id: 3).first.destroy }

assert_equal [4], ListMixin.where(parent_id: 5000).order('pos').map(&:id)

assert_equal 2, ListMixin.where(id: 4).first.pos
end

def test_acts_as_list_class
Expand Down
17 changes: 16 additions & 1 deletion test/shared_top_addition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,14 @@ def test_insert
assert new.first?
assert !new.last?

new = TopAdditionMixin.acts_as_list_no_update { TopAdditionMixin.create(parent_id: 20) }
assert_equal $default_position, new.pos
assert_equal $default_position.is_a?(Fixnum), new.first?
assert !new.last?

new = TopAdditionMixin.create(parent_id: 20)
assert_equal 1, new.pos
assert new.first?
assert_equal $default_position.nil?, new.first?
assert !new.last?

new = TopAdditionMixin.create(parent_id: 0)
Expand All @@ -67,6 +72,9 @@ def test_insert_at
new = TopAdditionMixin.create(parent_id: 20)
assert_equal 1, new.pos

new = TopAdditionMixin.acts_as_list_no_update { TopAdditionMixin.create(parent_id: 20) }
assert_equal $default_position, new.pos

new4 = TopAdditionMixin.create(parent_id: 20)
assert_equal 1, new4.pos

Expand All @@ -87,6 +95,13 @@ def test_delete_middle
assert_equal 1, TopAdditionMixin.where(id: 1).first.pos
assert_equal 2, TopAdditionMixin.where(id: 3).first.pos
assert_equal 3, TopAdditionMixin.where(id: 4).first.pos

TopAdditionMixin.acts_as_list_no_update { TopAdditionMixin.where(id: 3).first.destroy }

assert_equal [1, 4], TopAdditionMixin.where(parent_id: 5).order('pos').map(&:id)

assert_equal 1, TopAdditionMixin.where(id: 1).first.pos
assert_equal 3, TopAdditionMixin.where(id: 4).first.pos
end

end
Expand Down
11 changes: 11 additions & 0 deletions test/shared_zero_based.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ def test_insert
assert !new.first?
assert new.last?

new = ZeroBasedMixin.acts_as_list_no_update { ZeroBasedMixin.create(parent_id: 20) }
assert_equal $default_position, new.pos
assert !new.first?
assert !new.last?

new = ZeroBasedMixin.create(parent_id: 20)
assert_equal 2, new.pos
assert !new.first?
Expand Down Expand Up @@ -63,6 +68,9 @@ def test_insert_at
new = ZeroBasedMixin.create(parent_id: 20)
assert_equal 2, new.pos

new_noup = ZeroBasedMixin.acts_as_list_no_update { ZeroBasedMixin.create(parent_id: 20) }
assert_equal $default_position, new_noup.pos

new4 = ZeroBasedMixin.create(parent_id: 20)
assert_equal 3, new4.pos

Expand All @@ -86,6 +94,9 @@ def test_insert_at

new4.reload
assert_equal 4, new4.pos

new_noup.reload
assert_equal $default_position, new_noup.pos
end
end
end
Loading