acts_as_list-1.0.4/0000755000004100000410000000000014042233640014212 5ustar www-datawww-dataacts_as_list-1.0.4/.travis.yml0000644000004100000410000000313014042233640016320 0ustar www-datawww-datalanguage: ruby cache: bundler sudo: false before_script: - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true - gem install bundler -v '< 2' - mysql -e 'create database acts_as_list;' - psql -c 'create database acts_as_list;' -U postgres rvm: - 2.4 - 2.5 - 2.6 - 2.7 - 3.0 services: - mysql - postgresql env: - DB=sqlite - DB=mysql - DB=postgresql gemfile: - gemfiles/rails_4_2.gemfile - gemfiles/rails_5_0.gemfile - gemfiles/rails_5_1.gemfile - gemfiles/rails_5_2.gemfile - gemfiles/rails_6_0.gemfile - gemfiles/rails_6_1.gemfile matrix: exclude: - rvm: 2.4 gemfile: gemfiles/rails_6_0.gemfile - rvm: 2.4 gemfile: gemfiles/rails_6_1.gemfile # Ruby 2.7 uses a `bigdecimal` version that doesn't support BigDecimal.new # that Rails 4.2 uses. See also: # https://github.com/ruby/bigdecimal#which-version-should-you-select - rvm: 2.7 gemfile: gemfiles/rails_4_2.gemfile # Ruby 2.7 warning `sqlite3-1.3.13/lib/sqlite3/statement.rb:108: warning: rb_tainted_str_new is deprecated` # and job wil exceed the maximum log length # see also: https://github.com/sparklemotion/sqlite3-ruby/issues/276 - rvm: 2.7 gemfile: gemfiles/rails_5_0.gemfile env: DB=sqlite # Rails <6 does not support Ruby 3, see: # https://github.com/rails/rails/issues/40938#issuecomment-751898275 - rvm: 3.0 gemfile: gemfiles/rails_4_2.gemfile - rvm: 3.0 gemfile: gemfiles/rails_5_0.gemfile - rvm: 3.0 gemfile: gemfiles/rails_5_1.gemfile - rvm: 3.0 gemfile: gemfiles/rails_5_2.gemfile acts_as_list-1.0.4/test/0000755000004100000410000000000014042233640015171 5ustar www-datawww-dataacts_as_list-1.0.4/test/shared_top_addition.rb0000644000004100000410000000715614042233640021532 0ustar www-datawww-data# frozen_string_literal: true module Shared module TopAddition def setup (1..4).each { |counter| TopAdditionMixin.create! pos: counter, parent_id: 5 } end def test_setup_state # If we explicitly define a position (as above) then that position is what gets applied assert_equal [1, 2, 3, 4], TopAdditionMixin.where(parent_id: 5).order('pos').map(&:id) end def test_reordering TopAdditionMixin.where(id: 2).first.move_lower assert_equal [1, 3, 2, 4], TopAdditionMixin.where(parent_id: 5).order('pos').map(&:id) TopAdditionMixin.where(id: 2).first.move_higher assert_equal [1, 2, 3, 4], TopAdditionMixin.where(parent_id: 5).order('pos').map(&:id) TopAdditionMixin.where(id: 1).first.move_to_bottom assert_equal [2, 3, 4, 1], TopAdditionMixin.where(parent_id: 5).order('pos').map(&:id) TopAdditionMixin.where(id: 1).first.move_to_top assert_equal [1, 2, 3, 4], TopAdditionMixin.where(parent_id: 5).order('pos').map(&:id) TopAdditionMixin.where(id: 2).first.move_to_bottom assert_equal [1, 3, 4, 2], TopAdditionMixin.where(parent_id: 5).order('pos').map(&:id) TopAdditionMixin.where(id: 4).first.move_to_top assert_equal [4, 1, 3, 2], TopAdditionMixin.where(parent_id: 5).order('pos').map(&:id) end def test_injection item = TopAdditionMixin.new(parent_id: 1) assert_equal({ parent_id: 1 }, item.scope_condition) assert_equal "pos", item.position_column end def test_insert new = TopAdditionMixin.create(parent_id: 20) assert_equal 1, new.pos assert new.first? assert new.last? new = TopAdditionMixin.create(parent_id: 20) assert_equal 1, new.pos assert new.first? assert !new.last? new = TopAdditionMixin.acts_as_list_no_update { TopAdditionMixin.create(parent_id: 20) } assert_equal_or_nil $default_position, new.pos assert_equal $default_position.is_a?(Integer), new.first? assert !new.last? new = TopAdditionMixin.create(parent_id: 20) assert_equal 1, new.pos assert_equal $default_position.nil?, new.first? assert !new.last? new = TopAdditionMixin.create(parent_id: 0) assert_equal 1, new.pos assert new.first? assert new.last? end def test_insert_at new = TopAdditionMixin.create(parent_id: 20) assert_equal 1, new.pos new = TopAdditionMixin.create(parent_id: 20) assert_equal 1, new.pos 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_or_nil $default_position, new.pos new4 = TopAdditionMixin.create(parent_id: 20) assert_equal 1, new4.pos new4.insert_at(3) assert_equal 3, new4.pos end def test_supplied_position new = TopAdditionMixin.create(parent_id: 20, pos: 3) assert_equal 3, new.pos end def test_delete_middle TopAdditionMixin.where(id: 2).first.destroy assert_equal [1, 3, 4], TopAdditionMixin.where(parent_id: 5).order('pos').map(&:id) 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 end acts_as_list-1.0.4/test/helper.rb0000644000004100000410000000340014042233640016772 0ustar www-datawww-data# frozen_string_literal: true # $DEBUG = true require "rubygems" require "bundler/setup" begin Bundler.setup(:default, :development) rescue Bundler::BundlerError => e $stderr.puts e.message $stderr.puts "Run `bundle install` to install missing gems" exit e.status_code end require "active_record" require "minitest/autorun" require "mocha/minitest" require "#{File.dirname(__FILE__)}/../init" if defined?(ActiveRecord::VERSION) && ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 2 # Was removed in Rails 5 and is effectively true. ActiveRecord::Base.raise_in_transactional_callbacks = true end db_config = YAML.load_file(File.expand_path("../database.yml", __FILE__)).fetch(ENV["DB"] || "sqlite") ActiveRecord::Base.establish_connection(db_config) ActiveRecord::Schema.verbose = false def teardown_db if ActiveRecord::VERSION::MAJOR >= 5 tables = ActiveRecord::Base.connection.data_sources else tables = ActiveRecord::Base.connection.tables end tables.each do |table| ActiveRecord::Base.connection.drop_table(table) end end require "shared" # require 'logger' # ActiveRecord::Base.logger = Logger.new(STDOUT) def assert_equal_or_nil(a, b) if a.nil? assert_nil b else assert_equal a, b end end def assert_no_deprecation_warning_raised_by(failure_message = 'ActiveRecord deprecation warning raised when we didn\'t expect it', pass_message = 'No ActiveRecord deprecation raised') original_behavior = ActiveSupport::Deprecation.behavior ActiveSupport::Deprecation.behavior = :raise begin yield rescue ActiveSupport::DeprecationException => e flunk "#{failure_message}: #{e}" rescue raise else pass pass_message end ensure ActiveSupport::Deprecation.behavior = original_behavior end acts_as_list-1.0.4/test/test_no_update_for_extra_classes.rb0000644000004100000410000000731014042233640024322 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class TodoList < ActiveRecord::Base has_many :todo_items acts_as_list end class TodoItem < ActiveRecord::Base belongs_to :todo_list has_many :todo_item_attachments acts_as_list scope: :todo_list end class TodoItemAttachment < ActiveRecord::Base belongs_to :todo_item acts_as_list scope: :todo_item end class NoUpdateForCollectionClassesTestCase < Minitest::Test def setup ActiveRecord::Base.connection.create_table :todo_lists do |t| t.column :position, :integer end ActiveRecord::Base.connection.create_table :todo_items do |t| t.column :position, :integer t.column :todo_list_id, :integer end ActiveRecord::Base.connection.create_table :todo_item_attachments do |t| t.column :position, :integer t.column :todo_item_id, :integer end ActiveRecord::Base.connection.schema_cache.clear! [TodoList, TodoItem, TodoItemAttachment].each(&:reset_column_information) super end def teardown teardown_db super end end class NoUpdateForCollectionClassesTest < NoUpdateForCollectionClassesTestCase def setup super @list_1, @list_2 = (1..2).map { |counter| TodoList.create!(position: counter) } @item_1, @item_2 = (1..2).map { |counter| TodoItem.create!(position: counter, todo_list_id: @list_1.id) } @attachment_1, @attachment_2 = (1..2).map { |counter| TodoItemAttachment.create!(position: counter, todo_item_id: @item_1.id) } @attachment_3, @attachment_4 = (1..2).map {|counter| TodoItemAttachment.create!(position: counter, todo_item_id: @item_2.id)} end def test_update @item_1.update position: 2 assert_equal 2, @item_1.reload.position assert_equal 1, @item_2.reload.position end def test_no_update_for_single_class_instances TodoItem.acts_as_list_no_update { @item_1.update position: 2 } assert_equal 2, @item_1.reload.position assert_equal 2, @item_2.reload.position end def test_no_update_for_different_class_instances TodoItem.acts_as_list_no_update([TodoItemAttachment]) { update_records! } assert_equal 2, @item_1.reload.position assert_equal 2, @item_2.reload.position assert_equal 2, @attachment_1.reload.position assert_equal 2, @attachment_2.reload.position assert_equal 2, @list_1.reload.position assert_equal 1, @list_2.reload.position end def test_no_update_for_nested_blocks new_list = @list_1.dup new_list.save! TodoItem.acts_as_list_no_update do @list_1.todo_items.reverse.each do |item| new_item = item.dup new_list.todo_items << new_item new_item.save! assert_equal new_item.position, item.reload.position TodoItemAttachment.acts_as_list_no_update do item.todo_item_attachments.reverse.each do |attach| new_attach = attach.dup new_item.todo_item_attachments << new_attach new_attach.save! assert_equal new_attach.position, attach.reload.position end end end end end def test_raising_array_type_error exception = assert_raises ActiveRecord::Acts::List::NoUpdate::ArrayTypeError do TodoList.acts_as_list_no_update(nil) end assert_equal("The first argument must be an array", exception.message ) end def test_non_disparity_classes_error exception = assert_raises ActiveRecord::Acts::List::NoUpdate::DisparityClassesError do TodoList.acts_as_list_no_update([Class]) end assert_equal("The first argument should contain ActiveRecord or ApplicationRecord classes", exception.message ) end private def update_records! @item_1.update position: 2 @attachment_1.update position: 2 @list_1.update position: 2 end end acts_as_list-1.0.4/test/shared_list_sub.rb0000644000004100000410000001455714042233640020704 0ustar www-datawww-data# frozen_string_literal: true module Shared module ListSub def setup (1..4).each do |i| node = ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).new parent_id: 5000 node.pos = i node.save! end end def test_reordering assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id) ListMixin.where(id: 2).first.move_lower assert_equal [1, 3, 2, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id) ListMixin.where(id: 2).first.move_higher assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id) ListMixin.where(id: 1).first.move_to_bottom assert_equal [2, 3, 4, 1], ListMixin.where(parent_id: 5000).order('pos').map(&:id) ListMixin.where(id: 1).first.move_to_top assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id) ListMixin.where(id: 2).first.move_to_bottom assert_equal [1, 3, 4, 2], ListMixin.where(parent_id: 5000).order('pos').map(&:id) ListMixin.where(id: 4).first.move_to_top assert_equal [4, 1, 3, 2], ListMixin.where(parent_id: 5000).order('pos').map(&:id) end def test_move_to_bottom_with_next_to_last_item assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id) ListMixin.where(id: 3).first.move_to_bottom assert_equal [1, 2, 4, 3], ListMixin.where(parent_id: 5000).order('pos').map(&:id) end def test_next_prev assert_equal ListMixin.where(id: 2).first, ListMixin.where(id: 1).first.lower_item assert_nil ListMixin.where(id: 1).first.higher_item assert_equal ListMixin.where(id: 3).first, ListMixin.where(id: 4).first.higher_item assert_nil ListMixin.where(id: 4).first.lower_item end def test_next_prev_not_regular_sequence ListMixin.all.each do |item| item.update pos: item.pos * 5 end assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id) assert_equal [5, 10, 15, 20], ListMixin.where(parent_id: 5000).order('id').map(&:pos) ListMixin.where(id: 2).first.move_lower assert_equal [1, 3, 2, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id) assert_equal [5, 15, 10, 20], ListMixin.where(parent_id: 5000).order('id').map(&:pos) ListMixin.where(id: 2).first.move_higher assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id) assert_equal [5, 10, 15, 20], ListMixin.where(parent_id: 5000).order('id').map(&:pos) ListMixin.where(id: 1).first.move_to_bottom assert_equal [2, 3, 4, 1], ListMixin.where(parent_id: 5000).order('pos').map(&:id) ListMixin.where(id: 1).first.move_to_top assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id) ListMixin.where(id: 2).first.move_to_bottom assert_equal [1, 3, 4, 2], ListMixin.where(parent_id: 5000).order('pos').map(&:id) ListMixin.where(id: 4).first.move_to_top assert_equal [4, 1, 3, 2], ListMixin.where(parent_id: 5000).order('pos').map(&:id) end def test_next_prev_groups li1 = ListMixin.where(id: 1).first li2 = ListMixin.where(id: 2).first li3 = ListMixin.where(id: 3).first li4 = ListMixin.where(id: 4).first assert_equal [li2, li3, li4], li1.lower_items assert_equal [li4], li3.lower_items assert_equal [li2, li3], li1.lower_items(2) assert_equal [], li4.lower_items assert_equal [li2, li1], li3.higher_items assert_equal [li1], li2.higher_items assert_equal [li3, li2], li4.higher_items(2) assert_equal [], li1.higher_items end def test_next_prev_groups_with_same_position li1 = ListMixin.where(id: 1).first li2 = ListMixin.where(id: 2).first li3 = ListMixin.where(id: 3).first li4 = ListMixin.where(id: 4).first li3.update_column(:pos, 2) # Make the same position as li2 assert_equal [1, 2, 2, 4], ListMixin.order(:pos).pluck(:pos) assert_equal [li3, li4], li2.lower_items assert_equal [li2, li4], li3.lower_items assert_equal [li3, li1], li2.higher_items assert_equal [li2, li1], li3.higher_items end def test_injection item = ListMixin.new("parent_id"=>1) assert_equal({ parent_id: 1 }, item.scope_condition) assert_equal "pos", item.position_column end def test_insert_at new = ListMixin.create("parent_id" => 20) assert_equal 1, new.pos new = ListMixinSub1.create("parent_id" => 20) assert_equal 2, new.pos 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_or_nil $default_position, new_noup.pos new4 = ListMixin.create("parent_id" => 20) assert_equal 4, new4.pos new4.insert_at(3) assert_equal 3, new4.pos new.reload assert_equal 4, new.pos new.insert_at(2) assert_equal 2, new.pos new4.reload assert_equal 4, new4.pos new5 = ListMixinSub1.create("parent_id" => 20) assert_equal 5, new5.pos new5.insert_at(1) assert_equal 1, new5.pos new4.reload assert_equal 5, new4.pos new_noup.reload assert_equal_or_nil $default_position, new_noup.pos end def test_delete_middle assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id) ListMixin.where(id: 2).first.destroy assert_equal [1, 3, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id) assert_equal 1, ListMixin.where(id: 1).first.pos assert_equal 2, ListMixin.where(id: 3).first.pos assert_equal 3, ListMixin.where(id: 4).first.pos ListMixin.where(id: 1).first.destroy assert_equal [3, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id) 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 assert_equal TheBaseClass, TheBaseSubclass.new.acts_as_list_class assert_equal TheAbstractSubclass, TheAbstractSubclass.new.acts_as_list_class end end end acts_as_list-1.0.4/test/shared_quoting.rb0000644000004100000410000000067714042233640020544 0ustar www-datawww-data# frozen_string_literal: true module Shared module Quoting def setup 3.times { |counter| QuotedList.create! order: counter } end def test_create assert_equal QuotedList.in_list.size, 3 end # This test execute raw queries involving table name def test_moving item = QuotedList.first item.higher_items item.lower_items item.send :bottom_item # Part of private api end end end acts_as_list-1.0.4/test/shared_array_scope_list.rb0000644000004100000410000001647114042233640022417 0ustar www-datawww-data# frozen_string_literal: true module Shared module ArrayScopeList def setup (1..4).each { |counter| ArrayScopeListMixin.create! pos: counter, parent_id: 5, parent_type: 'ParentClass' } (1..4).each { |counter| ArrayScopeListMixin.create! pos: counter, parent_id: 6, parent_type: 'ParentClass' } end def test_reordering assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) ArrayScopeListMixin.where(id: 2).first.move_lower assert_equal [1, 3, 2, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) ArrayScopeListMixin.where(id: 2).first.move_higher assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) ArrayScopeListMixin.where(id: 1).first.move_to_bottom assert_equal [2, 3, 4, 1], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) ArrayScopeListMixin.where(id: 1).first.move_to_top assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) ArrayScopeListMixin.where(id: 2).first.move_to_bottom assert_equal [1, 3, 4, 2], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) ArrayScopeListMixin.where(id: 4).first.move_to_top assert_equal [4, 1, 3, 2], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) ArrayScopeListMixin.where(id: 4).first.insert_at(4) assert_equal [1, 3, 2, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:pos) end def test_move_to_bottom_with_next_to_last_item assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) ArrayScopeListMixin.where(id: 3).first.move_to_bottom assert_equal [1, 2, 4, 3], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) end def test_next_prev assert_equal ArrayScopeListMixin.where(id: 2).first, ArrayScopeListMixin.where(id: 1).first.lower_item assert_nil ArrayScopeListMixin.where(id: 1).first.higher_item assert_equal ArrayScopeListMixin.where(id: 3).first, ArrayScopeListMixin.where(id: 4).first.higher_item assert_nil ArrayScopeListMixin.where(id: 4).first.lower_item end def test_injection item = ArrayScopeListMixin.new(parent_id: 1, parent_type: 'ParentClass') assert_equal "pos", item.position_column end def test_insert new = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass') assert_equal 1, new.pos assert new.first? assert new.last? new = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass') assert_equal 2, new.pos assert !new.first? assert new.last? new = ArrayScopeListMixin.acts_as_list_no_update { ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass') } assert_equal_or_nil $default_position,new.pos assert_equal $default_position.is_a?(Integer), new.first? assert !new.last? new = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass') assert_equal 3, new.pos assert !new.first? assert new.last? new = ArrayScopeListMixin.create(parent_id: 0, parent_type: 'ParentClass') assert_equal 1, new.pos assert new.first? assert new.last? end def test_insert_at new = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass') assert_equal 1, new.pos new = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass') assert_equal 2, new.pos 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_or_nil $default_position,new_noup.pos new4 = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass') assert_equal 4, new4.pos new4.insert_at(3) assert_equal 3, new4.pos new.reload assert_equal 4, new.pos new.insert_at(2) assert_equal 2, new.pos new4.reload assert_equal 4, new4.pos new5 = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass') assert_equal 5, new5.pos new5.insert_at(1) assert_equal 1, new5.pos new4.reload assert_equal 5, new4.pos new_noup.reload assert_equal_or_nil $default_position, new_noup.pos end def test_delete_middle assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) ArrayScopeListMixin.where(id: 2).first.destroy assert_equal [1, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) assert_equal 1, ArrayScopeListMixin.where(id: 1).first.pos assert_equal 2, ArrayScopeListMixin.where(id: 3).first.pos assert_equal 3, ArrayScopeListMixin.where(id: 4).first.pos ArrayScopeListMixin.where(id: 1).first.destroy assert_equal [3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) 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? assert_equal true, ArrayScopeListMixin.where(id: 1).first.in_list? ArrayScopeListMixin.where(id: 1).first.remove_from_list assert_equal false, ArrayScopeListMixin.where(id: 1).first.in_list? end def test_remove_from_list_should_set_position_to_nil assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) ArrayScopeListMixin.where(id: 2).first.remove_from_list assert_equal 1, ArrayScopeListMixin.where(id: 1).first.pos assert_nil ArrayScopeListMixin.where(id: 2).first.pos assert_equal 2, ArrayScopeListMixin.where(id: 3).first.pos assert_equal 3, ArrayScopeListMixin.where(id: 4).first.pos end def test_remove_before_destroy_does_not_shift_lower_items_twice assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) ArrayScopeListMixin.where(id: 2).first.remove_from_list ArrayScopeListMixin.where(id: 2).first.destroy assert_equal [1, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id) assert_equal 1, ArrayScopeListMixin.where(id: 1).first.pos assert_equal 2, ArrayScopeListMixin.where(id: 3).first.pos assert_equal 3, ArrayScopeListMixin.where(id: 4).first.pos end end end acts_as_list-1.0.4/test/shared_zero_based.rb0000644000004100000410000000565614042233640021175 0ustar www-datawww-data# frozen_string_literal: true module Shared module ZeroBased def setup (1..4).each { |counter| ZeroBasedMixin.create! pos: counter, parent_id: 5 } end def test_insert new = ZeroBasedMixin.create(parent_id: 20) assert_equal 0, new.pos assert new.first? assert new.last? new = ZeroBasedMixin.create(parent_id: 20) assert_equal 1, new.pos assert !new.first? assert new.last? new = ZeroBasedMixin.acts_as_list_no_update { ZeroBasedMixin.create(parent_id: 20) } assert_equal_or_nil $default_position, new.pos assert !new.first? assert !new.last? new = ZeroBasedMixin.create(parent_id: 20) assert_equal 2, new.pos assert !new.first? assert new.last? new = ZeroBasedMixin.create(parent_id: 0) assert_equal 0, new.pos assert new.first? assert new.last? new = ZeroBasedMixin.create(parent_id: 1, pos: -500) assert_equal 0, new.pos assert new.first? assert new.last? end def test_reordering assert_equal [1, 2, 3, 4], ZeroBasedMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 2).first.move_lower assert_equal [1, 3, 2, 4], ZeroBasedMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 2).first.move_higher assert_equal [1, 2, 3, 4], ZeroBasedMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 1).first.move_to_bottom assert_equal [2, 3, 4, 1], ZeroBasedMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 1).first.move_to_top assert_equal [1, 2, 3, 4], ZeroBasedMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 2).first.move_to_bottom assert_equal [1, 3, 4, 2], ZeroBasedMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 4).first.move_to_top assert_equal [4, 1, 3, 2], ZeroBasedMixin.where(parent_id: 5).order('pos').map(&:id) end def test_insert_at new = ZeroBasedMixin.create(parent_id: 20) assert_equal 0, new.pos new = ZeroBasedMixin.create(parent_id: 20) assert_equal 1, new.pos 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_or_nil $default_position, new_noup.pos new4 = ZeroBasedMixin.create(parent_id: 20) assert_equal 3, new4.pos new4.insert_at(2) assert_equal 2, new4.pos new.reload assert_equal 3, new.pos new.insert_at(2) assert_equal 2, new.pos new4.reload assert_equal 3, new4.pos new5 = ListMixin.create(parent_id: 20) assert_equal 4, new5.pos new5.insert_at(1) assert_equal 1, new5.pos new4.reload assert_equal 4, new4.pos new_noup.reload assert_equal_or_nil $default_position, new_noup.pos end end end acts_as_list-1.0.4/test/test_joined_list.rb0000644000004100000410000000310114042233640021053 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class Section < ActiveRecord::Base has_many :items acts_as_list scope :visible, -> { where(visible: true) } end class Item < ActiveRecord::Base belongs_to :section acts_as_list scope: :section scope :visible, -> { where(visible: true).joins(:section).merge(Section.visible) } end class JoinedTestCase < Minitest::Test def setup ActiveRecord::Base.connection.create_table :sections do |t| t.column :position, :integer t.column :visible, :boolean, default: true end ActiveRecord::Base.connection.create_table :items do |t| t.column :position, :integer t.column :section_id, :integer t.column :visible, :boolean, default: true end ActiveRecord::Base.connection.schema_cache.clear! [Section, Item].each(&:reset_column_information) super end def teardown teardown_db super end end # joining the relation returned by `#higher_items` or `#lower_items` to another table # previously could result in ambiguous column names in the query class TestHigherLowerItems < JoinedTestCase def test_higher_items section = Section.create item1 = Item.create section: section item2 = Item.create section: section item3 = Item.create section: section assert_equal item3.higher_items.visible, [item2, item1] end def test_lower_items section = Section.create item1 = Item.create section: section item2 = Item.create section: section item3 = Item.create section: section assert_equal item1.lower_items.visible, [item2, item3] end end acts_as_list-1.0.4/test/test_list.rb0000644000004100000410000007615314042233640017544 0ustar www-datawww-data# frozen_string_literal: true # NOTE: following now done in helper.rb (better Readability) require 'helper' def setup_db(position_options = {}) $default_position = position_options[:default] # sqlite cannot drop/rename/alter columns and add constraints after table creation sqlite = ENV.fetch("DB", "sqlite") == "sqlite" # AR caches columns options like defaults etc. Clear them! ActiveRecord::Base.connection.create_table :mixins do |t| t.column :pos, :integer, **position_options unless position_options[:positive] && sqlite t.column :active, :boolean, default: true t.column :parent_id, :integer t.column :parent_type, :string t.column :created_at, :datetime t.column :updated_at, :datetime t.column :state, :integer end if position_options[:unique] && !(sqlite && position_options[:positive]) ActiveRecord::Base.connection.add_index :mixins, :pos, unique: true end if position_options[:positive] if sqlite # SQLite cannot add constraint after table creation, also cannot add unique inside ADD COLUMN ActiveRecord::Base.connection.execute('ALTER TABLE mixins ADD COLUMN pos integer8 NOT NULL CHECK (pos > 0) DEFAULT 1') ActiveRecord::Base.connection.execute('CREATE UNIQUE INDEX index_mixins_on_pos ON mixins(pos)') else ActiveRecord::Base.connection.execute('ALTER TABLE mixins ADD CONSTRAINT pos_check CHECK (pos > 0)') end end # This table is used to test table names and column names quoting ActiveRecord::Base.connection.create_table 'table-name' do |t| t.column :order, :integer end # This table is used to test table names with different primary_key columns ActiveRecord::Base.connection.create_table 'altid-table', primary_key: 'altid' do |t| t.column :pos, :integer t.column :created_at, :datetime t.column :updated_at, :datetime end ActiveRecord::Base.connection.add_index 'altid-table', :pos, unique: true mixins = [ Mixin, ListMixin, ListMixinSub1, ListMixinSub2, ListWithStringScopeMixin, ArrayScopeListMixin, ZeroBasedMixin, DefaultScopedMixin, EnumArrayScopeListMixin, DefaultScopedWhereMixin, TopAdditionMixin, NoAdditionMixin, QuotedList, TouchDisabledMixin ] ActiveRecord::Base.connection.schema_cache.clear! mixins.each do |klass| klass.reset_column_information end end def setup_db_with_default setup_db default: 0 end class Mixin < ActiveRecord::Base self.table_name = 'mixins' end class ListMixin < Mixin acts_as_list column: "pos", scope: :parent end class TouchDisabledMixin < Mixin acts_as_list column: "pos", touch_on_update: false end class ListMixinSub1 < ListMixin end class ListMixinSub2 < ListMixin validates :pos, presence: true end class ListMixinError < ListMixin validates :state, presence: true end class ListWithStringScopeMixin < Mixin acts_as_list column: "pos", scope: 'parent_id = #{parent_id}' end class ArrayScopeListMixin < Mixin acts_as_list column: "pos", scope: [:parent_id, :parent_type] end class ArrayScopeListWithHashMixin < Mixin acts_as_list column: "pos", scope: [:parent_id, state: nil] end class EnumArrayScopeListMixin < Mixin STATE_VALUES = %w(active archived) enum state: STATE_VALUES acts_as_list column: "pos", scope: [:parent_id, :state] end class ZeroBasedMixin < Mixin acts_as_list column: "pos", top_of_list: 0, scope: [:parent_id] end class DefaultScopedMixin < Mixin acts_as_list column: "pos" default_scope { order('pos ASC') } end class DefaultScopedWhereMixin < Mixin acts_as_list column: "pos" default_scope { order('pos ASC').where(active: true) } def self.for_active_false_tests unscope(:where).where(active: false) end end class SequentialUpdatesDefault < Mixin acts_as_list column: "pos" end class SequentialUpdatesAltId < ActiveRecord::Base self.table_name = "altid-table" self.primary_key = "altid" acts_as_list column: "pos" end class SequentialUpdatesAltIdTouchDisabled < SequentialUpdatesAltId acts_as_list column: "pos", touch_on_update: false end class SequentialUpdatesFalseMixin < Mixin acts_as_list column: "pos", sequential_updates: false end class TopAdditionMixin < Mixin acts_as_list column: "pos", add_new_at: :top, scope: :parent_id end class NoAdditionMixin < Mixin acts_as_list column: "pos", add_new_at: nil, scope: :parent_id end ## # The way we track changes to # scope and position can get tripped up # by someone using update within # a callback because it causes multiple passes # through the callback chain module CallbackMixin def self.included(base) base.send :include, InstanceMethods base.after_create :change_field end module InstanceMethods def change_field # doesn't matter what column changes, just # need to change something self.update active: !self.active end end end class TheAbstractClass < ActiveRecord::Base self.abstract_class = true self.table_name = 'mixins' end class TheAbstractSubclass < TheAbstractClass acts_as_list column: "pos", scope: :parent end class TheBaseClass < ActiveRecord::Base self.table_name = 'mixins' acts_as_list column: "pos", scope: :parent end class TheBaseSubclass < TheBaseClass end class QuotedList < ActiveRecord::Base self.table_name = 'table-name' acts_as_list column: :order end class ActsAsListTestCase < Minitest::Test # No default test required as this class is abstract. # Need for test/unit. undef_method :default_test if method_defined?(:default_test) def teardown teardown_db end end class ZeroBasedTest < ActsAsListTestCase include Shared::ZeroBased def setup setup_db super end end class ZeroBasedTestWithDefault < ActsAsListTestCase include Shared::ZeroBased def setup setup_db_with_default super end end class ListTest < ActsAsListTestCase include Shared::List def setup setup_db super end def test_insert_race_condition # the bigger n is the more likely we will have a race condition n = 1000 (1..n).each do |counter| node = ListMixin.new parent_id: 1 node.pos = counter node.save! end wait_for_it = true threads = [] 4.times do |i| threads << Thread.new do true while wait_for_it ActiveRecord::Base.connection_pool.with_connection do |c| n.times do begin ListMixin.where(parent_id: 1).order('pos').last.insert_at(1) rescue Exception # ignore SQLite3::SQLException due to table locking end end end end end wait_for_it = false threads.each(&:join) assert_equal((1..n).to_a, ListMixin.where(parent_id: 1).order('pos').map(&:pos)) end end class ListWithCallbackTest < ActsAsListTestCase include Shared::List def setup ListMixin.send(:include, CallbackMixin) setup_db super end end class ListTestWithDefault < ActsAsListTestCase include Shared::List def setup setup_db_with_default super end end class ListSubTest < ActsAsListTestCase include Shared::ListSub def setup setup_db super end end class ListSubTestWithDefault < ActsAsListTestCase include Shared::ListSub def setup setup_db_with_default super end end class ArrayScopeListTest < ActsAsListTestCase include Shared::ArrayScopeList def setup setup_db super end end class ArrayScopeListTestWithDefault < ActsAsListTestCase include Shared::ArrayScopeList def setup setup_db_with_default super end end class QuotingTestList < ActsAsListTestCase include Shared::Quoting def setup setup_db_with_default super end end class DefaultScopedTest < ActsAsListTestCase def setup setup_db (1..4).each { |counter| DefaultScopedMixin.create!({pos: counter}) } end def test_insert new = DefaultScopedMixin.create assert_equal 5, new.pos assert !new.first? assert new.last? new = DefaultScopedMixin.create assert_equal 6, new.pos assert !new.first? assert new.last? new = DefaultScopedMixin.acts_as_list_no_update { DefaultScopedMixin.create } assert_equal_or_nil $default_position, new.pos assert_equal $default_position.is_a?(Integer), new.first? assert !new.last? new = DefaultScopedMixin.create assert_equal 7, new.pos assert !new.first? assert new.last? end def test_reordering assert_equal [1, 2, 3, 4], DefaultScopedMixin.all.map(&:id) DefaultScopedMixin.where(id: 2).first.move_lower assert_equal [1, 3, 2, 4], DefaultScopedMixin.all.map(&:id) DefaultScopedMixin.where(id: 2).first.move_higher assert_equal [1, 2, 3, 4], DefaultScopedMixin.all.map(&:id) DefaultScopedMixin.where(id: 1).first.move_to_bottom assert_equal [2, 3, 4, 1], DefaultScopedMixin.all.map(&:id) DefaultScopedMixin.where(id: 1).first.move_to_top assert_equal [1, 2, 3, 4], DefaultScopedMixin.all.map(&:id) DefaultScopedMixin.where(id: 2).first.move_to_bottom assert_equal [1, 3, 4, 2], DefaultScopedMixin.all.map(&:id) DefaultScopedMixin.where(id: 4).first.move_to_top assert_equal [4, 1, 3, 2], DefaultScopedMixin.all.map(&:id) end def test_insert_at new = DefaultScopedMixin.create assert_equal 5, new.pos new = DefaultScopedMixin.create assert_equal 6, new.pos new_noup = DefaultScopedMixin.acts_as_list_no_update { DefaultScopedMixin.create } assert_equal_or_nil $default_position, new_noup.pos new = DefaultScopedMixin.create assert_equal 7, new.pos new4 = DefaultScopedMixin.create assert_equal 8, new4.pos new4.insert_at(2) assert_equal 2, new4.pos new.reload assert_equal 8, new.pos new.insert_at(2) assert_equal 2, new.pos new4.reload assert_equal 3, new4.pos new5 = DefaultScopedMixin.create assert_equal 9, new5.pos new5.insert_at(1) assert_equal 1, new5.pos new4.reload assert_equal 4, new4.pos new_noup.reload assert_equal_or_nil $default_position, new_noup.pos end def test_find_or_create_doesnt_raise_deprecation_warning assert_no_deprecation_warning_raised_by('ActiveRecord deprecation warning raised when using `find_or_create_by` when we didn\'t expect it') do DefaultScopedMixin.find_or_create_by(pos: 5) end end def test_update_position assert_equal [1, 2, 3, 4], DefaultScopedMixin.all.map(&:id) DefaultScopedMixin.where(id: 2).first.set_list_position(4) assert_equal [1, 3, 4, 2], DefaultScopedMixin.all.map(&:id) DefaultScopedMixin.where(id: 2).first.set_list_position(2) assert_equal [1, 2, 3, 4], DefaultScopedMixin.all.map(&:id) DefaultScopedMixin.where(id: 1).first.set_list_position(4) assert_equal [2, 3, 4, 1], DefaultScopedMixin.all.map(&:id) DefaultScopedMixin.where(id: 1).first.set_list_position(1) assert_equal [1, 2, 3, 4], DefaultScopedMixin.all.map(&:id) end end class DefaultScopedWhereTest < ActsAsListTestCase def setup setup_db (1..4).each { |counter| DefaultScopedWhereMixin.create! pos: counter, active: false } end def test_insert new = DefaultScopedWhereMixin.create assert_equal 5, new.pos assert !new.first? assert new.last? new = DefaultScopedWhereMixin.create assert_equal 6, new.pos assert !new.first? assert new.last? new = DefaultScopedWhereMixin.acts_as_list_no_update { DefaultScopedWhereMixin.create } assert_equal_or_nil $default_position, new.pos assert_equal $default_position.is_a?(Integer), new.first? assert !new.last? new = DefaultScopedWhereMixin.create assert_equal 7, new.pos assert !new.first? assert new.last? end def test_reordering assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id) DefaultScopedWhereMixin.for_active_false_tests.where(id: 2).first.move_lower assert_equal [1, 3, 2, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id) DefaultScopedWhereMixin.for_active_false_tests.where(id: 2).first.move_higher assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id) DefaultScopedWhereMixin.for_active_false_tests.where(id: 1).first.move_to_bottom assert_equal [2, 3, 4, 1], DefaultScopedWhereMixin.for_active_false_tests.map(&:id) DefaultScopedWhereMixin.for_active_false_tests.where(id: 1).first.move_to_top assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id) DefaultScopedWhereMixin.for_active_false_tests.where(id: 2).first.move_to_bottom assert_equal [1, 3, 4, 2], DefaultScopedWhereMixin.for_active_false_tests.map(&:id) DefaultScopedWhereMixin.for_active_false_tests.where(id: 4).first.move_to_top assert_equal [4, 1, 3, 2], DefaultScopedWhereMixin.for_active_false_tests.map(&:id) end def test_insert_at new = DefaultScopedWhereMixin.create assert_equal 5, new.pos new = DefaultScopedWhereMixin.create assert_equal 6, new.pos new = DefaultScopedWhereMixin.create assert_equal 7, new.pos new_noup = DefaultScopedWhereMixin.acts_as_list_no_update { DefaultScopedWhereMixin.create } assert_equal_or_nil $default_position, new_noup.pos new4 = DefaultScopedWhereMixin.create assert_equal 8, new4.pos new4.insert_at(2) assert_equal 2, new4.pos new.reload assert_equal 8, new.pos new.insert_at(2) assert_equal 2, new.pos new4.reload assert_equal 3, new4.pos new5 = DefaultScopedWhereMixin.create assert_equal 9, new5.pos new5.insert_at(1) assert_equal 1, new5.pos new4.reload assert_equal 4, new4.pos new_noup.reload assert_equal_or_nil $default_position, new_noup.pos end def test_find_or_create_doesnt_raise_deprecation_warning assert_no_deprecation_warning_raised_by('ActiveRecord deprecation warning raised when using `find_or_create_by` when we didn\'t expect it') do DefaultScopedWhereMixin.find_or_create_by(pos: 5) end end def test_update_position assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id) DefaultScopedWhereMixin.for_active_false_tests.where(id: 2).first.set_list_position(4) assert_equal [1, 3, 4, 2], DefaultScopedWhereMixin.for_active_false_tests.map(&:id) DefaultScopedWhereMixin.for_active_false_tests.where(id: 2).first.set_list_position(2) assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id) DefaultScopedWhereMixin.for_active_false_tests.where(id: 1).first.set_list_position(4) assert_equal [2, 3, 4, 1], DefaultScopedWhereMixin.for_active_false_tests.map(&:id) DefaultScopedWhereMixin.for_active_false_tests.where(id: 1).first.set_list_position(1) assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.for_active_false_tests.map(&:id) end end class MultiDestroyTest < ActsAsListTestCase def setup setup_db end # example: # # class TodoList < ActiveRecord::Base # has_many :todo_items, order: "position" # accepts_nested_attributes_for :todo_items, allow_destroy: true # end # # class TodoItem < ActiveRecord::Base # belongs_to :todo_list # acts_as_list scope: :todo_list # end # # Assume that there are three items. # The user mark two items as deleted, click save button, form will be post: # # todo_list.todo_items_attributes = [ # {id: 1, _destroy: true}, # {id: 2, _destroy: true} # ] # # Save toto_list, the position of item #3 should eql 1. # def test_destroy new1 = DefaultScopedMixin.create assert_equal 1, new1.pos new2 = DefaultScopedMixin.create assert_equal 2, new2.pos new3 = DefaultScopedMixin.create assert_equal 3, new3.pos new4 = DefaultScopedMixin.create assert_equal 4, new4.pos new1.destroy new2.destroy new3.reload new4.reload assert_equal 1, new3.pos assert_equal 2, new4.pos DefaultScopedMixin.acts_as_list_no_update { new3.destroy } new4.reload assert_equal 2, new4.pos end end #class TopAdditionMixin < Mixin class TopAdditionTest < ActsAsListTestCase include Shared::TopAddition def setup setup_db super end end class TopAdditionTestWithDefault < ActsAsListTestCase include Shared::TopAddition def setup setup_db_with_default super end end class NoAdditionTest < ActsAsListTestCase include Shared::NoAddition def setup setup_db super end end class MultipleListsTest < ActsAsListTestCase def setup setup_db (1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 1} (1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 2} end def test_check_scope_order assert_equal [1, 2, 3, 4], ListMixin.where(:parent_id => 1).order('pos').map(&:id) assert_equal [5, 6, 7, 8], ListMixin.where(:parent_id => 2).order('pos').map(&:id) ListMixin.find(4).update :parent_id => 2, :pos => 2 assert_equal [1, 2, 3], ListMixin.where(:parent_id => 1).order('pos').map(&:id) assert_equal [5, 4, 6, 7, 8], ListMixin.where(:parent_id => 2).order('pos').map(&:id) end def test_check_scope_position assert_equal [1, 2, 3, 4], ListMixin.where(:parent_id => 1).map(&:pos) assert_equal [1, 2, 3, 4], ListMixin.where(:parent_id => 2).map(&:pos) ListMixin.find(4).update :parent_id => 2, :pos => 2 assert_equal [1, 2, 3], ListMixin.where(:parent_id => 1).order('pos').map(&:pos) assert_equal [1, 2, 3, 4, 5], ListMixin.where(:parent_id => 2).order('pos').map(&:pos) end def test_find_or_create_doesnt_raise_deprecation_warning assert_no_deprecation_warning_raised_by('ActiveRecord deprecation warning raised when using `find_or_create_by` when we didn\'t expect it') do ListMixin.where(:parent_id => 1).find_or_create_by(pos: 5) end end end class EnumArrayScopeListMixinTest < ActsAsListTestCase def setup setup_db EnumArrayScopeListMixin.create! :parent_id => 1, :state => EnumArrayScopeListMixin.states['active'] EnumArrayScopeListMixin.create! :parent_id => 1, :state => EnumArrayScopeListMixin.states['archived'] EnumArrayScopeListMixin.create! :parent_id => 2, :state => EnumArrayScopeListMixin.states["active"] EnumArrayScopeListMixin.create! :parent_id => 2, :state => EnumArrayScopeListMixin.states["archived"] end def test_positions assert_equal [1], EnumArrayScopeListMixin.where(:parent_id => 1, :state => EnumArrayScopeListMixin.states['active']).map(&:pos) assert_equal [1], EnumArrayScopeListMixin.where(:parent_id => 1, :state => EnumArrayScopeListMixin.states['archived']).map(&:pos) assert_equal [1], EnumArrayScopeListMixin.where(:parent_id => 2, :state => EnumArrayScopeListMixin.states['active']).map(&:pos) assert_equal [1], EnumArrayScopeListMixin.where(:parent_id => 2, :state => EnumArrayScopeListMixin.states['archived']).map(&:pos) end def test_update_state active_item = EnumArrayScopeListMixin.find_by(:parent_id => 2, :state => EnumArrayScopeListMixin.states['active']) active_item.update(state: EnumArrayScopeListMixin.states['archived']) assert_equal [1, 2], EnumArrayScopeListMixin.where(:parent_id => 2, :state => EnumArrayScopeListMixin.states['archived']).map(&:pos).sort end end class MultipleListsArrayScopeTest < ActsAsListTestCase def setup setup_db (1..4).each { |counter| ArrayScopeListMixin.create! :pos => counter,:parent_id => 1, :parent_type => 'anything'} (1..4).each { |counter| ArrayScopeListMixin.create! :pos => counter,:parent_id => 2, :parent_type => 'something'} (1..4).each { |counter| ArrayScopeListMixin.create! :pos => counter,:parent_id => 3, :parent_type => 'anything'} end def test_order_after_all_scope_properties_are_changed assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').order('pos').map(&:id) assert_equal [5, 6, 7, 8], ArrayScopeListMixin.where(:parent_id => 2, :parent_type => 'something').order('pos').map(&:id) ArrayScopeListMixin.find(2).update :parent_id => 2, :pos => 2,:parent_type => 'something' assert_equal [1, 3, 4], ArrayScopeListMixin.where(:parent_id => 1,:parent_type => 'anything').order('pos').map(&:id) assert_equal [5, 2, 6, 7, 8], ArrayScopeListMixin.where(:parent_id => 2,:parent_type => 'something').order('pos').map(&:id) end def test_position_after_all_scope_properties_are_changed assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').map(&:pos) assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 2, :parent_type => 'something').map(&:pos) ArrayScopeListMixin.find(4).update :parent_id => 2, :pos => 2, :parent_type => 'something' assert_equal [1, 2, 3], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').order('pos').map(&:pos) assert_equal [1, 2, 3, 4, 5], ArrayScopeListMixin.where(:parent_id => 2, :parent_type => 'something').order('pos').map(&:pos) end def test_order_after_one_scope_property_is_changed assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').order('pos').map(&:id) assert_equal [9, 10, 11, 12], ArrayScopeListMixin.where(:parent_id => 3, :parent_type => 'anything').order('pos').map(&:id) ArrayScopeListMixin.find(2).update :parent_id => 3, :pos => 2 assert_equal [1, 3, 4], ArrayScopeListMixin.where(:parent_id => 1,:parent_type => 'anything').order('pos').map(&:id) assert_equal [9, 2, 10, 11, 12], ArrayScopeListMixin.where(:parent_id => 3,:parent_type => 'anything').order('pos').map(&:id) end def test_position_after_one_scope_property_is_changed assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').map(&:pos) assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 3, :parent_type => 'anything').map(&:pos) ArrayScopeListMixin.find(4).update :parent_id => 3, :pos => 2 assert_equal [1, 2, 3], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').order('pos').map(&:pos) assert_equal [1, 2, 3, 4, 5], ArrayScopeListMixin.where(:parent_id => 3, :parent_type => 'anything').order('pos').map(&:pos) end def test_order_after_moving_to_empty_list assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').order('pos').map(&:id) assert_equal [], ArrayScopeListMixin.where(:parent_id => 4, :parent_type => 'anything').order('pos').map(&:id) ArrayScopeListMixin.find(2).update :parent_id => 4, :pos => 1 assert_equal [1, 3, 4], ArrayScopeListMixin.where(:parent_id => 1,:parent_type => 'anything').order('pos').map(&:id) assert_equal [2], ArrayScopeListMixin.where(:parent_id => 4,:parent_type => 'anything').order('pos').map(&:id) end def test_position_after_moving_to_empty_list assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').map(&:pos) assert_equal [], ArrayScopeListMixin.where(:parent_id => 4, :parent_type => 'anything').map(&:pos) ArrayScopeListMixin.find(2).update :parent_id => 4, :pos => 1 assert_equal [1, 2, 3], ArrayScopeListMixin.where(:parent_id => 1, :parent_type => 'anything').order('pos').map(&:pos) assert_equal [1], ArrayScopeListMixin.where(:parent_id => 4, :parent_type => 'anything').order('pos').map(&:pos) end end class ArrayScopeListWithHashTest def setup setup_db @obj1 = ArrayScopeListWithHashMixin.create! :pos => counter, :parent_id => 1, :state => nil @obj2 = ArrayScopeListWithHashMixin.create! :pos => counter, :parent_id => 1, :state => 'anything' end def test_scope_condition_correct [@obj1, @obj2].each do |obj| assert_equal({ :parent_id => 1, :state => nil }, obj.scope_condition) end end end require 'timecop' class TouchTest < ActsAsListTestCase def setup setup_db Timecop.freeze(yesterday) do 4.times { ListMixin.create! } end end def now @now ||= Time.current.change(usec: 0) end def yesterday @yesterday ||= 1.day.ago end def updated_ats ListMixin.order(:id).pluck(:updated_at) end def test_moving_item_lower_touches_self_and_lower_item Timecop.freeze(now) do ListMixin.first.move_lower updated_ats[0..1].each do |updated_at| assert_equal updated_at.to_i, now.to_i end updated_ats[2..3].each do |updated_at| assert_equal updated_at.to_i, yesterday.to_i end end end def test_moving_item_higher_touches_self_and_higher_item Timecop.freeze(now) do ListMixin.all.second.move_higher updated_ats[0..1].each do |updated_at| assert_equal updated_at.to_i, now.to_i end updated_ats[2..3].each do |updated_at| assert_equal updated_at.to_i, yesterday.to_i end end end def test_moving_item_to_bottom_touches_all_other_items Timecop.freeze(now) do ListMixin.first.move_to_bottom updated_ats.each do |updated_at| assert_equal updated_at.to_i, now.to_i end end end def test_moving_item_to_top_touches_all_other_items Timecop.freeze(now) do ListMixin.last.move_to_top updated_ats.each do |updated_at| assert_equal updated_at.to_i, now.to_i end end end def test_removing_item_touches_all_lower_items Timecop.freeze(now) do ListMixin.all.third.remove_from_list updated_ats[0..1].each do |updated_at| assert_equal updated_at.to_i, yesterday.to_i end updated_ats[2..2].each do |updated_at| assert_equal updated_at.to_i, now.to_i end end end end class TouchDisabledTest < ActsAsListTestCase def setup setup_db Timecop.freeze(yesterday) do 4.times { TouchDisabledMixin.create! } end end def now @now ||= Time.current.change(usec: 0) end def yesterday @yesterday ||= 1.day.ago end def updated_ats TouchDisabledMixin.order(:id).pluck(:updated_at) end def positions ListMixin.order(:id).pluck(:pos) end def test_deleting_item_does_not_touch_higher_items Timecop.freeze(now) do TouchDisabledMixin.first.destroy updated_ats.each do |updated_at| assert_equal updated_at.to_i, yesterday.to_i end assert_equal positions, [1, 2, 3] end end end class ActsAsListTopTest < ActsAsListTestCase def setup setup_db end def test_acts_as_list_top assert_equal 1, TheBaseSubclass.new.acts_as_list_top assert_equal 0, ZeroBasedMixin.new.acts_as_list_top end def test_class_acts_as_list_top assert_equal 1, TheBaseSubclass.acts_as_list_top assert_equal 0, ZeroBasedMixin.acts_as_list_top end end class NilPositionTest < ActsAsListTestCase def setup setup_db end def test_nil_position_ordering new1 = DefaultScopedMixin.create pos: nil new2 = DefaultScopedMixin.create pos: nil new3 = DefaultScopedMixin.create pos: nil DefaultScopedMixin.update_all(pos: nil) assert_equal [nil, nil, nil], DefaultScopedMixin.all.map(&:pos) new1.reload.pos = 1 new1.save new3.reload.pos = 1 new3.save assert_equal [1, 2], DefaultScopedMixin.where("pos IS NOT NULL").map(&:pos) assert_equal [3, 1], DefaultScopedMixin.where("pos IS NOT NULL").map(&:id) assert_nil new2.reload.pos new2.reload.pos = 1 new2.save assert_equal [1, 2, 3], DefaultScopedMixin.all.map(&:pos) assert_equal [2, 3, 1], DefaultScopedMixin.all.map(&:id) end end class SequentialUpdatesOptionDefaultTest < ActsAsListTestCase def setup setup_db end def test_sequential_updates_default_to_false_without_unique_index assert_equal false, SequentialUpdatesDefault.new.send(:sequential_updates?) end end class SequentialUpdatesMixinNotNullUniquePositiveConstraintsTest < ActsAsListTestCase def setup setup_db null: false, unique: true, positive: true (1..4).each { |counter| SequentialUpdatesDefault.create!({pos: counter}) } end def test_sequential_updates_default_to_true_with_unique_index assert_equal true, SequentialUpdatesDefault.new.send(:sequential_updates?) end def test_sequential_updates_option_override_with_false assert_equal false, SequentialUpdatesFalseMixin.new.send(:sequential_updates?) end def test_insert_at new = SequentialUpdatesDefault.create assert_equal 5, new.pos new.insert_at(1) assert_equal 1, new.pos new.insert_at(5) assert_equal 5, new.pos new.insert_at(3) assert_equal 3, new.pos end def test_move_to_bottom item = SequentialUpdatesDefault.order(:pos).first item.move_to_bottom assert_equal 4, item.pos end def test_move_to_top new_item = SequentialUpdatesDefault.create! assert_equal 5, new_item.pos new_item.move_to_top assert_equal 1, new_item.pos end def test_destroy new_item = SequentialUpdatesDefault.create assert_equal 5, new_item.pos new_item.insert_at(2) assert_equal 2, new_item.pos new_item.destroy assert_equal [1,2,3,4], SequentialUpdatesDefault.all.map(&:pos).sort end def test_exception_on_wrong_position new_item = SequentialUpdatesDefault.create assert_raises ArgumentError do new_item.insert_at(0) end end class SequentialUpdatesMixinNotNullUniquePositiveConstraintsTest < ActsAsListTestCase def setup setup_db null: false, unique: true, positive: true (1..4).each { |counter| SequentialUpdatesAltId.create!({pos: counter}) } end def test_sequential_updates_default_to_true_with_unique_index assert_equal true, SequentialUpdatesAltId.new.send(:sequential_updates?) end def test_insert_at new = SequentialUpdatesAltId.create assert_equal 5, new.pos new.insert_at(1) assert_equal 1, new.pos new.insert_at(5) assert_equal 5, new.pos new.insert_at(3) assert_equal 3, new.pos end def test_create_at_top new = SequentialUpdatesAltId.create!(pos: 1) assert_equal 1, new.pos end def test_move_to_bottom item = SequentialUpdatesAltId.order(:pos).first item.move_to_bottom assert_equal 4, item.pos end def test_move_to_top new_item = SequentialUpdatesAltId.create! assert_equal 5, new_item.pos new_item.move_to_top assert_equal 1, new_item.pos end def test_destroy new_item = SequentialUpdatesAltId.create assert_equal 5, new_item.pos new_item.insert_at(2) assert_equal 2, new_item.pos new_item.destroy assert_equal [1,2,3,4], SequentialUpdatesAltId.all.map(&:pos).sort end end class SequentialUpdatesAltIdTouchDisabledTest < ActsAsListTestCase def setup setup_db Timecop.freeze(yesterday) do 4.times { SequentialUpdatesAltIdTouchDisabled.create! } end end def now @now ||= Time.current.change(usec: 0) end def yesterday @yesterday ||= 1.day.ago end def updated_ats SequentialUpdatesAltIdTouchDisabled.order(:altid).pluck(:updated_at) end def positions SequentialUpdatesAltIdTouchDisabled.order(:altid).pluck(:pos) end def test_sequential_updates_default_to_true_with_unique_index assert_equal true, SequentialUpdatesAltIdTouchDisabled.new.send(:sequential_updates?) end def test_deleting_item_does_not_touch_higher_items Timecop.freeze(now) do SequentialUpdatesAltIdTouchDisabled.first.destroy updated_ats.each do |updated_at| assert_equal updated_at.to_i, yesterday.to_i end assert_equal positions, [1, 2, 3] end end end end acts_as_list-1.0.4/test/test_default_scope_with_select.rb0000644000004100000410000000135614042233640023771 0ustar www-datawww-datarequire 'helper' class Animal < ActiveRecord::Base acts_as_list default_scope -> { select(:name) } end class DefaultScopeWithSelectTest < Minitest::Test def setup ActiveRecord::Base.connection.create_table :animals do |t| t.column :position, :integer t.column :name, :string end ActiveRecord::Base.connection.schema_cache.clear! Animal.reset_column_information super end def teardown teardown_db super end def test_default_scope_with_select animal1 = Animal.create name: 'Fox' animal2 = Animal.create name: 'Panda' animal3 = Animal.create name: 'Wildebeast' assert_equal 1, animal1.position assert_equal 2, animal2.position assert_equal 3, animal3.position end end acts_as_list-1.0.4/test/test_no_update_for_scope_destruction.rb0000644000004100000410000000416414042233640025222 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class DestructionTodoList < ActiveRecord::Base has_many :destruction_todo_items, dependent: :destroy has_many :destruction_tada_items, dependent: :destroy end class DestructionTodoItem < ActiveRecord::Base belongs_to :destruction_todo_list acts_as_list scope: :destruction_todo_list end class DestructionTadaItem < ActiveRecord::Base belongs_to :destruction_todo_list acts_as_list scope: [:destruction_todo_list_id, :enabled] end class NoUpdateForScopeDestructionTestCase < Minitest::Test def setup ActiveRecord::Base.connection.create_table :destruction_todo_lists do |t| end ActiveRecord::Base.connection.create_table :destruction_todo_items do |t| t.column :position, :integer t.column :destruction_todo_list_id, :integer end ActiveRecord::Base.connection.create_table :destruction_tada_items do |t| t.column :position, :integer t.column :destruction_todo_list_id, :integer t.column :enabled, :boolean end ActiveRecord::Base.connection.schema_cache.clear! [DestructionTodoList, DestructionTodoItem, DestructionTadaItem].each(&:reset_column_information) super end def teardown teardown_db super end class NoUpdateForScopeDestructionTest < NoUpdateForScopeDestructionTestCase def setup super @list = DestructionTodoList.create! @todo_item_1 = DestructionTodoItem.create! position: 1, destruction_todo_list_id: @list.id @tada_item_1 = DestructionTadaItem.create! position: 1, destruction_todo_list_id: @list.id, enabled: true end def test_no_update_children_when_parent_destroyed DestructionTodoItem.any_instance.expects(:decrement_positions_on_lower_items).never DestructionTadaItem.any_instance.expects(:decrement_positions_on_lower_items).never assert @list.destroy end def test_update_children_when_sibling_destroyed @todo_item_1.expects(:decrement_positions_on_lower_items).once @tada_item_1.expects(:decrement_positions_on_lower_items).once assert @todo_item_1.destroy assert @tada_item_1.destroy end end end acts_as_list-1.0.4/test/test_scope_with_user_defined_foreign_key.rb0000644000004100000410000000221714042233640026020 0ustar www-datawww-datarequire 'helper' class Checklist < ActiveRecord::Base has_many :checklist_items, foreign_key: 'list_id', inverse_of: :checklist end class ChecklistItem < ActiveRecord::Base belongs_to :checklist, foreign_key: 'list_id', inverse_of: :checklist_items acts_as_list scope: :checklist end class ScopeWithUserDefinedForeignKeyTest < Minitest::Test def setup ActiveRecord::Base.connection.create_table :checklists do |t| end ActiveRecord::Base.connection.create_table :checklist_items do |t| t.column :list_id, :integer t.column :position, :integer end ActiveRecord::Base.connection.schema_cache.clear! [Checklist, ChecklistItem].each(&:reset_column_information) super end def teardown teardown_db super end def test_scope_with_user_defined_foreign_key checklist = Checklist.create checklist_item_1 = checklist.checklist_items.create checklist_item_2 = checklist.checklist_items.create checklist_item_3 = checklist.checklist_items.create assert_equal 1, checklist_item_1.position assert_equal 2, checklist_item_2.position assert_equal 3, checklist_item_3.position end end acts_as_list-1.0.4/test/shared_no_addition.rb0000644000004100000410000000154414042233640021337 0ustar www-datawww-data# frozen_string_literal: true module Shared module NoAddition def setup (1..4).each { |counter| NoAdditionMixin.create! pos: counter, parent_id: 5 } end def test_insert new = NoAdditionMixin.create(parent_id: 20) assert_nil new.pos assert !new.in_list? new = NoAdditionMixin.create(parent_id: 20) assert_nil new.pos end def test_update_does_not_add_to_list new = NoAdditionMixin.create(parent_id: 20) new.update_attribute(:updated_at, Time.now) # force some change new.reload assert !new.in_list? end def test_update_scope_does_not_add_to_list new = NoAdditionMixin.create new.update_attribute(:parent_id, 20) new.reload assert !new.in_list? new.update_attribute(:parent_id, 5) new.reload assert !new.in_list? end end end acts_as_list-1.0.4/test/shared.rb0000644000004100000410000000063214042233640016765 0ustar www-datawww-data# frozen_string_literal: true # Common shared behaviour. module Shared autoload :List, 'shared_list' autoload :ListSub, 'shared_list_sub' autoload :ZeroBased, 'shared_zero_based' autoload :ArrayScopeList, 'shared_array_scope_list' autoload :TopAddition, 'shared_top_addition' autoload :NoAddition, 'shared_no_addition' autoload :Quoting, 'shared_quoting' end acts_as_list-1.0.4/test/shared_list.rb0000644000004100000410000002477714042233640020040 0ustar www-datawww-data# frozen_string_literal: true module Shared module List def setup (1..4).each do |counter| node = ListMixin.new parent_id: 5 node.pos = counter node.save! end end def test_current_position first_item = ListMixin.where(parent_id: 5).first assert_equal 1, first_item.current_position first_item.remove_from_list assert_nil first_item.current_position end def test_reordering assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 2).first.move_lower assert_equal [1, 3, 2, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 2).first.move_higher assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 1).first.move_to_bottom assert_equal [2, 3, 4, 1], ListMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 1).first.move_to_top assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 2).first.move_to_bottom assert_equal [1, 3, 4, 2], ListMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 4).first.move_to_top assert_equal [4, 1, 3, 2], ListMixin.where(parent_id: 5).order('pos').map(&:id) end def test_move_to_bottom_with_next_to_last_item assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 3).first.move_to_bottom assert_equal [1, 2, 4, 3], ListMixin.where(parent_id: 5).order('pos').map(&:id) end def test_next_prev assert_equal ListMixin.where(id: 2).first, ListMixin.where(id: 1).first.lower_item assert_nil ListMixin.where(id: 1).first.higher_item assert_equal ListMixin.where(id: 3).first, ListMixin.where(id: 4).first.higher_item assert_nil ListMixin.where(id: 4).first.lower_item end def test_injection item = ListMixin.new(parent_id: 1) assert_equal({ parent_id: 1 }, item.scope_condition) assert_equal "pos", item.position_column end def test_insert new = ListMixin.create(parent_id: 20) assert_equal 1, new.pos assert new.first? assert new.last? new = ListMixin.create(parent_id: 20) assert_equal 2, new.pos assert !new.first? assert new.last? new = ListMixin.acts_as_list_no_update { ListMixin.create(parent_id: 20) } assert_equal_or_nil $default_position, new.pos assert_equal $default_position.is_a?(Integer), new.first? assert !new.last? new = ListMixin.create(parent_id: 20) assert_equal 3, new.pos assert !new.first? assert new.last? new = ListMixin.create(parent_id: 0) assert_equal 1, new.pos assert new.first? assert new.last? end def test_insert_at new = ListMixin.create(parent_id: 20) assert_equal 1, new.pos new = ListMixin.create(parent_id: 20) assert_equal 2, new.pos 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_or_nil $default_position, new_noup.pos new4 = ListMixin.create(parent_id: 20) assert_equal 4, new4.pos new4.insert_at(3) assert_equal 3, new4.pos new.reload assert_equal 4, new.pos new.insert_at(2) assert_equal 2, new.pos new4.reload assert_equal 4, new4.pos new5 = ListMixin.create(parent_id: 20) assert_equal 5, new5.pos new5.insert_at(1) assert_equal 1, new5.pos new4.reload assert_equal 5, new4.pos new_noup.reload assert_equal_or_nil $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) pos_list = ListMixin.where(parent_id: 20).order("pos ASC#{' NULLS FIRST' if ENV['DB'] == 'postgresql'}").map(&:pos) assert_equal [$default_position, 1, 2, 3, 4, 5], pos_list end def test_insert_at_after_dup new1 = ListMixin.create(parent_id: 20) new2 = ListMixin.create(parent_id: 20) new3 = ListMixin.create(parent_id: 20) duped = new1.dup duped.save [new1, new2, new3, duped].map(&:reload) assert_equal [1, 2, 3, 4], [duped.pos, new1.pos, new2.pos, new3.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) assert_equal 1, ListMixin.where(id: 1).first.pos assert_equal 2, ListMixin.where(id: 3).first.pos assert_equal 3, ListMixin.where(id: 4).first.pos ListMixin.where(id: 1).first.destroy assert_equal [3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) 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 new = ListWithStringScopeMixin.create(parent_id: 500) assert_equal 1, new.pos assert new.first? assert new.last? end def test_nil_scope new1, new2, new3 = ListMixin.create, ListMixin.create, ListMixin.create new2.move_higher assert_equal [new2, new1, new3].map(&:id), ListMixin.where(parent_id: nil).order('pos').map(&:id) end def test_update_position_when_scope_changes assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.create(parent_id: 6) ListMixin.where(id: 2).first.move_within_scope(6) assert_equal 2, ListMixin.where(id: 2).first.pos assert_equal [1, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) assert_equal 1, ListMixin.where(id: 1).first.pos assert_equal 2, ListMixin.where(id: 3).first.pos assert_equal 3, ListMixin.where(id: 4).first.pos ListMixin.where(id: 2).first.move_within_scope(5) assert_equal [1, 3, 4, 2], ListMixin.where(parent_id: 5).order('pos').map(&:id) end def test_remove_from_list_should_then_fail_in_list? assert_equal true, ListMixin.where(id: 1).first.in_list? ListMixin.where(id: 1).first.remove_from_list assert_equal false, ListMixin.where(id: 1).first.in_list? end def test_remove_from_list_should_set_position_to_nil assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 2).first.remove_from_list assert_equal 1, ListMixin.where(id: 1).first.pos assert_nil ListMixin.where(id: 2).first.pos assert_equal 2, ListMixin.where(id: 3).first.pos assert_equal 3, ListMixin.where(id: 4).first.pos end def test_remove_before_destroy_does_not_shift_lower_items_twice assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) ListMixin.where(id: 2).first.remove_from_list ListMixin.where(id: 2).first.destroy assert_equal [1, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) assert_equal 1, ListMixin.where(id: 1).first.pos assert_equal 2, ListMixin.where(id: 3).first.pos assert_equal 3, ListMixin.where(id: 4).first.pos end def test_before_destroy_callbacks_do_not_update_position_to_nil_before_deleting_the_record assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) # We need to trigger all the before_destroy callbacks without actually # destroying the record so we can see the affect the callbacks have on # the record. list = ListMixin.where(id: 2).first if list.respond_to?(:run_callbacks) list.run_callbacks(:destroy) else list.send(:callback, :before_destroy) end assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) assert_equal 1, ListMixin.where(id: 1).first.pos assert_equal 2, ListMixin.where(id: 2).first.pos assert_equal 2, ListMixin.where(id: 3).first.pos assert_equal 3, ListMixin.where(id: 4).first.pos end def test_before_create_callback_adds_to_bottom assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) new = ListMixin.create(parent_id: 5) assert_equal 5, new.pos assert !new.first? assert new.last? assert_equal [1, 2, 3, 4, 5], ListMixin.where(parent_id: 5).order('pos').map(&:id) end def test_before_create_callback_adds_to_given_position assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id) new = ListMixin.new(parent_id: 5) new.pos = 1 new.save! assert_equal 1, new.pos assert new.first? assert !new.last? 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 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, 7, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos, id').map(&:id) end def test_non_persisted_records_dont_get_lock_called new = ListMixin.new(parent_id: 5) new.destroy end def test_invalid_records_dont_get_inserted new = ListMixinError.new(parent_id: 5, state: nil) assert !new.valid? new.insert_at(1) assert !new.persisted? end def test_invalid_records_raise_error_with_insert_at! new = ListMixinError.new(parent_id: 5, state: nil) assert !new.valid? assert_raises ActiveRecord::RecordInvalid do new.insert_at!(1) end end def test_find_or_create_doesnt_raise_deprecation_warning assert_no_deprecation_warning_raised_by('ActiveRecord deprecation warning raised when using `find_or_create_by` when we didn\'t expect it') do ListMixin.where(parent_id: 5).find_or_create_by(pos: 5) end end end end acts_as_list-1.0.4/test/test_no_update_for_subclasses.rb0000644000004100000410000000250014042233640023625 0ustar www-datawww-data# frozen_string_literal: true require 'helper' class MasterItem < ActiveRecord::Base acts_as_list end class SlaveItem < MasterItem; end class NoUpdateForSubclassesTestCase < Minitest::Test def setup ActiveRecord::Base.connection.create_table :master_items do |t| t.column :position, :integer t.column :type, :string end ActiveRecord::Base.connection.schema_cache.clear! [MasterItem, SlaveItem].each(&:reset_column_information) super end def teardown teardown_db super end end class NoUpdateForSubclassesTest < NoUpdateForSubclassesTestCase def setup super @item_1, @item_2 = (1..2).map do |counter| SlaveItem.create!(position: counter) end end def test_update @item_1.update position: 2 assert_equal 2, @item_1.reload.position assert_equal 1, @item_2.reload.position end def test_no_update_for_subclass_instances_with_no_update_on_superclass MasterItem.acts_as_list_no_update { @item_1.update position: 2 } assert_equal 2, @item_1.reload.position assert_equal 2, @item_2.reload.position end def test_no_update_for_subclass_instances_with_no_update_on_subclass SlaveItem.acts_as_list_no_update { @item_1.update position: 2 } assert_equal 2, @item_1.reload.position assert_equal 2, @item_2.reload.position end end acts_as_list-1.0.4/test/database.yml0000644000004100000410000000042114042233640017455 0ustar www-datawww-datasqlite: adapter: sqlite3 database: "file:memdb1?mode=memory&cache=shared" mysql: adapter: mysql2 username: root password: database: acts_as_list postgresql: adapter: postgresql username: postgres password: database: acts_as_list min_messages: ERROR acts_as_list-1.0.4/README.md0000644000004100000410000003377614042233640015511 0ustar www-datawww-data# Acts As List ## Build Status [![Build Status](https://travis-ci.org/brendon/acts_as_list.svg?branch=master)](https://travis-ci.org/brendon/acts_as_list) [![Gem Version](https://badge.fury.io/rb/acts_as_list.svg)](https://badge.fury.io/rb/acts_as_list) ## Description This `acts_as` extension provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a `position` column defined as an integer on the mapped database table. ## 0.8.0 Upgrade Notes There are a couple of changes of behaviour from `0.8.0` onwards: - If you specify `add_new_at: :top`, new items will be added to the top of the list like always. But now, if you specify a position at insert time: `.create(position: 3)`, the position will be respected. In this example, the item will end up at position `3` and will move other items further down the list. Before `0.8.0` the position would be ignored and the item would still be added to the top of the list. [#220](https://github.com/brendon/acts_as_list/pull/220) - `acts_as_list` now copes with disparate position integers (i.e. gaps between the numbers). There has been a change in behaviour for the `higher_items` method. It now returns items with the first item in the collection being the closest item to the reference item, and the last item in the collection being the furthest from the reference item (a.k.a. the first item in the list). [#223](https://github.com/brendon/acts_as_list/pull/223) ## Installation In your Gemfile: gem 'acts_as_list' Or, from the command line: gem install acts_as_list ## Example At first, you need to add a `position` column to desired table: rails g migration AddPositionToTodoItem position:integer rake db:migrate After that you can use `acts_as_list` method in the model: ```ruby class TodoList < ActiveRecord::Base has_many :todo_items, -> { order(position: :asc) } end class TodoItem < ActiveRecord::Base belongs_to :todo_list acts_as_list scope: :todo_list end todo_list = TodoList.find(...) todo_list.todo_items.first.move_to_bottom todo_list.todo_items.last.move_higher ``` ## Instance Methods Added To ActiveRecord Models You'll have a number of methods added to each instance of the ActiveRecord model that to which `acts_as_list` is added. In `acts_as_list`, "higher" means further up the list (a lower `position`), and "lower" means further down the list (a higher `position`). That can be confusing, so it might make sense to add tests that validate that you're using the right method given your context. ### Methods That Change Position and Reorder List - `list_item.insert_at(2)` - `list_item.move_lower` will do nothing if the item is the lowest item - `list_item.move_higher` will do nothing if the item is the highest item - `list_item.move_to_bottom` - `list_item.move_to_top` - `list_item.remove_from_list` ### Methods That Change Position Without Reordering List - `list_item.increment_position` - `list_item.decrement_position` - `list_item.set_list_position(3)` ### Methods That Return Attributes of the Item's List Position - `list_item.first?` - `list_item.last?` - `list_item.in_list?` - `list_item.not_in_list?` - `list_item.default_position?` - `list_item.higher_item` - `list_item.higher_items` will return all the items above `list_item` in the list (ordered by the position, ascending) - `list_item.lower_item` - `list_item.lower_items` will return all the items below `list_item` in the list (ordered by the position, ascending) ## Adding `acts_as_list` To An Existing Model As it stands `acts_as_list` requires position values to be set on the model before the instance methods above will work. Adding something like the below to your migration will set the default position. Change the parameters to order if you want a different initial ordering. ```ruby class AddPositionToTodoItem < ActiveRecord::Migration def change add_column :todo_items, :position, :integer TodoItem.order(:updated_at).each.with_index(1) do |todo_item, index| todo_item.update_column :position, index end end end ``` If you are using the scope option things can get a bit more complicated. Let's say you have `acts_as_list scope: :todo_list`, you might instead need something like this: ```ruby TodoList.all.each do |todo_list| todo_list.todo_items.order(:updated_at).each.with_index(1) do |todo_item, index| todo_item.update_column :position, index end end ``` When using PostgreSQL, it is also possible to leave this migration up to the database layer. Inside of the `change` block you could write: ```ruby execute <<~SQL.squish UPDATE todo_items SET position = mapping.new_position FROM ( SELECT id, ROW_NUMBER() OVER ( PARTITION BY todo_list_id ORDER BY updated_at ) as new_position FROM todo_items ) AS mapping WHERE todo_items.id = mapping.id; SQL ``` ## Notes All `position` queries (select, update, etc.) inside gem methods are executed without the default scope (i.e. `Model.unscoped`), this will prevent nasty issues when the default scope is different from `acts_as_list` scope. The `position` column is set after validations are called, so you should not put a `presence` validation on the `position` column. If you need a scope by a non-association field you should pass an array, containing field name, to a scope: ```ruby class TodoItem < ActiveRecord::Base # `kind` is a plain text field (e.g. 'work', 'shopping', 'meeting'), not an association acts_as_list scope: [:kind] end ``` You can also add multiple scopes in this fashion: ```ruby class TodoItem < ActiveRecord::Base acts_as_list scope: [:kind, :owner_id] end ``` Furthermore, you can optionally include a hash of fixed parameters that will be included in all queries: ```ruby class TodoItem < ActiveRecord::Base acts_as_list scope: [:kind, :owner_id, deleted_at: nil] end ``` This is useful when using this gem in conjunction with the popular [acts_as_paranoid](https://github.com/ActsAsParanoid/acts_as_paranoid) gem. ## More Options - `column` default: `position`. Use this option if the column name in your database is different from position. - `top_of_list` default: `1`. Use this option to define the top of the list. Use 0 to make the collection act more like an array in its indexing. - `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. - `touch_on_update` default: `true`. Use `touch_on_update: false` if you don't want to update the timestamps of the associated records. - `sequential_updates` Specifies whether insert_at should update objects positions during shuffling one by one to respect position column unique not null constraint. Defaults to true if position column has unique index, otherwise false. If constraint is `deferrable initially deferred` (PostgreSQL), overriding it with false will speed up insert_at. ## 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. You can also pass an array of classes as an argument to disable database updates on just those classes. It can be any ActiveRecord class that has acts_as_list enabled. ```ruby class TodoList < ActiveRecord::Base has_many :todo_items, -> { order(position: :asc) } acts_as_list end class TodoItem < ActiveRecord::Base belongs_to :todo_list has_many :todo_attachments, -> { order(position: :asc) } acts_as_list scope: :todo_list end class TodoAttachment < ActiveRecord::Base belongs_to :todo_list acts_as_list scope: :todo_item end TodoItem.acts_as_list_no_update([TodoAttachment]) do TodoItem.find(10).update(position: 2) TodoAttachment.find(10).update(position: 1) TodoAttachment.find(11).update(position: 2) TodoList.find(2).update(position: 3) # For this instance the callbacks will be called because we haven't passed the class as an argument end ``` ## Troubleshooting Database Deadlock Errors When using this gem in an app with a high amount of concurrency, you may see "deadlock" errors raised by your database server. It's difficult for the gem to provide a solution that fits every app. Here are some steps you can take to mitigate and handle these kinds of errors. ### 1) Use the Most Concise API One easy way to reduce deadlocks is to use the most concise gem API available for what you want to accomplish. In this specific example, the more concise API for creating a list item at a position results in one transaction instead of two, and it issues fewer SQL statements. Issuing fewer statements tends to lead to faster transactions. Faster transactions are less likely to deadlock. Example: ```ruby # Good TodoItem.create(todo_list: todo_list, position: 1) # Bad item = TodoItem.create(todo_list: todo_list) item.insert_at(1) ``` ### 2) Rescue then Retry Deadlocks are always a possibility when updating tables rows concurrently. The general advice from MySQL documentation is to catch these errors and simply retry the transaction; it will probably succeed on another attempt. (see [How to Minimize and Handle Deadlocks](https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks-handling.html)) Retrying transactions sounds simple, but there are many details that need to be chosen on a per-app basis: How many retry attempts should be made? Should there be a wait time between attempts? What _other_ statements were in the transaction that got rolled back? Here a simple example of rescuing from deadlock and retrying the operation: * `ActiveRecord::Deadlocked` is available in Rails >= 5.1.0. * If you have Rails < 5.1.0, you will need to rescue `ActiveRecord::StatementInvalid` and check `#cause`. ```ruby attempts_left = 2 while attempts_left > 0 attempts_left -= 1 begin TodoItem.transaction do TodoItem.create(todo_list: todo_list, position: 1) end attempts_left = 0 rescue ActiveRecord::Deadlocked raise unless attempts_left > 0 end end ``` You can also use the approach suggested in this StackOverflow post: https://stackoverflow.com/questions/4027659/activerecord3-deadlock-retry ### 3) Lock Parent Record In addition to reacting to deadlocks, it is possible to reduce their frequency with more pessimistic locking. This approach uses the parent record as a mutex for the entire list. This kind of locking is very effective at reducing the frequency of deadlocks while updating list items. However, there are some things to keep in mind: * This locking pattern needs to be used around *every* call that modifies the list; even if it does not reorder list items. * This locking pattern effectively serializes operations on the list. The throughput of operations on the list will decrease. * Locking the parent record may lead to deadlock elsewhere if some other code also locks the parent table. Example: ```ruby todo_list = TodoList.create(name: "The List") todo_list.with_lock do item = TodoItem.create(description: "Buy Groceries", todo_list: todo_list, position: 1) end ``` ## Versions Version `0.9.0` adds `acts_as_list_no_update` (https://github.com/brendon/acts_as_list/pull/244) and compatibility with not-null and uniqueness constraints on the database (https://github.com/brendon/acts_as_list/pull/246). These additions shouldn't break compatibility with existing implementations. As of version `0.7.5` Rails 5 is supported. All versions `0.1.5` onwards require Rails 3.0.x and higher. ## A note about data integrity We often hear complaints that `position` values are repeated, incorrect etc. For example, #254. To ensure data integrity, you should rely on your database. There are two things you can do: 1. Use constraints. If you model `Item` that `belongs_to` an `Order`, and it has a `position` column, then add a unique constraint on `items` with `[:order_id, :position]`. Think of it as a list invariant. What are the properties of your list that don't change no matter how many items you have in it? One such propery is that each item has a distinct position. Another _could be_ that position is always greater than 0. It is strongly recommended that you rely on your database to enforce these invariants or constraints. Here are the docs for [PostgreSQL](https://www.postgresql.org/docs/9.5/static/ddl-constraints.html) and [MySQL](https://dev.mysql.com/doc/refman/8.0/en/alter-table.html). 2. Use mutexes or row level locks. At its heart the duplicate problem is that of handling concurrency. Adding a contention resolution mechanism like locks will solve it to some extent. But it is not a solution or replacement for constraints. Locks are also prone to deadlocks. As a library, `acts_as_list` may not always have all the context needed to apply these tools. They are much better suited at the application level. ## Roadmap 1. Sort based feature ## Contributing to `acts_as_list` - Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet - Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it - Fork the project - Start a feature/bugfix branch - Commit and push until you are happy with your contribution - Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. - Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. - I would recommend using Rails 3.1.x and higher for testing the build before a pull request. The current test harness does not quite work with 3.0.x. The plugin itself works, but the issue lies with testing infrastructure. ## Copyright Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license acts_as_list-1.0.4/gemfiles/0000755000004100000410000000000014042233640016005 5ustar www-datawww-dataacts_as_list-1.0.4/gemfiles/rails_4_2.gemfile0000644000004100000410000000074414042233640021122 0ustar www-datawww-data# This file was generated by Appraisal source "http://rubygems.org" gem "rake" gem "appraisal" gem "activerecord", "~> 4.2.0" group :development do gem "github_changelog_generator", "1.9.0" end group :test do gem "minitest", "~> 5.0" gem "timecop" gem "mocha" gem "test_after_commit", "~> 0.4.2" end group :sqlite do gem "sqlite3", "~> 1.3.13" end group :postgresql do gem "pg", "~> 0.18.4" end group :mysql do gem "mysql2", "~> 0.4.0" end gemspec path: "../" acts_as_list-1.0.4/gemfiles/rails_5_2.gemfile0000644000004100000410000000067214042233640021123 0ustar www-datawww-data# This file was generated by Appraisal source "http://rubygems.org" gem "rake" gem "appraisal" gem "activerecord", "~> 5.2.0" group :development do gem "github_changelog_generator", "1.9.0" end group :test do gem "minitest", "~> 5.0" gem "timecop" gem "mocha" end group :sqlite do gem "sqlite3", "~> 1.4" end group :postgresql do gem "pg", "~> 1.2.0" end group :mysql do gem "mysql2", "~> 0.5.0" end gemspec path: "../" acts_as_list-1.0.4/gemfiles/rails_6_1.gemfile0000644000004100000410000000067314042233640021124 0ustar www-datawww-data# This file was generated by Appraisal source "http://rubygems.org" gem "rake" gem "appraisal" gem "activerecord", "6.1.0.rc1" group :development do gem "github_changelog_generator", "1.9.0" end group :test do gem "minitest", "~> 5.0" gem "timecop" gem "mocha" end group :sqlite do gem "sqlite3", "~> 1.4" end group :postgresql do gem "pg", "~> 1.2.0" end group :mysql do gem "mysql2", "~> 0.5.0" end gemspec path: "../" acts_as_list-1.0.4/gemfiles/rails_5_0.gemfile0000644000004100000410000000067514042233640021124 0ustar www-datawww-data# This file was generated by Appraisal source "http://rubygems.org" gem "rake" gem "appraisal" gem "activerecord", "~> 5.0.0" group :development do gem "github_changelog_generator", "1.9.0" end group :test do gem "minitest", "~> 5.0" gem "timecop" gem "mocha" end group :sqlite do gem "sqlite3", "~> 1.3.13" end group :postgresql do gem "pg", "~> 1.2.0" end group :mysql do gem "mysql2", "~> 0.5.0" end gemspec path: "../" acts_as_list-1.0.4/gemfiles/rails_5_1.gemfile0000644000004100000410000000067214042233640021122 0ustar www-datawww-data# This file was generated by Appraisal source "http://rubygems.org" gem "rake" gem "appraisal" gem "activerecord", "~> 5.1.0" group :development do gem "github_changelog_generator", "1.9.0" end group :test do gem "minitest", "~> 5.0" gem "timecop" gem "mocha" end group :sqlite do gem "sqlite3", "~> 1.4" end group :postgresql do gem "pg", "~> 1.2.0" end group :mysql do gem "mysql2", "~> 0.5.0" end gemspec path: "../" acts_as_list-1.0.4/gemfiles/rails_6_0.gemfile0000644000004100000410000000067214042233640021122 0ustar www-datawww-data# This file was generated by Appraisal source "http://rubygems.org" gem "rake" gem "appraisal" gem "activerecord", "~> 6.0.0" group :development do gem "github_changelog_generator", "1.9.0" end group :test do gem "minitest", "~> 5.0" gem "timecop" gem "mocha" end group :sqlite do gem "sqlite3", "~> 1.4" end group :postgresql do gem "pg", "~> 1.2.0" end group :mysql do gem "mysql2", "~> 0.5.0" end gemspec path: "../" acts_as_list-1.0.4/acts_as_list.gemspec0000644000004100000410000000324114042233640020227 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "acts_as_list/version" Gem::Specification.new do |s| # Description Meta... s.name = "acts_as_list" s.version = ActiveRecord::Acts::List::VERSION s.platform = Gem::Platform::RUBY s.authors = ["Swanand Pagnis", "Brendon Muir"] s.email = %w(swanand.pagnis@gmail.com brendon@spikeatschool.co.nz) s.homepage = "http://github.com/brendon/acts_as_list" s.summary = "A gem adding sorting, reordering capabilities to an active_record model, allowing it to act as a list" s.description = 'This "acts_as" extension provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a "position" column defined as an integer on the mapped database table.' s.license = "MIT" s.required_ruby_version = ">= 2.4.7" if s.respond_to?(:metadata) s.metadata['changelog_uri'] = 'https://github.com/brendon/acts_as_list/blob/master/CHANGELOG.md' s.metadata['source_code_uri'] = 'https://github.com/brendon/acts_as_list' s.metadata['bug_tracker_uri'] = 'https://github.com/brendon/acts_as_list/issues' end # Load Paths... s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map {|f| File.basename(f)} s.require_paths = ["lib"] # Dependencies (installed via "bundle install") s.add_dependency "activerecord", ">= 4.2" s.add_development_dependency "bundler", ">= 1.0.0" end acts_as_list-1.0.4/CHANGELOG.md0000644000004100000410000012627414042233640016037 0ustar www-datawww-data# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased ## v1.0.4 - 2021-04-20 ### Fixed - Add Tests ruby 2.7 and 3.0 [\#393](https://github.com/brendon/acts_as_list/pull/393) ([QWYNG]) ## v1.0.3 - 2020-12-24 ### Fixed - Silence deprecation warnings in Rails 6 [\#384](https://github.com/brendon/acts_as_list/pull/384) ([h-lame]) - Add explicit requirement of ActiveSupport::Inflector [\#387](https://github.com/brendon/acts_as_list/pull/387) ([rdvdijk]) ## v1.0.2 - 2020-09-14 ### Fixed - Get foreign key from reflections when possible [\#383](https://github.com/brendon/acts_as_list/pull/383) ([jefftsang]) ### Removed - gemspec: Drop defunct `rubyforge_project` directive [\#373](https://github.com/brendon/acts_as_list/pull/373) ([olleolleolle]) [olleolleolle]: https://github.com/olleolleolle [jefftsang]: https://github.com/jefftsang ## v1.0.1 - 2020-02-27 ### Fixed - Invert order when incrementing to circumvent unique index violations (#368) ## [v1.0.0](https://github.com/swanandp/acts_as_list/tree/v1.0.0) - 2019-09-26 [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.9.19...v1.0.0) ### Removed - **BREAKING CHANGE**: Support for Rails 3.2 > 4.1 has been removed. 0.9.19 is the last version that supports these Rails versions ### Added - Added *Troubleshooting Database Deadlock Errors* section to `README.md` - Added support for Rails 6.0 in testing - Various README fixes - A new method called `current_position` now exists and returns the integer position of the item it's called on, or `nil` if the position isn't set. ## [v0.9.19](https://github.com/swanandp/acts_as_list/tree/v0.9.19) - 2019-03-12 [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.9.18...v0.9.19) ### Added - Allow `acts_as_list_no_update` blocks to be nested [@conorbdaly](https://github.com/conorbdaly) ## [v0.9.18](https://github.com/swanandp/acts_as_list/tree/v0.9.18) - 2019-03-08 [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.9.17...v0.9.18) ### Added - Added additional gemspec metadata [@boone](https://github.com/boone) - Add gem version badge to README [@joshuapinter](https://github.com/joshuapinter) - Add touch on update configuration [@mgbatchelor](https://github.com/mgbatchelor) ### Changed - Let's start a new direction with the CHANGELOG file [@mainameiz](https://github.com/mainameiz) ### Fixed - Fix sqlite3 gem pinning breaking tests ## [v0.9.17](https://github.com/brendon/acts_as_list/tree/v0.9.17) (2018-10-29) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.16...v0.9.17) **Closed issues:** - Inconsistent behavior [\#330](https://github.com/brendon/acts_as_list/issues/330) - Using `top\_of\_list` set to 1 and setting a record to index 0 triggers a `PG::UniqueViolation` [\#322](https://github.com/brendon/acts_as_list/issues/322) **Merged pull requests:** - Feature/add exception to wrong position [\#323](https://github.com/brendon/acts_as_list/pull/323) ([TheNeikos](https://github.com/TheNeikos)) - Methods move\_to\_bottom and move\_to\_top should not fail when there are unique constraints [\#320](https://github.com/brendon/acts_as_list/pull/320) ([faucct](https://github.com/faucct)) ## [v0.9.16](https://github.com/brendon/acts_as_list/tree/v0.9.16) (2018-08-30) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.15...v0.9.16) **Closed issues:** - Re-ordering at specific position [\#318](https://github.com/brendon/acts_as_list/issues/318) - `no\_update` is not applied to subclasses [\#314](https://github.com/brendon/acts_as_list/issues/314) - NoMethodError: undefined method `acts\_as\_list' [\#303](https://github.com/brendon/acts_as_list/issues/303) - Cannot create item at position 0 [\#297](https://github.com/brendon/acts_as_list/issues/297) **Merged pull requests:** - Unscope `select` to avoid PG::UndefinedFunction [\#283](https://github.com/brendon/acts_as_list/pull/283) ([donv](https://github.com/donv)) ## [v0.9.15](https://github.com/brendon/acts_as_list/tree/v0.9.15) (2018-06-11) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.14...v0.9.15) **Merged pull requests:** - Fix \#314: `no\_update` is not applied to subclasses [\#315](https://github.com/brendon/acts_as_list/pull/315) ([YoranBrondsema](https://github.com/YoranBrondsema)) ## [v0.9.14](https://github.com/brendon/acts_as_list/tree/v0.9.14) (2018-06-05) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.13...v0.9.14) **Closed issues:** - `insert\_at` saves invalid ActiveRecord objects [\#311](https://github.com/brendon/acts_as_list/issues/311) **Merged pull requests:** - \#311 Don't insert invalid ActiveRecord objects [\#312](https://github.com/brendon/acts_as_list/pull/312) ([seanabrahams](https://github.com/seanabrahams)) ## [v0.9.13](https://github.com/brendon/acts_as_list/tree/v0.9.13) (2018-06-05) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.12...v0.9.13) **Merged pull requests:** - Fix unique index constraint failure on item destroy [\#313](https://github.com/brendon/acts_as_list/pull/313) ([yjukaku](https://github.com/yjukaku)) ## [v0.9.12](https://github.com/brendon/acts_as_list/tree/v0.9.12) (2018-05-02) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.11...v0.9.12) **Closed issues:** - acts\_as\_list methods on has\_many through [\#308](https://github.com/brendon/acts_as_list/issues/308) - Travis badge [\#307](https://github.com/brendon/acts_as_list/issues/307) - Unscoping breaks STI subclasses, but is soon to be fixed in Rails [\#291](https://github.com/brendon/acts_as_list/issues/291) - Refactor string eval for scope\_condition [\#227](https://github.com/brendon/acts_as_list/issues/227) **Merged pull requests:** - mocha/minitest, not mocha/mini\_test now. [\#310](https://github.com/brendon/acts_as_list/pull/310) ([jmarkbrooks](https://github.com/jmarkbrooks)) ## [v0.9.11](https://github.com/brendon/acts_as_list/tree/v0.9.11) (2018-03-19) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.10...v0.9.11) **Closed issues:** - Setting `position: nil` on update returns `Column 'position' cannot be null` instead of putting the item at the start or the end of the list, like it does on create. [\#302](https://github.com/brendon/acts_as_list/issues/302) - Switching to Semaphore [\#301](https://github.com/brendon/acts_as_list/issues/301) - Dropping jruby support [\#300](https://github.com/brendon/acts_as_list/issues/300) - Rails 5.2.0 [\#299](https://github.com/brendon/acts_as_list/issues/299) - Cannot update record position when scoped to enum [\#298](https://github.com/brendon/acts_as_list/issues/298) - `add\_new\_at: :top` does not work [\#296](https://github.com/brendon/acts_as_list/issues/296) - remove\_from\_list causing "wrong number of arguments \(given 2, expected 0..1\)" [\#293](https://github.com/brendon/acts_as_list/issues/293) - Passing raw strings to reorder deprecated in Rails 5.2 [\#290](https://github.com/brendon/acts_as_list/issues/290) **Merged pull requests:** - Fix Test Suite [\#306](https://github.com/brendon/acts_as_list/pull/306) ([brendon](https://github.com/brendon)) - Add frozen\_string\_literal pragma to ruby files [\#305](https://github.com/brendon/acts_as_list/pull/305) ([krzysiek1507](https://github.com/krzysiek1507)) - Use symbols instead of SQL strings for reorder \(for Rails 5.2\) [\#294](https://github.com/brendon/acts_as_list/pull/294) ([jhawthorn](https://github.com/jhawthorn)) ## [v0.9.10](https://github.com/brendon/acts_as_list/tree/v0.9.10) (2017-11-19) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.9...v0.9.10) **Closed issues:** - Make insert\_at respect position when creating a new record [\#287](https://github.com/brendon/acts_as_list/issues/287) - Why does acts\_as\_list override rails validation on it's own field? [\#269](https://github.com/brendon/acts_as_list/issues/269) **Merged pull requests:** - Change error classes parents [\#288](https://github.com/brendon/acts_as_list/pull/288) ([alexander-lazarov](https://github.com/alexander-lazarov)) ## [v0.9.9](https://github.com/brendon/acts_as_list/tree/v0.9.9) (2017-10-03) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.8...v0.9.9) **Merged pull requests:** - Added fixed values option for scope array [\#286](https://github.com/brendon/acts_as_list/pull/286) ([smoyth](https://github.com/smoyth)) ## [v0.9.8](https://github.com/brendon/acts_as_list/tree/v0.9.8) (2017-09-28) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.7...v0.9.8) **Closed issues:** - Deadlocking in update\_positions count query [\#285](https://github.com/brendon/acts_as_list/issues/285) - Updating the position fails uniqueness constraint. [\#275](https://github.com/brendon/acts_as_list/issues/275) ## [v0.9.7](https://github.com/brendon/acts_as_list/tree/v0.9.7) (2017-07-06) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.6...v0.9.7) ## [v0.9.6](https://github.com/brendon/acts_as_list/tree/v0.9.6) (2017-07-05) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.5...v0.9.6) **Closed issues:** - undefined method `+' for nil:NilClass [\#278](https://github.com/brendon/acts_as_list/issues/278) - Enum does not scope correctly [\#277](https://github.com/brendon/acts_as_list/issues/277) - Can we don't use remove\_from\_list when destroy a lot objects? [\#276](https://github.com/brendon/acts_as_list/issues/276) - The NoUpdate code rely's on AS::Concern [\#273](https://github.com/brendon/acts_as_list/issues/273) - ActiveRecord associations are no longer required \(even though belongs\_to\_required\_by\_default == true\) [\#268](https://github.com/brendon/acts_as_list/issues/268) - Unique constraint violation on move\_higher, move\_lower, destroy [\#267](https://github.com/brendon/acts_as_list/issues/267) **Merged pull requests:** - Fix Fixnum deprecation warnings. [\#282](https://github.com/brendon/acts_as_list/pull/282) ([patrickdavey](https://github.com/patrickdavey)) - Fix update to scope that was defined with an enum [\#281](https://github.com/brendon/acts_as_list/pull/281) ([scottmalone](https://github.com/scottmalone)) - Refactor update\_all\_with\_touch [\#279](https://github.com/brendon/acts_as_list/pull/279) ([ledestin](https://github.com/ledestin)) - Remove AS::Concern from NoUpdate [\#274](https://github.com/brendon/acts_as_list/pull/274) ([brendon](https://github.com/brendon)) - Use `ActiveSupport.on\_load` to hook into ActiveRecord [\#272](https://github.com/brendon/acts_as_list/pull/272) ([brendon](https://github.com/brendon)) ## [v0.9.5](https://github.com/brendon/acts_as_list/tree/v0.9.5) (2017-04-04) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.4...v0.9.5) **Closed issues:** - acts\_as\_list\_class.maximum\(position\_column\) is causing the entire table to lock [\#264](https://github.com/brendon/acts_as_list/issues/264) - Be more precise with unscope-ing [\#263](https://github.com/brendon/acts_as_list/issues/263) **Merged pull requests:** - Use bottom\_position\_in\_list instead of the highest value in the table [\#266](https://github.com/brendon/acts_as_list/pull/266) ([brendon](https://github.com/brendon)) - Be more surgical about unscoping [\#265](https://github.com/brendon/acts_as_list/pull/265) ([brendon](https://github.com/brendon)) ## [v0.9.4](https://github.com/brendon/acts_as_list/tree/v0.9.4) (2017-03-16) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.3...v0.9.4) **Merged pull requests:** - Optimize first? and last? instance methods. [\#262](https://github.com/brendon/acts_as_list/pull/262) ([marshall-lee](https://github.com/marshall-lee)) ## [v0.9.3](https://github.com/brendon/acts_as_list/tree/v0.9.3) (2017-03-14) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.2...v0.9.3) **Closed issues:** - Rails 5.1.0.beta1 deprecation [\#257](https://github.com/brendon/acts_as_list/issues/257) - Move item X after item Y [\#256](https://github.com/brendon/acts_as_list/issues/256) - Is there way to specify the position when creating a resource? [\#255](https://github.com/brendon/acts_as_list/issues/255) **Merged pull requests:** - Don't update a child destroyed via relation [\#261](https://github.com/brendon/acts_as_list/pull/261) ([brendon](https://github.com/brendon)) - No update list for collection classes [\#260](https://github.com/brendon/acts_as_list/pull/260) ([IlkhamGaysin](https://github.com/IlkhamGaysin)) - Fix deprecation introduced in ActiveRecord 5.1.0.beta1. Closes \#257 [\#259](https://github.com/brendon/acts_as_list/pull/259) ([CvX](https://github.com/CvX)) - Refactor column definer module [\#258](https://github.com/brendon/acts_as_list/pull/258) ([ledestin](https://github.com/ledestin)) ## [v0.9.2](https://github.com/brendon/acts_as_list/tree/v0.9.2) (2017-02-07) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.1...v0.9.2) **Closed issues:** - Getting invalid input syntax for uuid [\#253](https://github.com/brendon/acts_as_list/issues/253) ## [v0.9.1](https://github.com/brendon/acts_as_list/tree/v0.9.1) (2017-01-26) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.9.0...v0.9.1) **Closed issues:** - DEPRECATION WARNING on rails 5.0 as of acts\_as\_list 0.9 [\#251](https://github.com/brendon/acts_as_list/issues/251) - highter\_items returns items with the same position value [\#247](https://github.com/brendon/acts_as_list/issues/247) - Broken with unique constraint on position [\#245](https://github.com/brendon/acts_as_list/issues/245) **Merged pull requests:** - fixes \#251 table\_exists? deprecation warning with Rails 5.0 [\#252](https://github.com/brendon/acts_as_list/pull/252) ([zharikovpro](https://github.com/zharikovpro)) ## [v0.9.0](https://github.com/brendon/acts_as_list/tree/v0.9.0) (2017-01-23) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.8.2...v0.9.0) **Closed issues:** - warning: too many arguments for format string [\#239](https://github.com/brendon/acts_as_list/issues/239) - Broken tests related to time comparison [\#238](https://github.com/brendon/acts_as_list/issues/238) - Shuffling positions is halting the callback chain [\#234](https://github.com/brendon/acts_as_list/issues/234) - Reorder positions [\#233](https://github.com/brendon/acts_as_list/issues/233) - Tests break when upgrading from 0.7.2 to 0.7.4 [\#228](https://github.com/brendon/acts_as_list/issues/228) - RE \#221 needing a test [\#226](https://github.com/brendon/acts_as_list/issues/226) - Adding to existing model with data and methods don't work [\#209](https://github.com/brendon/acts_as_list/issues/209) - Position is set incorrectly when circular dependencies exist [\#153](https://github.com/brendon/acts_as_list/issues/153) **Merged pull requests:** - Revert "Updates documentation with valid string interpolation syntax" [\#250](https://github.com/brendon/acts_as_list/pull/250) ([brendon](https://github.com/brendon)) - Updates documentation with valid string interpolation syntax [\#249](https://github.com/brendon/acts_as_list/pull/249) ([naveedkakal](https://github.com/naveedkakal)) - Comply to tests warnings [\#248](https://github.com/brendon/acts_as_list/pull/248) ([randoum](https://github.com/randoum)) - insert\_at respects unique not null check \(\>= 0\) db constraints [\#246](https://github.com/brendon/acts_as_list/pull/246) ([zharikovpro](https://github.com/zharikovpro)) - acts\_as\_list\_no\_update [\#244](https://github.com/brendon/acts_as_list/pull/244) ([randoum](https://github.com/randoum)) - Update README.md [\#243](https://github.com/brendon/acts_as_list/pull/243) ([rahuldstiwari](https://github.com/rahuldstiwari)) - Fixed tests to prevent warning: too many arguments for format string [\#242](https://github.com/brendon/acts_as_list/pull/242) ([brendon](https://github.com/brendon)) - Be explicit about ordering when mapping :pos [\#241](https://github.com/brendon/acts_as_list/pull/241) ([brendon](https://github.com/brendon)) - Improve load method [\#240](https://github.com/brendon/acts_as_list/pull/240) ([brendon](https://github.com/brendon)) - Fix non regular sequence movement [\#237](https://github.com/brendon/acts_as_list/pull/237) ([tiagotex](https://github.com/tiagotex)) - Add travis config for testing against multiple databases [\#236](https://github.com/brendon/acts_as_list/pull/236) ([fschwahn](https://github.com/fschwahn)) - Extract modules [\#229](https://github.com/brendon/acts_as_list/pull/229) ([ledestin](https://github.com/ledestin)) ## [v0.8.2](https://github.com/brendon/acts_as_list/tree/v0.8.2) (2016-09-23) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.8.1...v0.8.2) **Closed issues:** - We're a repo now, no longer a fork attached to rails/acts\_as\_list [\#232](https://github.com/brendon/acts_as_list/issues/232) - Break away from rails/acts\_as\_list [\#224](https://github.com/brendon/acts_as_list/issues/224) - Problem when inserting straight at top of list [\#109](https://github.com/brendon/acts_as_list/issues/109) **Merged pull requests:** - Show items with same position in higher and lower items [\#231](https://github.com/brendon/acts_as_list/pull/231) ([jpalumickas](https://github.com/jpalumickas)) - fix setting position when previous position was nil [\#230](https://github.com/brendon/acts_as_list/pull/230) ([StoneFrog](https://github.com/StoneFrog)) ## [v0.8.1](https://github.com/brendon/acts_as_list/tree/v0.8.1) (2016-09-06) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.8.0...v0.8.1) **Closed issues:** - Rubinius Intermittent testing error [\#218](https://github.com/brendon/acts_as_list/issues/218) - ActiveRecord dependency causes rake assets:compile to fail without access to a database [\#84](https://github.com/brendon/acts_as_list/issues/84) **Merged pull requests:** - Refactor class\_eval with string into class\_eval with block [\#215](https://github.com/brendon/acts_as_list/pull/215) ([rdvdijk](https://github.com/rdvdijk)) ## [v0.8.0](https://github.com/brendon/acts_as_list/tree/v0.8.0) (2016-08-23) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.7.7...v0.8.0) **Closed issues:** - Behavior with DB default seems unclear [\#219](https://github.com/brendon/acts_as_list/issues/219) **Merged pull requests:** - No longer a need specify additional rbx gems [\#225](https://github.com/brendon/acts_as_list/pull/225) ([brendon](https://github.com/brendon)) - Fix position when no serial positions [\#223](https://github.com/brendon/acts_as_list/pull/223) ([jpalumickas](https://github.com/jpalumickas)) - Bug: Specifying a position with add\_new\_at: :top fails to insert at that position [\#220](https://github.com/brendon/acts_as_list/pull/220) ([brendon](https://github.com/brendon)) ## [v0.7.7](https://github.com/brendon/acts_as_list/tree/v0.7.7) (2016-08-18) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.7.6...v0.7.7) **Closed issues:** - Issue after upgrading to 0.7.5: No connection pool with id primary found. [\#214](https://github.com/brendon/acts_as_list/issues/214) - Changing scope is inconsistent based on add\_new\_at [\#138](https://github.com/brendon/acts_as_list/issues/138) - Duplicate positions and lost items [\#76](https://github.com/brendon/acts_as_list/issues/76) **Merged pull requests:** - Add quoted table names to some columns [\#221](https://github.com/brendon/acts_as_list/pull/221) ([jpalumickas](https://github.com/jpalumickas)) - Appraisals cleanup [\#217](https://github.com/brendon/acts_as_list/pull/217) ([brendon](https://github.com/brendon)) - Fix insert\_at\_position in race condition [\#195](https://github.com/brendon/acts_as_list/pull/195) ([danielross](https://github.com/danielross)) ## [v0.7.6](https://github.com/brendon/acts_as_list/tree/v0.7.6) (2016-07-15) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.7.5...v0.7.6) **Closed issues:** - add\_new\_at nil with scope causes NoMethodError [\#211](https://github.com/brendon/acts_as_list/issues/211) **Merged pull requests:** - Add class method acts\_as\_list\_top as reader for configured top\_of\_list [\#213](https://github.com/brendon/acts_as_list/pull/213) ([krzysiek1507](https://github.com/krzysiek1507)) - Bugfix/add new at nil on scope change [\#212](https://github.com/brendon/acts_as_list/pull/212) ([greatghoul](https://github.com/greatghoul)) ## [v0.7.5](https://github.com/brendon/acts_as_list/tree/v0.7.5) (2016-06-30) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.7.4...v0.7.5) **Implemented enhancements:** - Touch when reordering [\#173](https://github.com/brendon/acts_as_list/pull/173) ([botandrose](https://github.com/botandrose)) **Closed issues:** - Exception raised when calling destroy "NameError - instance variable @scope\_changed not defined:" [\#206](https://github.com/brendon/acts_as_list/issues/206) - Undefined instance variable @scope\_changed since 0.7.3 [\#199](https://github.com/brendon/acts_as_list/issues/199) - Reordering large lists is slow [\#198](https://github.com/brendon/acts_as_list/issues/198) - Reparenting child leaves gap in source list in rails 5 [\#194](https://github.com/brendon/acts_as_list/issues/194) - Support rails 5 ? [\#186](https://github.com/brendon/acts_as_list/issues/186) - I get a NoMethodError: undefined method `acts\_as\_list' when trying to include acts\_as\_list [\#176](https://github.com/brendon/acts_as_list/issues/176) - Phenomenon of mysterious value of the position is skipped by one [\#166](https://github.com/brendon/acts_as_list/issues/166) - Model.find being called twice with acts\_as\_list on destroy [\#161](https://github.com/brendon/acts_as_list/issues/161) - `scope\_changed?` problem with acts\_as\_paranoid [\#158](https://github.com/brendon/acts_as_list/issues/158) - Inconsistent behaviour between Symbol and Array scopes [\#155](https://github.com/brendon/acts_as_list/issues/155) - insert\_at doesn't seem to be working in ActiveRecord callback \(Rails 4.2\) [\#150](https://github.com/brendon/acts_as_list/issues/150) - Project Documentation link redirects to expired domain [\#149](https://github.com/brendon/acts_as_list/issues/149) - Problem when updating an position of array of AR objects. [\#137](https://github.com/brendon/acts_as_list/issues/137) - Unexpected behaviour when inserting consecutive items with default positions [\#124](https://github.com/brendon/acts_as_list/issues/124) - self.reload prone to error [\#122](https://github.com/brendon/acts_as_list/issues/122) - Rails 3.0.x in\_list causes the return of default\_scope [\#120](https://github.com/brendon/acts_as_list/issues/120) - Relationships with dependency:destroy cause ActiveRecord::RecordNotFound [\#118](https://github.com/brendon/acts_as_list/issues/118) - Using insert\_at with values with type String [\#117](https://github.com/brendon/acts_as_list/issues/117) - Batch setting of position [\#112](https://github.com/brendon/acts_as_list/issues/112) - position: 0 now makes model pushed to top? [\#110](https://github.com/brendon/acts_as_list/issues/110) - Create element in default position [\#103](https://github.com/brendon/acts_as_list/issues/103) - Enhancement: Expose scope object [\#97](https://github.com/brendon/acts_as_list/issues/97) - Shuffle list [\#96](https://github.com/brendon/acts_as_list/issues/96) - Creating an item with a nil scope should not add it to the list [\#92](https://github.com/brendon/acts_as_list/issues/92) - Performance Improvements [\#88](https://github.com/brendon/acts_as_list/issues/88) - has\_many :through or has\_many\_and\_belongs\_to\_many support [\#86](https://github.com/brendon/acts_as_list/issues/86) - move\_higher/move\_lower vs move\_to\_top/move\_to\_bottom act differently when item is already at top or bottom [\#77](https://github.com/brendon/acts_as_list/issues/77) - Limiting the list size [\#61](https://github.com/brendon/acts_as_list/issues/61) - Adding multiple creates strange ordering [\#55](https://github.com/brendon/acts_as_list/issues/55) - Feature: sort [\#26](https://github.com/brendon/acts_as_list/issues/26) **Merged pull requests:** - Fix position when no serial positions [\#208](https://github.com/brendon/acts_as_list/pull/208) ([PoslinskiNet](https://github.com/PoslinskiNet)) - Removed duplicated assignment [\#207](https://github.com/brendon/acts_as_list/pull/207) ([shunwen](https://github.com/shunwen)) - Quote all identifiers [\#205](https://github.com/brendon/acts_as_list/pull/205) ([fabn](https://github.com/fabn)) - Start testing Rails 5 [\#203](https://github.com/brendon/acts_as_list/pull/203) ([brendon](https://github.com/brendon)) - Lock! the record before destroying [\#201](https://github.com/brendon/acts_as_list/pull/201) ([brendon](https://github.com/brendon)) - Fix ambiguous column error when joining some relations [\#180](https://github.com/brendon/acts_as_list/pull/180) ([natw](https://github.com/natw)) ## [v0.7.4](https://github.com/brendon/acts_as_list/tree/v0.7.4) (2016-04-15) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.7.3...v0.7.4) **Closed issues:** - Releasing a new gem version [\#196](https://github.com/brendon/acts_as_list/issues/196) **Merged pull requests:** - Fix scope changed [\#200](https://github.com/brendon/acts_as_list/pull/200) ([brendon](https://github.com/brendon)) ## [v0.7.3](https://github.com/brendon/acts_as_list/tree/v0.7.3) (2016-04-14) [Full Changelog](https://github.com/brendon/acts_as_list/compare/v0.7.2...v0.7.3) ## [v0.7.2](https://github.com/brendon/acts_as_list/tree/v0.7.2) (2016-04-01) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.7.2...v0.7.2) **Closed issues:** - DEPRECATION WARNING: Passing string to define callback on Rails 5 beta 3 [\#191](https://github.com/brendon/acts_as_list/issues/191) - Why is `add\_to\_list\_bottom` private? [\#187](https://github.com/brendon/acts_as_list/issues/187) - Ordering of children when there are two possible parent models. [\#172](https://github.com/brendon/acts_as_list/issues/172) - Fix the jruby and rbx builds [\#169](https://github.com/brendon/acts_as_list/issues/169) - Unable to run tests [\#162](https://github.com/brendon/acts_as_list/issues/162) - shuffle\_positions\_on\_intermediate\_items is creating problems [\#134](https://github.com/brendon/acts_as_list/issues/134) - introduce Changelog file to quickly track changes [\#68](https://github.com/brendon/acts_as_list/issues/68) - Mongoid support? [\#52](https://github.com/brendon/acts_as_list/issues/52) **Merged pull requests:** - Add filename/line number to class\_eval call [\#193](https://github.com/brendon/acts_as_list/pull/193) ([hfwang](https://github.com/hfwang)) - Use a symbol as a string to define callback [\#192](https://github.com/brendon/acts_as_list/pull/192) ([brendon](https://github.com/brendon)) - Pin changelog generator to a working version [\#190](https://github.com/brendon/acts_as_list/pull/190) ([fabn](https://github.com/fabn)) - Fix bug, position is recomputed when object saved [\#188](https://github.com/brendon/acts_as_list/pull/188) ([chrisortman](https://github.com/chrisortman)) - Update bundler before running tests, fixes test run on travis [\#179](https://github.com/brendon/acts_as_list/pull/179) ([fabn](https://github.com/fabn)) - Changelog generator, closes \#68 [\#177](https://github.com/brendon/acts_as_list/pull/177) ([fabn](https://github.com/fabn)) - Updating README example [\#175](https://github.com/brendon/acts_as_list/pull/175) ([ryanbillings](https://github.com/ryanbillings)) - Adds description about various options available with the acts\_as\_list method [\#168](https://github.com/brendon/acts_as_list/pull/168) ([udit7590](https://github.com/udit7590)) - Small changes to DRY up list.rb [\#163](https://github.com/brendon/acts_as_list/pull/163) ([Albin-Willman](https://github.com/Albin-Willman)) - Only swap changed attributes which are persistable, i.e. are DB columns. [\#152](https://github.com/brendon/acts_as_list/pull/152) ([ludwigschubert](https://github.com/ludwigschubert)) ## [0.7.2](https://github.com/brendon/acts_as_list/tree/0.7.2) (2015-05-06) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.7.1...0.7.2) ## [0.7.1](https://github.com/brendon/acts_as_list/tree/0.7.1) (2015-05-06) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.7.0...0.7.1) **Merged pull requests:** - Update README.md [\#159](https://github.com/brendon/acts_as_list/pull/159) ([tibastral](https://github.com/tibastral)) ## [0.7.0](https://github.com/brendon/acts_as_list/tree/0.7.0) (2015-05-01) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.6.0...0.7.0) **Closed issues:** - Problem with reordering scoped list items [\#154](https://github.com/brendon/acts_as_list/issues/154) - Can no longer load acts\_as\_list in isolation if Rails is installed [\#145](https://github.com/brendon/acts_as_list/issues/145) **Merged pull requests:** - Fix regression with using acts\_as\_list on base classes [\#147](https://github.com/brendon/acts_as_list/pull/147) ([botandrose](https://github.com/botandrose)) - Don't require rails when loading [\#146](https://github.com/brendon/acts_as_list/pull/146) ([botandrose](https://github.com/botandrose)) ## [0.6.0](https://github.com/brendon/acts_as_list/tree/0.6.0) (2014-12-24) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.5.0...0.6.0) **Closed issues:** - Deprecation Warning: sanitize\_sql\_hash\_for\_conditions is deprecated and will be removed in Rails 5.0 [\#143](https://github.com/brendon/acts_as_list/issues/143) - Release a new gem version [\#136](https://github.com/brendon/acts_as_list/issues/136) **Merged pull requests:** - Fix sanitize\_sql\_hash\_for\_conditions deprecation warning in Rails 4.2 [\#140](https://github.com/brendon/acts_as_list/pull/140) ([eagletmt](https://github.com/eagletmt)) - Simpler method to find the subclass name [\#139](https://github.com/brendon/acts_as_list/pull/139) ([brendon](https://github.com/brendon)) - Rails4 enum column support [\#130](https://github.com/brendon/acts_as_list/pull/130) ([arunagw](https://github.com/arunagw)) - use eval for determing the self.class.name useful when this is used in an abstract class [\#123](https://github.com/brendon/acts_as_list/pull/123) ([flarik](https://github.com/flarik)) ## [0.5.0](https://github.com/brendon/acts_as_list/tree/0.5.0) (2014-10-31) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.4.0...0.5.0) **Closed issues:** - I want to have my existing records works like list [\#133](https://github.com/brendon/acts_as_list/issues/133) - Add Support For Multiple Indexes [\#127](https://github.com/brendon/acts_as_list/issues/127) - changing parent\_id does not update item positions [\#126](https://github.com/brendon/acts_as_list/issues/126) - How to exclude objects to be positioned? [\#125](https://github.com/brendon/acts_as_list/issues/125) - Scope for Polymorphic association + ManyToMany [\#106](https://github.com/brendon/acts_as_list/issues/106) - Bug when use \#insert\_at on an invalid ActiveRecord object [\#99](https://github.com/brendon/acts_as_list/issues/99) - has\_many :through with acts as list [\#95](https://github.com/brendon/acts_as_list/issues/95) - Update position when scope changes [\#19](https://github.com/brendon/acts_as_list/issues/19) **Merged pull requests:** - Cast column default value to int before comparing with position column [\#129](https://github.com/brendon/acts_as_list/pull/129) ([wioux](https://github.com/wioux)) - Fix travis builds for rbx [\#128](https://github.com/brendon/acts_as_list/pull/128) ([meineerde](https://github.com/meineerde)) - Use unscoped blocks instead of chaining [\#121](https://github.com/brendon/acts_as_list/pull/121) ([brendon](https://github.com/brendon)) - Make acts\_as\_list more compatible with BINARY column [\#116](https://github.com/brendon/acts_as_list/pull/116) ([sikachu](https://github.com/sikachu)) - Added help notes on non-association scopes [\#115](https://github.com/brendon/acts_as_list/pull/115) ([VorontsovIE](https://github.com/VorontsovIE)) - Let AR::Base properly lazy-loaded if Railtie is available [\#114](https://github.com/brendon/acts_as_list/pull/114) ([amatsuda](https://github.com/amatsuda)) ## [0.4.0](https://github.com/brendon/acts_as_list/tree/0.4.0) (2014-02-22) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.3.0...0.4.0) **Closed issues:** - insert\_at creates gaps [\#108](https://github.com/brendon/acts_as_list/issues/108) - move\_lower and move\_higher not working returning nil [\#57](https://github.com/brendon/acts_as_list/issues/57) - Mass-assignment issue with 0.1.8 [\#50](https://github.com/brendon/acts_as_list/issues/50) - validates error [\#49](https://github.com/brendon/acts_as_list/issues/49) - Ability to move multiple at once [\#40](https://github.com/brendon/acts_as_list/issues/40) - Duplicates created when using accepts\_nested\_attributes\_for [\#29](https://github.com/brendon/acts_as_list/issues/29) **Merged pull requests:** - Update README [\#107](https://github.com/brendon/acts_as_list/pull/107) ([Senjai](https://github.com/Senjai)) - Add license info: license file and gemspec [\#105](https://github.com/brendon/acts_as_list/pull/105) ([chulkilee](https://github.com/chulkilee)) - Fix top position when position is lower than top position [\#104](https://github.com/brendon/acts_as_list/pull/104) ([csaura](https://github.com/csaura)) - Get specs running under Rails 4.1.0.beta1 [\#101](https://github.com/brendon/acts_as_list/pull/101) ([petergoldstein](https://github.com/petergoldstein)) - Add support for JRuby and Rubinius specs [\#100](https://github.com/brendon/acts_as_list/pull/100) ([petergoldstein](https://github.com/petergoldstein)) - Use the correct syntax for conditions in Rails 4 on the readme. [\#94](https://github.com/brendon/acts_as_list/pull/94) ([gotjosh](https://github.com/gotjosh)) - Adds `required\_ruby\_version` to gemspec [\#90](https://github.com/brendon/acts_as_list/pull/90) ([tvdeyen](https://github.com/tvdeyen)) ## [0.3.0](https://github.com/brendon/acts_as_list/tree/0.3.0) (2013-08-02) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.2.0...0.3.0) **Closed issues:** - act\_as\_list didn't install with bundle install [\#83](https://github.com/brendon/acts_as_list/issues/83) - Cannot update to version 0.1.7 [\#48](https://github.com/brendon/acts_as_list/issues/48) - when position is null all new items get inserted in position 1 [\#41](https://github.com/brendon/acts_as_list/issues/41) **Merged pull requests:** - Test against activerecord v3 and v4 [\#82](https://github.com/brendon/acts_as_list/pull/82) ([sanemat](https://github.com/sanemat)) - Fix check\_scope to work on lists with array scopes [\#81](https://github.com/brendon/acts_as_list/pull/81) ([conzett](https://github.com/conzett)) - Rails4 compatibility [\#80](https://github.com/brendon/acts_as_list/pull/80) ([philippfranke](https://github.com/philippfranke)) - Add tests for moving within scope and add method: move\_within\_scope [\#79](https://github.com/brendon/acts_as_list/pull/79) ([philippfranke](https://github.com/philippfranke)) - Option to not automatically add items to the list [\#72](https://github.com/brendon/acts_as_list/pull/72) ([forrest](https://github.com/forrest)) ## [0.2.0](https://github.com/brendon/acts_as_list/tree/0.2.0) (2013-02-28) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.1.9...0.2.0) **Merged pull requests:** - Fix update\_all deprecation warnings in Rails 4.0.0.beta1 [\#73](https://github.com/brendon/acts_as_list/pull/73) ([soffes](https://github.com/soffes)) - Add quotes to Id in SQL requests [\#69](https://github.com/brendon/acts_as_list/pull/69) ([noefroidevaux](https://github.com/noefroidevaux)) - Update position when scope changes [\#67](https://github.com/brendon/acts_as_list/pull/67) ([philippfranke](https://github.com/philippfranke)) - add and categorize public instance methods in readme; add misc notes to ... [\#66](https://github.com/brendon/acts_as_list/pull/66) ([barelyknown](https://github.com/barelyknown)) - Updates \#bottom\_item .find syntax to \>= Rails 3 compatible syntax. [\#65](https://github.com/brendon/acts_as_list/pull/65) ([tvdeyen](https://github.com/tvdeyen)) - add GitHub Flavored Markdown to README [\#63](https://github.com/brendon/acts_as_list/pull/63) ([phlipper](https://github.com/phlipper)) ## [0.1.9](https://github.com/brendon/acts_as_list/tree/0.1.9) (2012-12-04) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.1.8...0.1.9) **Closed issues:** - Mysql2 error [\#54](https://github.com/brendon/acts_as_list/issues/54) - Use alternative column name? [\#53](https://github.com/brendon/acts_as_list/issues/53) **Merged pull requests:** - attr-accessible can be damaging, is not always necessary. [\#60](https://github.com/brendon/acts_as_list/pull/60) ([graemeworthy](https://github.com/graemeworthy)) - More reliable lower/higher item detection [\#59](https://github.com/brendon/acts_as_list/pull/59) ([miks](https://github.com/miks)) - Instructions for using an array with scope [\#58](https://github.com/brendon/acts_as_list/pull/58) ([zukowski](https://github.com/zukowski)) - Attr accessible patch, should solve \#50 [\#51](https://github.com/brendon/acts_as_list/pull/51) ([fabn](https://github.com/fabn)) - support accepts\_nested\_attributes\_for multi-destroy [\#46](https://github.com/brendon/acts_as_list/pull/46) ([saberma](https://github.com/saberma)) ## [0.1.8](https://github.com/brendon/acts_as_list/tree/0.1.8) (2012-08-09) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.1.7...0.1.8) ## [0.1.7](https://github.com/brendon/acts_as_list/tree/0.1.7) (2012-08-09) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.1.6...0.1.7) **Closed issues:** - Remove use of update\_attribute [\#44](https://github.com/brendon/acts_as_list/issues/44) - Order is reversed when adding multiple rows at once [\#34](https://github.com/brendon/acts_as_list/issues/34) **Merged pull requests:** - Fixed issue with update\_positions that wasn't taking 'scope\_condition' into account [\#47](https://github.com/brendon/acts_as_list/pull/47) ([bastien](https://github.com/bastien)) - Replaced usage of update\_attribute with update\_attribute! [\#45](https://github.com/brendon/acts_as_list/pull/45) ([kevmoo](https://github.com/kevmoo)) - use self.class.primary\_key instead of id in shuffle\_positions\_on\_intermediate\_items [\#42](https://github.com/brendon/acts_as_list/pull/42) ([servercrunch](https://github.com/servercrunch)) - initialize gem [\#39](https://github.com/brendon/acts_as_list/pull/39) ([megatux](https://github.com/megatux)) - Added ability to set item positions directly \(e.g. In a form\) [\#38](https://github.com/brendon/acts_as_list/pull/38) ([dubroe](https://github.com/dubroe)) - Prevent SQL error when position\_column is not unique [\#37](https://github.com/brendon/acts_as_list/pull/37) ([hinrik](https://github.com/hinrik)) - Add installation instructions to README.md [\#35](https://github.com/brendon/acts_as_list/pull/35) ([mark-rushakoff](https://github.com/mark-rushakoff)) ## [0.1.6](https://github.com/brendon/acts_as_list/tree/0.1.6) (2012-04-19) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.1.5...0.1.6) **Closed issues:** - eval mistakenly resolved the module path [\#32](https://github.com/brendon/acts_as_list/issues/32) - Duplicated positions when creating parent and children from scratch in 0.1.5 [\#31](https://github.com/brendon/acts_as_list/issues/31) - add info about v0.1.5 require Rails 3 [\#28](https://github.com/brendon/acts_as_list/issues/28) - position not updated with move\_higher or move\_lover [\#23](https://github.com/brendon/acts_as_list/issues/23) **Merged pull requests:** - update ActiveRecord class eval to support ActiveSupport on\_load [\#33](https://github.com/brendon/acts_as_list/pull/33) ([mergulhao](https://github.com/mergulhao)) - Add :add\_new\_at option [\#30](https://github.com/brendon/acts_as_list/pull/30) ([mjbellantoni](https://github.com/mjbellantoni)) ## [0.1.5](https://github.com/brendon/acts_as_list/tree/0.1.5) (2012-02-24) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.1.4...0.1.5) **Closed issues:** - increment\_positions\_on\_lower\_items called twice on insert\_at with new item [\#21](https://github.com/brendon/acts_as_list/issues/21) - Change bundler dependency from ~\>1.0.0 to ~\>1.0 [\#20](https://github.com/brendon/acts_as_list/issues/20) - decrement\_positions\_on\_lower\_items method [\#17](https://github.com/brendon/acts_as_list/issues/17) - New gem release [\#16](https://github.com/brendon/acts_as_list/issues/16) - acts\_as\_list :scope =\> "doesnt\_seem\_to\_work" [\#12](https://github.com/brendon/acts_as_list/issues/12) - don't work perfectly with default\_scope [\#11](https://github.com/brendon/acts_as_list/issues/11) - MySQL: Position column MUST NOT have default [\#10](https://github.com/brendon/acts_as_list/issues/10) - insert\_at fails on postgresql w/ non-null constraint on postion\_column [\#8](https://github.com/brendon/acts_as_list/issues/8) **Merged pull requests:** - Efficiency improvement for insert\_at when repositioning an existing item [\#27](https://github.com/brendon/acts_as_list/pull/27) ([bradediger](https://github.com/bradediger)) - Use before validate instead of before create [\#25](https://github.com/brendon/acts_as_list/pull/25) ([webervin](https://github.com/webervin)) - Massive test refactorings. [\#24](https://github.com/brendon/acts_as_list/pull/24) ([splattael](https://github.com/splattael)) - Silent migrations to reduce test noise. [\#22](https://github.com/brendon/acts_as_list/pull/22) ([splattael](https://github.com/splattael)) - Should decrement lower items after the item has been destroyed to avoid unique key conflicts. [\#18](https://github.com/brendon/acts_as_list/pull/18) ([aepstein](https://github.com/aepstein)) - Fix spelling and grammer [\#15](https://github.com/brendon/acts_as_list/pull/15) ([tmiller](https://github.com/tmiller)) - store\_at\_0 should yank item from the list then decrement items to avoid r [\#14](https://github.com/brendon/acts_as_list/pull/14) ([aepstein](https://github.com/aepstein)) - Support default\_scope ordering by calling .unscoped [\#13](https://github.com/brendon/acts_as_list/pull/13) ([tanordheim](https://github.com/tanordheim)) ## [0.1.4](https://github.com/brendon/acts_as_list/tree/0.1.4) (2011-07-27) [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.1.3...0.1.4) **Merged pull requests:** - Fix sqlite3 dependency [\#7](https://github.com/brendon/acts_as_list/pull/7) ([joneslee85](https://github.com/joneslee85)) ## [0.1.3](https://github.com/brendon/acts_as_list/tree/0.1.3) (2011-06-10) **Closed issues:** - Graph like behaviour [\#5](https://github.com/brendon/acts_as_list/issues/5) - Updated Gem? [\#4](https://github.com/brendon/acts_as_list/issues/4) **Merged pull requests:** - Converted into a gem... plus some slight refactors [\#6](https://github.com/brendon/acts_as_list/pull/6) ([chaffeqa](https://github.com/chaffeqa)) - Fixed test issue for test\_injection: expected SQL was reversed. [\#3](https://github.com/brendon/acts_as_list/pull/3) ([afriqs](https://github.com/afriqs)) - Added an option to set the top of the position [\#2](https://github.com/brendon/acts_as_list/pull/2) ([danielcooper](https://github.com/danielcooper)) - minor change to acts\_as\_list's callbacks [\#1](https://github.com/brendon/acts_as_list/pull/1) ([tiegz](https://github.com/tiegz)) acts_as_list-1.0.4/Appraisals0000644000004100000410000000122114042233640016230 0ustar www-datawww-dataappraise "rails-4-2" do group :mysql do gem "mysql2", "~> 0.4.0" end group :postgresql do gem "pg", "~> 0.18.4" end group :test do gem "test_after_commit", "~> 0.4.2" end group :sqlite do gem "sqlite3", "~> 1.3.13" end gem "activerecord", "~> 4.2.0" end appraise "rails-5-0" do group :sqlite do gem "sqlite3", "~> 1.3.13" end gem "activerecord", "~> 5.0.0" end appraise "rails-5-1" do gem "activerecord", "~> 5.1.0" end appraise "rails-5-2" do gem "activerecord", "~> 5.2.0" end appraise "rails-6-0" do gem "activerecord", "~> 6.0.0" end appraise "rails-6-1" do gem "activerecord", "6.1.0.rc1" end acts_as_list-1.0.4/init.rb0000644000004100000410000000014114042233640015476 0ustar www-datawww-data# frozen_string_literal: true $:.unshift "#{File.dirname(__FILE__)}/lib" require "acts_as_list" acts_as_list-1.0.4/.gitignore0000644000004100000410000000022414042233640016200 0ustar www-datawww-data*.gem .bundle Gemfile.lock pkg/* .rvmrc *.tmproj .rbenv-version .ruby-gemset .ruby-version # Appraisal generated lockfiles *.gemfile.lock .DS_Store acts_as_list-1.0.4/.gemtest0000644000004100000410000000000014042233640015651 0ustar www-datawww-dataacts_as_list-1.0.4/Rakefile0000644000004100000410000000225514042233640015663 0ustar www-datawww-datarequire "rubygems" require "bundler/setup" Bundler::GemHelper.install_tasks require "rake/testtask" # Run the test with "rake" or "rake test" desc "Default: run acts_as_list unit tests." task default: :test desc "Test the acts_as_list plugin." Rake::TestTask.new(:test) do |t| t.libs << "test" << "." t.test_files = Rake::FileList["test/**/test_*.rb"] t.verbose = false end begin # Run the rdoc task to generate rdocs for this gem require "rdoc/task" RDoc::Task.new do |rdoc| require "acts_as_list/version" version = ActiveRecord::Acts::List::VERSION rdoc.rdoc_dir = "rdoc" rdoc.title = "acts_as_list #{version}" rdoc.rdoc_files.include("README*") rdoc.rdoc_files.include("lib/**/*.rb") end rescue LoadError puts "RDocTask is not supported on this platform." rescue StandardError puts "RDocTask is not supported on this platform." end # See https://github.com/skywinder/github-changelog-generator#rake-task for details # and github_changelog_generator --help for available options require 'github_changelog_generator/task' GitHubChangelogGenerator::RakeTask.new :changelog do |config| config.project = 'acts_as_list' config.user = 'brendon' end acts_as_list-1.0.4/lib/0000755000004100000410000000000014042233640014760 5ustar www-datawww-dataacts_as_list-1.0.4/lib/acts_as_list.rb0000644000004100000410000000121714042233640017756 0ustar www-datawww-data# frozen_string_literal: true require "acts_as_list/active_record/acts/list" require "acts_as_list/active_record/acts/position_column_method_definer" require "acts_as_list/active_record/acts/scope_method_definer" require "acts_as_list/active_record/acts/top_of_list_method_definer" require "acts_as_list/active_record/acts/add_new_at_method_definer" 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/no_update" require "acts_as_list/active_record/acts/sequential_updates_method_definer" require "acts_as_list/active_record/acts/active_record" acts_as_list-1.0.4/lib/acts_as_list/0000755000004100000410000000000014042233640017430 5ustar www-datawww-dataacts_as_list-1.0.4/lib/acts_as_list/version.rb0000644000004100000410000000017314042233640021443 0ustar www-datawww-data# frozen_string_literal: true module ActiveRecord module Acts module List VERSION = '1.0.4' end end end acts_as_list-1.0.4/lib/acts_as_list/active_record/0000755000004100000410000000000014042233640022241 5ustar www-datawww-dataacts_as_list-1.0.4/lib/acts_as_list/active_record/acts/0000755000004100000410000000000014042233640023173 5ustar www-datawww-dataacts_as_list-1.0.4/lib/acts_as_list/active_record/acts/list.rb0000644000004100000410000004553614042233640024510 0ustar www-datawww-data# frozen_string_literal: true module ActiveRecord module Acts #:nodoc: module List #:nodoc: module ClassMethods # Configuration options are: # # * +column+ - specifies the column name to use for keeping the position integer (default: +position+) # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach _id # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key. # Example: acts_as_list scope: 'todo_list_id = #{todo_list_id} AND completed = 0' # * +top_of_list+ - defines the integer used for the top of the list. Defaults to 1. Use 0 to make the collection # act more like an array in its indexing. # * +add_new_at+ - specifies whether objects get added to the :top or :bottom of the list. (default: +bottom+) # `nil` will result in new items not being added to the list on create. # * +sequential_updates+ - specifies whether insert_at should update objects positions during shuffling # one by one to respect position column unique not null constraint. # Defaults to true if position column has unique index, otherwise false. # If constraint is deferrable initially deferred, overriding it with false will speed up insert_at. # * +touch_on_update+ - configuration to disable the update of the model timestamps when the positions are updated. def acts_as_list(options = {}) configuration = { column: "position", scope: "1 = 1", top_of_list: 1, add_new_at: :bottom, touch_on_update: true } configuration.update(options) if options.is_a?(Hash) caller_class = self ActiveRecord::Acts::List::PositionColumnMethodDefiner.call(caller_class, configuration[:column], configuration[:touch_on_update]) ActiveRecord::Acts::List::ScopeMethodDefiner.call(caller_class, configuration[:scope]) ActiveRecord::Acts::List::TopOfListMethodDefiner.call(caller_class, configuration[:top_of_list]) ActiveRecord::Acts::List::AddNewAtMethodDefiner.call(caller_class, configuration[:add_new_at]) ActiveRecord::Acts::List::AuxMethodDefiner.call(caller_class) ActiveRecord::Acts::List::CallbackDefiner.call(caller_class, configuration[:add_new_at]) ActiveRecord::Acts::List::SequentialUpdatesMethodDefiner.call(caller_class, configuration[:column], configuration[:sequential_updates]) include ActiveRecord::Acts::List::InstanceMethods include ActiveRecord::Acts::List::NoUpdate end # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list. # The class that has this specified needs to have a +position+ column defined as an integer on # the mapped database table. # # Todo list example: # # class TodoList < ActiveRecord::Base # has_many :todo_items, order: "position" # end # # class TodoItem < ActiveRecord::Base # belongs_to :todo_list # acts_as_list scope: :todo_list # end # # todo_list.first.move_to_bottom # todo_list.last.move_higher # All the methods available to a record that has had acts_as_list specified. Each method works # by assuming the object to be the item in the list, so chapter.move_lower would move that chapter # lower in the list of all chapters. Likewise, chapter.first? would return +true+ if that chapter is # the first in the list of all chapters. end module InstanceMethods # Get the current position of the item in the list def current_position position = send(position_column) position ? position.to_i : nil end # Insert the item at the given position (defaults to the top position of 1). def insert_at(position = acts_as_list_top) insert_at_position(position) end def insert_at!(position = acts_as_list_top) insert_at_position(position, true) end # Swap positions with the next lower item, if one exists. def move_lower return unless lower_item acts_as_list_class.transaction do if lower_item.current_position != current_position swap_positions_with(lower_item) else lower_item.decrement_position increment_position end end end # Swap positions with the next higher item, if one exists. def move_higher return unless higher_item acts_as_list_class.transaction do if higher_item.current_position != current_position swap_positions_with(higher_item) else higher_item.increment_position decrement_position end end end # Move to the bottom of the list. If the item is already in the list, the items below it have their # position adjusted accordingly. def move_to_bottom return unless in_list? insert_at_position bottom_position_in_list.to_i end # Move to the top of the list. If the item is already in the list, the items above it have their # position adjusted accordingly. def move_to_top return unless in_list? insert_at_position acts_as_list_top end # Removes the item from the list. def remove_from_list if in_list? decrement_positions_on_lower_items set_list_position(nil) end end # Move the item within scope. If a position within the new scope isn't supplied, the item will # be appended to the end of the list. def move_within_scope(scope_id) send("#{scope_name}=", scope_id) save! end # Increase the position of this item without adjusting the rest of the list. def increment_position return unless in_list? set_list_position(current_position + 1) end # Decrease the position of this item without adjusting the rest of the list. def decrement_position return unless in_list? set_list_position(current_position - 1) end def first? return false unless in_list? !higher_items(1).exists? end def last? return false unless in_list? !lower_items(1).exists? end # Return the next higher item in the list. def higher_item return nil unless in_list? higher_items(1).first end # Return the next n higher items in the list # selects all higher items by default def higher_items(limit=nil) limit ||= acts_as_list_list.count acts_as_list_list. where("#{quoted_position_column_with_table_name} <= ?", current_position). where("#{quoted_table_name}.#{self.class.primary_key} != ?", self.send(self.class.primary_key)). reorder(acts_as_list_order_argument(:desc)). limit(limit) end # Return the next lower item in the list. def lower_item return nil unless in_list? lower_items(1).first end # Return the next n lower items in the list # selects all lower items by default def lower_items(limit=nil) limit ||= acts_as_list_list.count acts_as_list_list. where("#{quoted_position_column_with_table_name} >= ?", current_position). where("#{quoted_table_name}.#{self.class.primary_key} != ?", self.send(self.class.primary_key)). reorder(acts_as_list_order_argument(:asc)). limit(limit) end # Test if this record is in a list def in_list? !not_in_list? end def not_in_list? current_position.nil? end def default_position acts_as_list_class.column_defaults[position_column.to_s] end def default_position? default_position && default_position == current_position end # Sets the new position and saves it def set_list_position(new_position, raise_exception_if_save_fails=false) self[position_column] = new_position raise_exception_if_save_fails ? save! : save end private def swap_positions_with(item) item_position = item.current_position item.set_list_position(current_position) set_list_position(item_position) end def acts_as_list_list acts_as_list_class.default_scoped.unscope(:select, :where).where(scope_condition) end # Poorly named methods. They will insert the item at the desired position if the position # has been set manually using position=, not necessarily the top or bottom of the list: def add_to_list_top if assume_default_position? increment_positions_on_all_items self[position_column] = acts_as_list_top else increment_positions_on_lower_items(self[position_column], id) end # Make sure we know that we've processed this scope change already @scope_changed = false # Don't halt the callback chain true end def add_to_list_bottom if assume_default_position? self[position_column] = bottom_position_in_list.to_i + 1 else increment_positions_on_lower_items(self[position_column], id) end # Make sure we know that we've processed this scope change already @scope_changed = false # Don't halt the callback chain true end def assume_default_position? not_in_list? || persisted? && internal_scope_changed? && !position_changed || default_position? end # Overwrite this method to define the scope of the list changes def scope_condition() {} end # Returns the bottom position number in the list. # bottom_position_in_list # => 2 def bottom_position_in_list(except = nil) item = bottom_item(except) item ? item.current_position : acts_as_list_top - 1 end # Returns the bottom item def bottom_item(except = nil) scope = acts_as_list_list if except scope = scope.where("#{quoted_table_name}.#{self.class.primary_key} != ?", except.id) end scope.in_list.reorder(acts_as_list_order_argument(:desc)).first end # Forces item to assume the bottom position in the list. def assume_bottom_position set_list_position(bottom_position_in_list(self).to_i + 1) end # Forces item to assume the top position in the list. def assume_top_position set_list_position(acts_as_list_top) end # This has the effect of moving all the higher items down one. def increment_positions_on_higher_items return unless in_list? acts_as_list_list.where("#{quoted_position_column_with_table_name} < ?", current_position).increment_all end # This has the effect of moving all the lower items down one. def increment_positions_on_lower_items(position, avoid_id = nil) scope = acts_as_list_list if avoid_id scope = scope.where("#{quoted_table_name}.#{self.class.primary_key} != ?", avoid_id) end if sequential_updates? scope.where("#{quoted_position_column_with_table_name} >= ?", position).reorder(acts_as_list_order_argument(:desc)).increment_sequentially else scope.where("#{quoted_position_column_with_table_name} >= ?", position).increment_all end end # This has the effect of moving all the higher items up one. def decrement_positions_on_higher_items(position) acts_as_list_list.where("#{quoted_position_column_with_table_name} <= ?", position).decrement_all end # This has the effect of moving all the lower items up one. def decrement_positions_on_lower_items(position=current_position) return unless in_list? if sequential_updates? acts_as_list_list.where("#{quoted_position_column_with_table_name} > ?", position).reorder(acts_as_list_order_argument(:asc)).decrement_sequentially else acts_as_list_list.where("#{quoted_position_column_with_table_name} > ?", position).decrement_all end end # Increments position (position_column) of all items in the list. def increment_positions_on_all_items acts_as_list_list.increment_all end # Reorders intermediate items to support moving an item from old_position to new_position. # unique constraint prevents regular increment_all and forces to do increments one by one # http://stackoverflow.com/questions/7703196/sqlite-increment-unique-integer-field # both SQLite and PostgreSQL (and most probably MySQL too) has same issue # that's why *sequential_updates?* check alters implementation behavior def shuffle_positions_on_intermediate_items(old_position, new_position, avoid_id = nil) return if old_position == new_position scope = acts_as_list_list if avoid_id scope = scope.where("#{quoted_table_name}.#{self.class.primary_key} != ?", avoid_id) end if old_position < new_position # Decrement position of intermediate items # # e.g., if moving an item from 2 to 5, # move [3, 4, 5] to [2, 3, 4] items = scope.where( "#{quoted_position_column_with_table_name} > ?", old_position ).where( "#{quoted_position_column_with_table_name} <= ?", new_position ) if sequential_updates? items.reorder(acts_as_list_order_argument(:asc)).decrement_sequentially else items.decrement_all end else # Increment position of intermediate items # # e.g., if moving an item from 5 to 2, # move [2, 3, 4] to [3, 4, 5] items = scope.where( "#{quoted_position_column_with_table_name} >= ?", new_position ).where( "#{quoted_position_column_with_table_name} < ?", old_position ) if sequential_updates? items.reorder(acts_as_list_order_argument(:desc)).increment_sequentially else items.increment_all end end end def insert_at_position(position, raise_exception_if_save_fails=false) raise ArgumentError.new("position cannot be lower than top") if position < acts_as_list_top return set_list_position(position, raise_exception_if_save_fails) if new_record? with_lock do if in_list? old_position = current_position return if position == old_position # temporary move after bottom with gap, avoiding duplicate values # gap is required to leave room for position increments # positive number will be valid with unique not null check (>= 0) db constraint temporary_position = bottom_position_in_list + 2 set_list_position(temporary_position, raise_exception_if_save_fails) shuffle_positions_on_intermediate_items(old_position, position, id) else increment_positions_on_lower_items(position) end set_list_position(position, raise_exception_if_save_fails) end end def update_positions return unless position_before_save_changed? old_position = position_before_save || bottom_position_in_list + 1 return unless current_position && acts_as_list_list.where( "#{quoted_position_column_with_table_name} = #{current_position}" ).count > 1 shuffle_positions_on_intermediate_items old_position, current_position, id end def position_before_save_changed? if active_record_version_is?('>= 5.1') saved_change_to_attribute? position_column else attribute_changed? position_column end end def position_before_save if active_record_version_is?('>= 5.1') attribute_before_last_save position_column else attribute_was position_column end end def internal_scope_changed? return @scope_changed if defined?(@scope_changed) @scope_changed = scope_changed? end def clear_scope_changed remove_instance_variable(:@scope_changed) if defined?(@scope_changed) end def check_scope if internal_scope_changed? cached_changes = changes cached_changes.each { |attribute, values| send("#{attribute}=", values[0]) } send('decrement_positions_on_lower_items') if lower_item cached_changes.each { |attribute, values| send("#{attribute}=", values[1]) } send("add_to_list_#{add_new_at}") if add_new_at.present? end end # This check is skipped if the position is currently the default position from the table # as modifying the default position on creation is handled elsewhere def check_top_position if current_position && !default_position? && current_position < acts_as_list_top self[position_column] = acts_as_list_top end end # When using raw column name it must be quoted otherwise it can raise syntax errors with SQL keywords (e.g. order) def quoted_position_column @_quoted_position_column ||= self.class.connection.quote_column_name(position_column) end # Used in order clauses def quoted_table_name @_quoted_table_name ||= acts_as_list_class.quoted_table_name end def quoted_position_column_with_table_name @_quoted_position_column_with_table_name ||= "#{quoted_table_name}.#{quoted_position_column}" end def acts_as_list_order_argument(direction = :asc) { position_column => direction } end def active_record_version_is?(version_requirement) requirement = Gem::Requirement.new(version_requirement) version = Gem.loaded_specs['activerecord'].version requirement.satisfied_by?(version) end end end end end acts_as_list-1.0.4/lib/acts_as_list/active_record/acts/active_record.rb0000644000004100000410000000017314042233640026332 0ustar www-datawww-data# frozen_string_literal: true ActiveSupport.on_load :active_record do extend ActiveRecord::Acts::List::ClassMethods end acts_as_list-1.0.4/lib/acts_as_list/active_record/acts/aux_method_definer.rb0000644000004100000410000000036314042233640027353 0ustar www-datawww-data# frozen_string_literal: true module ActiveRecord::Acts::List::AuxMethodDefiner #:nodoc: def self.call(caller_class) caller_class.class_eval do define_method :acts_as_list_class do caller_class end end end end acts_as_list-1.0.4/lib/acts_as_list/active_record/acts/position_column_method_definer.rb0000644000004100000410000000603514042233640032001 0ustar www-datawww-data# frozen_string_literal: true module ActiveRecord::Acts::List::PositionColumnMethodDefiner #:nodoc: def self.call(caller_class, position_column, touch_on_update) define_class_methods(caller_class, position_column, touch_on_update) define_instance_methods(caller_class, position_column) if mass_assignment_protection_was_used_by_user?(caller_class) protect_attributes_from_mass_assignment(caller_class, position_column) end end private def self.define_class_methods(caller_class, position_column, touch_on_update) caller_class.class_eval do define_singleton_method :quoted_position_column do @_quoted_position_column ||= connection.quote_column_name(position_column) end define_singleton_method :quoted_position_column_with_table_name do @_quoted_position_column_with_table_name ||= "#{caller_class.quoted_table_name}.#{quoted_position_column}" end define_singleton_method :decrement_sequentially do pluck(primary_key).each do |id| where(primary_key => id).decrement_all end end define_singleton_method :increment_sequentially do pluck(primary_key).each do |id| where(primary_key => id).increment_all end end define_singleton_method :decrement_all do update_all_with_touch "#{quoted_position_column} = (#{quoted_position_column_with_table_name} - 1)" end define_singleton_method :increment_all do update_all_with_touch "#{quoted_position_column} = (#{quoted_position_column_with_table_name} + 1)" end define_singleton_method :update_all_with_touch do |updates| updates += touch_record_sql if touch_on_update update_all(updates) end private define_singleton_method :touch_record_sql do new.touch_record_sql end end end def self.define_instance_methods(caller_class, position_column) caller_class.class_eval do attr_reader :position_changed define_method :position_column do position_column end define_method :"#{position_column}=" do |position| self[position_column] = position @position_changed = true end define_method :touch_record_sql do cached_quoted_now = quoted_current_time_from_proper_timezone timestamp_attributes_for_update_in_model.map do |attr| ", #{connection.quote_column_name(attr)} = #{cached_quoted_now}" end.join end private delegate :connection, to: self def quoted_current_time_from_proper_timezone connection.quote(connection.quoted_date( current_time_from_proper_timezone)) end end end def self.mass_assignment_protection_was_used_by_user?(caller_class) caller_class.class_eval do respond_to?(:accessible_attributes) and accessible_attributes.present? end end def self.protect_attributes_from_mass_assignment(caller_class, position_column) caller_class.class_eval do attr_accessible position_column.to_sym end end end acts_as_list-1.0.4/lib/acts_as_list/active_record/acts/sequential_updates_method_definer.rb0000644000004100000410000000174014042233640032455 0ustar www-datawww-data# frozen_string_literal: true module ActiveRecord::Acts::List::SequentialUpdatesMethodDefiner #:nodoc: def self.call(caller_class, column, sequential_updates_option) caller_class.class_eval do define_method :sequential_updates? do if !defined?(@sequential_updates) if sequential_updates_option.nil? table_exists = if active_record_version_is?('>= 5') caller_class.connection.data_source_exists?(caller_class.table_name) else caller_class.connection.table_exists?(caller_class.table_name) end index_exists = caller_class.connection.index_exists?(caller_class.table_name, column, unique: true) @sequential_updates = table_exists && index_exists else @sequential_updates = sequential_updates_option end else @sequential_updates end end private :sequential_updates? end end end acts_as_list-1.0.4/lib/acts_as_list/active_record/acts/add_new_at_method_definer.rb0000644000004100000410000000037214042233640030643 0ustar www-datawww-data# frozen_string_literal: true module ActiveRecord::Acts::List::AddNewAtMethodDefiner #:nodoc: def self.call(caller_class, add_new_at) caller_class.class_eval do define_method :add_new_at do add_new_at end end end end acts_as_list-1.0.4/lib/acts_as_list/active_record/acts/no_update.rb0000644000004100000410000000734114042233640025503 0ustar www-datawww-data# frozen_string_literal: true module ActiveRecord module Acts module List module NoUpdate def self.included(base) base.extend ClassMethods end class ArrayTypeError < ArgumentError def initialize super("The first argument must be an array") end end class DisparityClassesError < ArgumentError def initialize super("The first argument should contain ActiveRecord or ApplicationRecord classes") end end module ClassMethods # Lets you selectively disable all act_as_list database updates # for the duration of a block. # # ==== Examples # # class TodoList < ActiveRecord::Base # has_many :todo_items, -> { order(position: :asc) } # end # # class TodoItem < ActiveRecord::Base # belongs_to :todo_list # # acts_as_list scope: :todo_list # end # # TodoItem.acts_as_list_no_update do # TodoList.first.update(position: 2) # end # # You can also pass an array of classes as an argument to disable database updates on just those classes. # It can be any ActiveRecord class that has acts_as_list enabled. # # ==== Examples # # class TodoList < ActiveRecord::Base # has_many :todo_items, -> { order(position: :asc) } # acts_as_list # end # # class TodoItem < ActiveRecord::Base # belongs_to :todo_list # has_many :todo_attachments, -> { order(position: :asc) } # # acts_as_list scope: :todo_list # end # # class TodoAttachment < ActiveRecord::Base # belongs_to :todo_list # acts_as_list scope: :todo_item # end # # TodoItem.acts_as_list_no_update([TodoAttachment]) do # TodoItem.find(10).update(position: 2) # TodoAttachment.find(10).update(position: 1) # TodoAttachment.find(11).update(position: 2) # TodoList.find(2).update(position: 3) # For this instance the callbacks will be called because we haven't passed the class as an argument # end def acts_as_list_no_update(extra_classes = [], &block) return raise ArrayTypeError unless extra_classes.is_a?(Array) extra_classes << self return raise DisparityClassesError unless active_record_objects?(extra_classes) NoUpdate.apply_to(extra_classes, &block) end private def active_record_objects?(extra_classes) extra_classes.all? { |klass| klass.ancestors.include? ActiveRecord::Base } end end class << self def apply_to(klasses) klasses.map {|klass| add_klass(klass)} yield ensure klasses.map {|klass| remove_klass(klass)} end def applied_to?(klass) !(klass.ancestors & extracted_klasses.keys).empty? end private def extracted_klasses Thread.current[:act_as_list_no_update] ||= {} end def add_klass(klass) extracted_klasses[klass] = 0 unless extracted_klasses.key?(klass) extracted_klasses[klass] += 1 end def remove_klass(klass) extracted_klasses[klass] -= 1 extracted_klasses.delete(klass) if extracted_klasses[klass] <= 0 end end def act_as_list_no_update? NoUpdate.applied_to?(self.class) end end end end end acts_as_list-1.0.4/lib/acts_as_list/active_record/acts/scope_method_definer.rb0000644000004100000410000000440714042233640027672 0ustar www-datawww-data# frozen_string_literal: true require "active_support/inflector" module ActiveRecord::Acts::List::ScopeMethodDefiner #:nodoc: extend ActiveSupport::Inflector def self.call(caller_class, scope) scope = idify(caller_class, scope) if scope.is_a?(Symbol) caller_class.class_eval do define_method :scope_name do scope end if scope.is_a?(Symbol) define_method :scope_condition do { scope => send(:"#{scope}") } end define_method :scope_changed? do changed.include?(scope_name.to_s) end define_method :destroyed_via_scope? do scope == (destroyed_by_association && destroyed_by_association.foreign_key.to_sym) end elsif scope.is_a?(Array) define_method :scope_condition do # The elements of the Array can be symbols, strings, or hashes. # If symbols or strings, they are treated as column names and the current value is looked up. # If hashes, they are treated as fixed values. scope.inject({}) do |hash, column_or_fixed_vals| if column_or_fixed_vals.is_a?(Hash) fixed_vals = column_or_fixed_vals hash.merge!(fixed_vals) else column = column_or_fixed_vals hash.merge!({ column.to_sym => read_attribute(column.to_sym) }) end end end define_method :scope_changed? do (scope_condition.keys & changed.map(&:to_sym)).any? end define_method :destroyed_via_scope? do scope_condition.keys.include? (destroyed_by_association && destroyed_by_association.foreign_key.to_sym) end else define_method :scope_condition do eval "%{#{scope}}" end define_method :scope_changed? do false end define_method :destroyed_via_scope? do false end end self.scope :in_list, lambda { where("#{quoted_position_column_with_table_name} IS NOT NULL") } end end def self.idify(caller_class, name) return name if name.to_s =~ /_id$/ if caller_class.reflections.key?(name.to_s) caller_class.reflections[name.to_s].foreign_key.to_sym else foreign_key(name).to_sym end end end acts_as_list-1.0.4/lib/acts_as_list/active_record/acts/top_of_list_method_definer.rb0000644000004100000410000000053714042233640031102 0ustar www-datawww-data# frozen_string_literal: true module ActiveRecord::Acts::List::TopOfListMethodDefiner #:nodoc: def self.call(caller_class, top_of_list) caller_class.class_eval do define_singleton_method :acts_as_list_top do top_of_list.to_i end define_method :acts_as_list_top do top_of_list.to_i end end end end acts_as_list-1.0.4/lib/acts_as_list/active_record/acts/callback_definer.rb0000644000004100000410000000144414042233640026753 0ustar www-datawww-data# frozen_string_literal: true module ActiveRecord::Acts::List::CallbackDefiner #:nodoc: def self.call(caller_class, add_new_at) caller_class.class_eval do before_validation :check_top_position, unless: :act_as_list_no_update? before_destroy :reload, unless: Proc.new { new_record? || destroyed_via_scope? || act_as_list_no_update? } after_destroy :decrement_positions_on_lower_items, unless: Proc.new { destroyed_via_scope? || act_as_list_no_update? } 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, unless: :act_as_list_no_update? end end end end acts_as_list-1.0.4/Gemfile0000644000004100000410000000054714042233640015513 0ustar www-datawww-datasource "http://rubygems.org" gemspec gem "rake" gem "appraisal" group :development do gem "github_changelog_generator", "1.9.0" end group :test do gem "minitest", "~> 5.0" gem "timecop" gem "mocha" end group :sqlite do gem "sqlite3", "~> 1.4" end group :postgresql do gem "pg", "~> 1.2.0" end group :mysql do gem "mysql2", "~> 0.5.0" end acts_as_list-1.0.4/.github/0000755000004100000410000000000014042233640015552 5ustar www-datawww-dataacts_as_list-1.0.4/.github/FUNDING.yml0000644000004100000410000000010114042233640017357 0ustar www-datawww-data# These are supported funding model platforms github: [brendon] acts_as_list-1.0.4/MIT-LICENSE0000644000004100000410000000205414042233640015647 0ustar www-datawww-dataCopyright (c) 2007 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.