pax_global_header00006660000000000000000000000064133375304250014517gustar00rootroot0000000000000052 comment=f721bba2a3fd1d87ba2f0d63717827e113f5bfc5 ruby-acts-as-list-0.9.15/000077500000000000000000000000001333753042500150765ustar00rootroot00000000000000ruby-acts-as-list-0.9.15/.gemtest000066400000000000000000000000001333753042500165350ustar00rootroot00000000000000ruby-acts-as-list-0.9.15/.gitignore000066400000000000000000000002241333753042500170640ustar00rootroot00000000000000*.gem .bundle Gemfile.lock pkg/* .rvmrc *.tmproj .rbenv-version .ruby-gemset .ruby-version # Appraisal generated lockfiles *.gemfile.lock .DS_Store ruby-acts-as-list-0.9.15/.travis.yml000066400000000000000000000030171333753042500172100ustar00rootroot00000000000000language: ruby cache: bundler # Explicit usage of containerized builds, should provide faster feedback # see https://docs.travis-ci.com/user/workers/container-based-infrastructure/ # and https://docs.travis-ci.com/user/ci-environment/#Virtualization-environments sudo: false before_install: - gem install bundler -v 1.16.1 before_script: - mysql -e 'create database acts_as_list;' - psql -c 'create database acts_as_list;' -U postgres rvm: - 1.9.3 - 2.0.0 - 2.1.10 - 2.2.6 - 2.3.6 - 2.4.3 - 2.5.0 env: - DB=sqlite - DB=mysql - DB=postgresql gemfile: - gemfiles/rails_3_2.gemfile - gemfiles/rails_4_1.gemfile - gemfiles/rails_4_2.gemfile - gemfiles/rails_5_0.gemfile - gemfiles/rails_5_1.gemfile - gemfiles/rails_5_2.gemfile matrix: exclude: - rvm: 1.9.3 gemfile: gemfiles/rails_5_0.gemfile - rvm: 1.9.3 gemfile: gemfiles/rails_5_1.gemfile - rvm: 1.9.3 gemfile: gemfiles/rails_5_2.gemfile - rvm: 2.0.0 gemfile: gemfiles/rails_5_0.gemfile - rvm: 2.0.0 gemfile: gemfiles/rails_5_1.gemfile - rvm: 2.0.0 gemfile: gemfiles/rails_5_2.gemfile - rvm: 2.1.10 gemfile: gemfiles/rails_5_0.gemfile - rvm: 2.1.10 gemfile: gemfiles/rails_5_1.gemfile - rvm: 2.1.10 gemfile: gemfiles/rails_5_2.gemfile - rvm: 2.4.3 gemfile: gemfiles/rails_3_2.gemfile - rvm: 2.4.3 gemfile: gemfiles/rails_4_1.gemfile - rvm: 2.5.0 gemfile: gemfiles/rails_3_2.gemfile - rvm: 2.5.0 gemfile: gemfiles/rails_4_1.gemfile ruby-acts-as-list-0.9.15/Appraisals000066400000000000000000000017021333753042500171200ustar00rootroot00000000000000appraise "rails-3-2" do group :mysql do gem "mysql2", "~> 0.3.21", platforms: [:ruby] end gem "activerecord", "~> 3.2.22.2" group :test do gem "after_commit_exception_notification" end end appraise "rails-4-1" do group :mysql do gem "mysql2", "~> 0.3.21", platforms: [:ruby] end gem "activerecord", "~> 4.1.16" group :test do gem "after_commit_exception_notification" end end appraise "rails-4-2" do group :mysql do gem "mysql2", "~> 0.4.10", platforms: [:ruby] end gem "activerecord", "~> 4.2.10" end appraise "rails-5-0" do group :mysql do gem "mysql2", "~> 0.4.10", platforms: [:ruby] end gem "activerecord", "~> 5.0.6" end appraise "rails-5-1" do group :mysql do gem "mysql2", "~> 0.4.10", platforms: [:ruby] end gem "activerecord", "~> 5.1.4" end appraise "rails-5-2" do group :mysql do gem "mysql2", "~> 0.4.10", platforms: [:ruby] end gem "activerecord", "~> 5.2.0.rc1" end ruby-acts-as-list-0.9.15/CHANGELOG.md000066400000000000000000001163341333753042500167170ustar00rootroot00000000000000# Change Log ## [v0.9.14](https://github.com/swanandp/acts_as_list/tree/v0.9.14) (2018-06-05) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.9.13...v0.9.14) **Closed issues:** - `insert\_at` saves invalid ActiveRecord objects [\#311](https://github.com/swanandp/acts_as_list/issues/311) **Merged pull requests:** - \#311 Don't insert invalid ActiveRecord objects [\#312](https://github.com/swanandp/acts_as_list/pull/312) ([seanabrahams](https://github.com/seanabrahams)) ## [v0.9.13](https://github.com/swanandp/acts_as_list/tree/v0.9.13) (2018-06-05) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/pull/313) ([yjukaku](https://github.com/yjukaku)) ## [v0.9.12](https://github.com/swanandp/acts_as_list/tree/v0.9.12) (2018-05-02) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.9.11...v0.9.12) **Closed issues:** - acts\_as\_list methods on has\_many through [\#308](https://github.com/swanandp/acts_as_list/issues/308) - Travis badge [\#307](https://github.com/swanandp/acts_as_list/issues/307) - Unscoping breaks STI subclasses, but is soon to be fixed in Rails [\#291](https://github.com/swanandp/acts_as_list/issues/291) - Refactor string eval for scope\_condition [\#227](https://github.com/swanandp/acts_as_list/issues/227) **Merged pull requests:** - mocha/minitest, not mocha/mini\_test now. [\#310](https://github.com/swanandp/acts_as_list/pull/310) ([jmarkbrooks](https://github.com/jmarkbrooks)) ## [v0.9.11](https://github.com/swanandp/acts_as_list/tree/v0.9.11) (2018-03-19) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/issues/302) - Switching to Semaphore [\#301](https://github.com/swanandp/acts_as_list/issues/301) - Dropping jruby support [\#300](https://github.com/swanandp/acts_as_list/issues/300) - Rails 5.2.0 [\#299](https://github.com/swanandp/acts_as_list/issues/299) - Cannot update record position when scoped to enum [\#298](https://github.com/swanandp/acts_as_list/issues/298) - `add\_new\_at: :top` does not work [\#296](https://github.com/swanandp/acts_as_list/issues/296) - remove\_from\_list causing "wrong number of arguments \(given 2, expected 0..1\)" [\#293](https://github.com/swanandp/acts_as_list/issues/293) - Passing raw strings to reorder deprecated in Rails 5.2 [\#290](https://github.com/swanandp/acts_as_list/issues/290) **Merged pull requests:** - Fix Test Suite [\#306](https://github.com/swanandp/acts_as_list/pull/306) ([brendon](https://github.com/brendon)) - Add frozen\_string\_literal pragma to ruby files [\#305](https://github.com/swanandp/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/swanandp/acts_as_list/pull/294) ([jhawthorn](https://github.com/jhawthorn)) ## [v0.9.10](https://github.com/swanandp/acts_as_list/tree/v0.9.10) (2017-11-19) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/issues/287) - Why does acts\_as\_list override rails validation on it's own field? [\#269](https://github.com/swanandp/acts_as_list/issues/269) **Merged pull requests:** - Change error classes parents [\#288](https://github.com/swanandp/acts_as_list/pull/288) ([alexander-lazarov](https://github.com/alexander-lazarov)) ## [v0.9.9](https://github.com/swanandp/acts_as_list/tree/v0.9.9) (2017-10-03) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.9.8...v0.9.9) **Merged pull requests:** - Added fixed values option for scope array [\#286](https://github.com/swanandp/acts_as_list/pull/286) ([smoyth](https://github.com/smoyth)) ## [v0.9.8](https://github.com/swanandp/acts_as_list/tree/v0.9.8) (2017-09-28) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.9.7...v0.9.8) **Closed issues:** - Deadlocking in update\_positions count query [\#285](https://github.com/swanandp/acts_as_list/issues/285) - Updating the position fails uniqueness constraint. [\#275](https://github.com/swanandp/acts_as_list/issues/275) ## [v0.9.7](https://github.com/swanandp/acts_as_list/tree/v0.9.7) (2017-07-06) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.9.6...v0.9.7) ## [v0.9.6](https://github.com/swanandp/acts_as_list/tree/v0.9.6) (2017-07-05) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.9.5...v0.9.6) **Closed issues:** - undefined method `+' for nil:NilClass [\#278](https://github.com/swanandp/acts_as_list/issues/278) - Enum does not scope correctly [\#277](https://github.com/swanandp/acts_as_list/issues/277) - Can we don't use remove\_from\_list when destroy a lot objects? [\#276](https://github.com/swanandp/acts_as_list/issues/276) - The NoUpdate code rely's on AS::Concern [\#273](https://github.com/swanandp/acts_as_list/issues/273) - ActiveRecord associations are no longer required \(even though belongs\_to\_required\_by\_default == true\) [\#268](https://github.com/swanandp/acts_as_list/issues/268) - Unique constraint violation on move\_higher, move\_lower, destroy [\#267](https://github.com/swanandp/acts_as_list/issues/267) **Merged pull requests:** - Fix Fixnum deprecation warnings. [\#282](https://github.com/swanandp/acts_as_list/pull/282) ([patrickdavey](https://github.com/patrickdavey)) - Fix update to scope that was defined with an enum [\#281](https://github.com/swanandp/acts_as_list/pull/281) ([scottmalone](https://github.com/scottmalone)) - Refactor update\_all\_with\_touch [\#279](https://github.com/swanandp/acts_as_list/pull/279) ([ledestin](https://github.com/ledestin)) - Remove AS::Concern from NoUpdate [\#274](https://github.com/swanandp/acts_as_list/pull/274) ([brendon](https://github.com/brendon)) - Use `ActiveSupport.on\_load` to hook into ActiveRecord [\#272](https://github.com/swanandp/acts_as_list/pull/272) ([brendon](https://github.com/brendon)) ## [v0.9.5](https://github.com/swanandp/acts_as_list/tree/v0.9.5) (2017-04-04) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/issues/264) - Be more precise with unscope-ing [\#263](https://github.com/swanandp/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/swanandp/acts_as_list/pull/266) ([brendon](https://github.com/brendon)) - Be more surgical about unscoping [\#265](https://github.com/swanandp/acts_as_list/pull/265) ([brendon](https://github.com/brendon)) ## [v0.9.4](https://github.com/swanandp/acts_as_list/tree/v0.9.4) (2017-03-16) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.9.3...v0.9.4) **Merged pull requests:** - Optimize first? and last? instance methods. [\#262](https://github.com/swanandp/acts_as_list/pull/262) ([marshall-lee](https://github.com/marshall-lee)) ## [v0.9.3](https://github.com/swanandp/acts_as_list/tree/v0.9.3) (2017-03-14) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.9.2...v0.9.3) **Closed issues:** - Rails 5.1.0.beta1 deprecation [\#257](https://github.com/swanandp/acts_as_list/issues/257) - Move item X after item Y [\#256](https://github.com/swanandp/acts_as_list/issues/256) - Is there way to specify the position when creating a resource? [\#255](https://github.com/swanandp/acts_as_list/issues/255) **Merged pull requests:** - Don't update a child destroyed via relation [\#261](https://github.com/swanandp/acts_as_list/pull/261) ([brendon](https://github.com/brendon)) - No update list for collection classes [\#260](https://github.com/swanandp/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/swanandp/acts_as_list/pull/259) ([CvX](https://github.com/CvX)) - Refactor column definer module [\#258](https://github.com/swanandp/acts_as_list/pull/258) ([ledestin](https://github.com/ledestin)) ## [v0.9.2](https://github.com/swanandp/acts_as_list/tree/v0.9.2) (2017-02-07) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.9.1...v0.9.2) **Closed issues:** - Getting invalid input syntax for uuid [\#253](https://github.com/swanandp/acts_as_list/issues/253) ## [v0.9.1](https://github.com/swanandp/acts_as_list/tree/v0.9.1) (2017-01-26) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/issues/251) - highter\_items returns items with the same position value [\#247](https://github.com/swanandp/acts_as_list/issues/247) - Broken with unique constraint on position [\#245](https://github.com/swanandp/acts_as_list/issues/245) **Merged pull requests:** - fixes \#251 table\_exists? deprecation warning with Rails 5.0 [\#252](https://github.com/swanandp/acts_as_list/pull/252) ([zharikovpro](https://github.com/zharikovpro)) ## [v0.9.0](https://github.com/swanandp/acts_as_list/tree/v0.9.0) (2017-01-23) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.8.2...v0.9.0) **Closed issues:** - warning: too many arguments for format string [\#239](https://github.com/swanandp/acts_as_list/issues/239) - Broken tests related to time comparison [\#238](https://github.com/swanandp/acts_as_list/issues/238) - Shuffling positions is halting the callback chain [\#234](https://github.com/swanandp/acts_as_list/issues/234) - Reorder positions [\#233](https://github.com/swanandp/acts_as_list/issues/233) - Tests break when upgrading from 0.7.2 to 0.7.4 [\#228](https://github.com/swanandp/acts_as_list/issues/228) - RE \#221 needing a test [\#226](https://github.com/swanandp/acts_as_list/issues/226) - Adding to existing model with data and methods don't work [\#209](https://github.com/swanandp/acts_as_list/issues/209) - Position is set incorrectly when circular dependencies exist [\#153](https://github.com/swanandp/acts_as_list/issues/153) **Merged pull requests:** - Revert "Updates documentation with valid string interpolation syntax" [\#250](https://github.com/swanandp/acts_as_list/pull/250) ([brendon](https://github.com/brendon)) - Updates documentation with valid string interpolation syntax [\#249](https://github.com/swanandp/acts_as_list/pull/249) ([naveedkakal](https://github.com/naveedkakal)) - Comply to tests warnings [\#248](https://github.com/swanandp/acts_as_list/pull/248) ([randoum](https://github.com/randoum)) - insert\_at respects unique not null check \(\>= 0\) db constraints [\#246](https://github.com/swanandp/acts_as_list/pull/246) ([zharikovpro](https://github.com/zharikovpro)) - acts\_as\_list\_no\_update [\#244](https://github.com/swanandp/acts_as_list/pull/244) ([randoum](https://github.com/randoum)) - Update README.md [\#243](https://github.com/swanandp/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/swanandp/acts_as_list/pull/242) ([brendon](https://github.com/brendon)) - Be explicit about ordering when mapping :pos [\#241](https://github.com/swanandp/acts_as_list/pull/241) ([brendon](https://github.com/brendon)) - Improve load method [\#240](https://github.com/swanandp/acts_as_list/pull/240) ([brendon](https://github.com/brendon)) - Fix non regular sequence movement [\#237](https://github.com/swanandp/acts_as_list/pull/237) ([tiagotex](https://github.com/tiagotex)) - Add travis config for testing against multiple databases [\#236](https://github.com/swanandp/acts_as_list/pull/236) ([fschwahn](https://github.com/fschwahn)) - Extract modules [\#229](https://github.com/swanandp/acts_as_list/pull/229) ([ledestin](https://github.com/ledestin)) ## [v0.8.2](https://github.com/swanandp/acts_as_list/tree/v0.8.2) (2016-09-23) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/issues/232) - Break away from rails/acts\_as\_list [\#224](https://github.com/swanandp/acts_as_list/issues/224) - Problem when inserting straight at top of list [\#109](https://github.com/swanandp/acts_as_list/issues/109) **Merged pull requests:** - Show items with same position in higher and lower items [\#231](https://github.com/swanandp/acts_as_list/pull/231) ([jpalumickas](https://github.com/jpalumickas)) - fix setting position when previous position was nil [\#230](https://github.com/swanandp/acts_as_list/pull/230) ([StoneFrog](https://github.com/StoneFrog)) ## [v0.8.1](https://github.com/swanandp/acts_as_list/tree/v0.8.1) (2016-09-06) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.8.0...v0.8.1) **Closed issues:** - Rubinius Intermittent testing error [\#218](https://github.com/swanandp/acts_as_list/issues/218) - ActiveRecord dependency causes rake assets:compile to fail without access to a database [\#84](https://github.com/swanandp/acts_as_list/issues/84) **Merged pull requests:** - Refactor class\_eval with string into class\_eval with block [\#215](https://github.com/swanandp/acts_as_list/pull/215) ([rdvdijk](https://github.com/rdvdijk)) ## [v0.8.0](https://github.com/swanandp/acts_as_list/tree/v0.8.0) (2016-08-23) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.7.7...v0.8.0) **Closed issues:** - Behavior with DB default seems unclear [\#219](https://github.com/swanandp/acts_as_list/issues/219) **Merged pull requests:** - No longer a need specify additional rbx gems [\#225](https://github.com/swanandp/acts_as_list/pull/225) ([brendon](https://github.com/brendon)) - Fix position when no serial positions [\#223](https://github.com/swanandp/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/swanandp/acts_as_list/pull/220) ([brendon](https://github.com/brendon)) ## [v0.7.7](https://github.com/swanandp/acts_as_list/tree/v0.7.7) (2016-08-18) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/issues/214) - Changing scope is inconsistent based on add\_new\_at [\#138](https://github.com/swanandp/acts_as_list/issues/138) - Duplicate positions and lost items [\#76](https://github.com/swanandp/acts_as_list/issues/76) **Merged pull requests:** - Add quoted table names to some columns [\#221](https://github.com/swanandp/acts_as_list/pull/221) ([jpalumickas](https://github.com/jpalumickas)) - Appraisals cleanup [\#217](https://github.com/swanandp/acts_as_list/pull/217) ([brendon](https://github.com/brendon)) - Fix insert\_at\_position in race condition [\#195](https://github.com/swanandp/acts_as_list/pull/195) ([danielross](https://github.com/danielross)) ## [v0.7.6](https://github.com/swanandp/acts_as_list/tree/v0.7.6) (2016-07-15) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.7.5...v0.7.6) **Closed issues:** - add\_new\_at nil with scope causes NoMethodError [\#211](https://github.com/swanandp/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/swanandp/acts_as_list/pull/213) ([krzysiek1507](https://github.com/krzysiek1507)) - Bugfix/add new at nil on scope change [\#212](https://github.com/swanandp/acts_as_list/pull/212) ([greatghoul](https://github.com/greatghoul)) ## [v0.7.5](https://github.com/swanandp/acts_as_list/tree/v0.7.5) (2016-06-30) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.7.4...v0.7.5) **Implemented enhancements:** - Touch when reordering [\#173](https://github.com/swanandp/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/swanandp/acts_as_list/issues/206) - Undefined instance variable @scope\_changed since 0.7.3 [\#199](https://github.com/swanandp/acts_as_list/issues/199) - Reordering large lists is slow [\#198](https://github.com/swanandp/acts_as_list/issues/198) - Reparenting child leaves gap in source list in rails 5 [\#194](https://github.com/swanandp/acts_as_list/issues/194) - Support rails 5 ? [\#186](https://github.com/swanandp/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/swanandp/acts_as_list/issues/176) - Phenomenon of mysterious value of the position is skipped by one [\#166](https://github.com/swanandp/acts_as_list/issues/166) - Model.find being called twice with acts\_as\_list on destroy [\#161](https://github.com/swanandp/acts_as_list/issues/161) - `scope\_changed?` problem with acts\_as\_paranoid [\#158](https://github.com/swanandp/acts_as_list/issues/158) - Inconsistent behaviour between Symbol and Array scopes [\#155](https://github.com/swanandp/acts_as_list/issues/155) - insert\_at doesn't seem to be working in ActiveRecord callback \(Rails 4.2\) [\#150](https://github.com/swanandp/acts_as_list/issues/150) - Project Documentation link redirects to expired domain [\#149](https://github.com/swanandp/acts_as_list/issues/149) - Problem when updating an position of array of AR objects. [\#137](https://github.com/swanandp/acts_as_list/issues/137) - Unexpected behaviour when inserting consecutive items with default positions [\#124](https://github.com/swanandp/acts_as_list/issues/124) - self.reload prone to error [\#122](https://github.com/swanandp/acts_as_list/issues/122) - Rails 3.0.x in\_list causes the return of default\_scope [\#120](https://github.com/swanandp/acts_as_list/issues/120) - Relationships with dependency:destroy cause ActiveRecord::RecordNotFound [\#118](https://github.com/swanandp/acts_as_list/issues/118) - Using insert\_at with values with type String [\#117](https://github.com/swanandp/acts_as_list/issues/117) - Batch setting of position [\#112](https://github.com/swanandp/acts_as_list/issues/112) - position: 0 now makes model pushed to top? [\#110](https://github.com/swanandp/acts_as_list/issues/110) - Create element in default position [\#103](https://github.com/swanandp/acts_as_list/issues/103) - Enhancement: Expose scope object [\#97](https://github.com/swanandp/acts_as_list/issues/97) - Shuffle list [\#96](https://github.com/swanandp/acts_as_list/issues/96) - Creating an item with a nil scope should not add it to the list [\#92](https://github.com/swanandp/acts_as_list/issues/92) - Performance Improvements [\#88](https://github.com/swanandp/acts_as_list/issues/88) - has\_many :through or has\_many\_and\_belongs\_to\_many support [\#86](https://github.com/swanandp/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/swanandp/acts_as_list/issues/77) - Limiting the list size [\#61](https://github.com/swanandp/acts_as_list/issues/61) - Adding multiple creates strange ordering [\#55](https://github.com/swanandp/acts_as_list/issues/55) - Feature: sort [\#26](https://github.com/swanandp/acts_as_list/issues/26) **Merged pull requests:** - Fix position when no serial positions [\#208](https://github.com/swanandp/acts_as_list/pull/208) ([PoslinskiNet](https://github.com/PoslinskiNet)) - Removed duplicated assignment [\#207](https://github.com/swanandp/acts_as_list/pull/207) ([shunwen](https://github.com/shunwen)) - Quote all identifiers [\#205](https://github.com/swanandp/acts_as_list/pull/205) ([fabn](https://github.com/fabn)) - Start testing Rails 5 [\#203](https://github.com/swanandp/acts_as_list/pull/203) ([brendon](https://github.com/brendon)) - Lock! the record before destroying [\#201](https://github.com/swanandp/acts_as_list/pull/201) ([brendon](https://github.com/brendon)) - Fix ambiguous column error when joining some relations [\#180](https://github.com/swanandp/acts_as_list/pull/180) ([natw](https://github.com/natw)) ## [v0.7.4](https://github.com/swanandp/acts_as_list/tree/v0.7.4) (2016-04-15) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.7.3...v0.7.4) **Closed issues:** - Releasing a new gem version [\#196](https://github.com/swanandp/acts_as_list/issues/196) **Merged pull requests:** - Fix scope changed [\#200](https://github.com/swanandp/acts_as_list/pull/200) ([brendon](https://github.com/brendon)) ## [v0.7.3](https://github.com/swanandp/acts_as_list/tree/v0.7.3) (2016-04-14) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.7.2...v0.7.3) ## [v0.7.2](https://github.com/swanandp/acts_as_list/tree/v0.7.2) (2016-04-01) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/issues/191) - Why is `add\_to\_list\_bottom` private? [\#187](https://github.com/swanandp/acts_as_list/issues/187) - Ordering of children when there are two possible parent models. [\#172](https://github.com/swanandp/acts_as_list/issues/172) - Fix the jruby and rbx builds [\#169](https://github.com/swanandp/acts_as_list/issues/169) - Unable to run tests [\#162](https://github.com/swanandp/acts_as_list/issues/162) - shuffle\_positions\_on\_intermediate\_items is creating problems [\#134](https://github.com/swanandp/acts_as_list/issues/134) - introduce Changelog file to quickly track changes [\#68](https://github.com/swanandp/acts_as_list/issues/68) - Mongoid support? [\#52](https://github.com/swanandp/acts_as_list/issues/52) **Merged pull requests:** - Add filename/line number to class\_eval call [\#193](https://github.com/swanandp/acts_as_list/pull/193) ([hfwang](https://github.com/hfwang)) - Use a symbol as a string to define callback [\#192](https://github.com/swanandp/acts_as_list/pull/192) ([brendon](https://github.com/brendon)) - Pin changelog generator to a working version [\#190](https://github.com/swanandp/acts_as_list/pull/190) ([fabn](https://github.com/fabn)) - Fix bug, position is recomputed when object saved [\#188](https://github.com/swanandp/acts_as_list/pull/188) ([chrisortman](https://github.com/chrisortman)) - Update bundler before running tests, fixes test run on travis [\#179](https://github.com/swanandp/acts_as_list/pull/179) ([fabn](https://github.com/fabn)) - Changelog generator, closes \#68 [\#177](https://github.com/swanandp/acts_as_list/pull/177) ([fabn](https://github.com/fabn)) - Updating README example [\#175](https://github.com/swanandp/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/swanandp/acts_as_list/pull/168) ([udit7590](https://github.com/udit7590)) - Small changes to DRY up list.rb [\#163](https://github.com/swanandp/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/swanandp/acts_as_list/pull/152) ([ludwigschubert](https://github.com/ludwigschubert)) ## [0.7.2](https://github.com/swanandp/acts_as_list/tree/0.7.2) (2015-05-06) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/0.7.1...0.7.2) ## [0.7.1](https://github.com/swanandp/acts_as_list/tree/0.7.1) (2015-05-06) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/0.7.0...0.7.1) **Merged pull requests:** - Update README.md [\#159](https://github.com/swanandp/acts_as_list/pull/159) ([tibastral](https://github.com/tibastral)) ## [0.7.0](https://github.com/swanandp/acts_as_list/tree/0.7.0) (2015-05-01) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/0.6.0...0.7.0) **Closed issues:** - Problem with reordering scoped list items [\#154](https://github.com/swanandp/acts_as_list/issues/154) - Can no longer load acts\_as\_list in isolation if Rails is installed [\#145](https://github.com/swanandp/acts_as_list/issues/145) **Merged pull requests:** - Fix regression with using acts\_as\_list on base classes [\#147](https://github.com/swanandp/acts_as_list/pull/147) ([botandrose](https://github.com/botandrose)) - Don't require rails when loading [\#146](https://github.com/swanandp/acts_as_list/pull/146) ([botandrose](https://github.com/botandrose)) ## [0.6.0](https://github.com/swanandp/acts_as_list/tree/0.6.0) (2014-12-24) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/issues/143) - Release a new gem version [\#136](https://github.com/swanandp/acts_as_list/issues/136) **Merged pull requests:** - Fix sanitize\_sql\_hash\_for\_conditions deprecation warning in Rails 4.2 [\#140](https://github.com/swanandp/acts_as_list/pull/140) ([eagletmt](https://github.com/eagletmt)) - Simpler method to find the subclass name [\#139](https://github.com/swanandp/acts_as_list/pull/139) ([brendon](https://github.com/brendon)) - Rails4 enum column support [\#130](https://github.com/swanandp/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/swanandp/acts_as_list/pull/123) ([flarik](https://github.com/flarik)) ## [0.5.0](https://github.com/swanandp/acts_as_list/tree/0.5.0) (2014-10-31) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/issues/133) - Add Support For Multiple Indexes [\#127](https://github.com/swanandp/acts_as_list/issues/127) - changing parent\_id does not update item positions [\#126](https://github.com/swanandp/acts_as_list/issues/126) - How to exclude objects to be positioned? [\#125](https://github.com/swanandp/acts_as_list/issues/125) - Scope for Polymorphic association + ManyToMany [\#106](https://github.com/swanandp/acts_as_list/issues/106) - Bug when use \#insert\_at on an invalid ActiveRecord object [\#99](https://github.com/swanandp/acts_as_list/issues/99) - has\_many :through with acts as list [\#95](https://github.com/swanandp/acts_as_list/issues/95) - Update position when scope changes [\#19](https://github.com/swanandp/acts_as_list/issues/19) **Merged pull requests:** - Cast column default value to int before comparing with position column [\#129](https://github.com/swanandp/acts_as_list/pull/129) ([wioux](https://github.com/wioux)) - Fix travis builds for rbx [\#128](https://github.com/swanandp/acts_as_list/pull/128) ([meineerde](https://github.com/meineerde)) - Use unscoped blocks instead of chaining [\#121](https://github.com/swanandp/acts_as_list/pull/121) ([brendon](https://github.com/brendon)) - Make acts\_as\_list more compatible with BINARY column [\#116](https://github.com/swanandp/acts_as_list/pull/116) ([sikachu](https://github.com/sikachu)) - Added help notes on non-association scopes [\#115](https://github.com/swanandp/acts_as_list/pull/115) ([VorontsovIE](https://github.com/VorontsovIE)) - Let AR::Base properly lazy-loaded if Railtie is available [\#114](https://github.com/swanandp/acts_as_list/pull/114) ([amatsuda](https://github.com/amatsuda)) ## [0.4.0](https://github.com/swanandp/acts_as_list/tree/0.4.0) (2014-02-22) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/0.3.0...0.4.0) **Closed issues:** - insert\_at creates gaps [\#108](https://github.com/swanandp/acts_as_list/issues/108) - move\_lower and move\_higher not working returning nil [\#57](https://github.com/swanandp/acts_as_list/issues/57) - Mass-assignment issue with 0.1.8 [\#50](https://github.com/swanandp/acts_as_list/issues/50) - validates error [\#49](https://github.com/swanandp/acts_as_list/issues/49) - Ability to move multiple at once [\#40](https://github.com/swanandp/acts_as_list/issues/40) - Duplicates created when using accepts\_nested\_attributes\_for [\#29](https://github.com/swanandp/acts_as_list/issues/29) **Merged pull requests:** - Update README [\#107](https://github.com/swanandp/acts_as_list/pull/107) ([Senjai](https://github.com/Senjai)) - Add license info: license file and gemspec [\#105](https://github.com/swanandp/acts_as_list/pull/105) ([chulkilee](https://github.com/chulkilee)) - Fix top position when position is lower than top position [\#104](https://github.com/swanandp/acts_as_list/pull/104) ([csaura](https://github.com/csaura)) - Get specs running under Rails 4.1.0.beta1 [\#101](https://github.com/swanandp/acts_as_list/pull/101) ([petergoldstein](https://github.com/petergoldstein)) - Add support for JRuby and Rubinius specs [\#100](https://github.com/swanandp/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/swanandp/acts_as_list/pull/94) ([gotjosh](https://github.com/gotjosh)) - Adds `required\_ruby\_version` to gemspec [\#90](https://github.com/swanandp/acts_as_list/pull/90) ([tvdeyen](https://github.com/tvdeyen)) ## [0.3.0](https://github.com/swanandp/acts_as_list/tree/0.3.0) (2013-08-02) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/issues/83) - Cannot update to version 0.1.7 [\#48](https://github.com/swanandp/acts_as_list/issues/48) - when position is null all new items get inserted in position 1 [\#41](https://github.com/swanandp/acts_as_list/issues/41) **Merged pull requests:** - Test against activerecord v3 and v4 [\#82](https://github.com/swanandp/acts_as_list/pull/82) ([sanemat](https://github.com/sanemat)) - Fix check\_scope to work on lists with array scopes [\#81](https://github.com/swanandp/acts_as_list/pull/81) ([conzett](https://github.com/conzett)) - Rails4 compatibility [\#80](https://github.com/swanandp/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/swanandp/acts_as_list/pull/79) ([philippfranke](https://github.com/philippfranke)) - Option to not automatically add items to the list [\#72](https://github.com/swanandp/acts_as_list/pull/72) ([forrest](https://github.com/forrest)) ## [0.2.0](https://github.com/swanandp/acts_as_list/tree/0.2.0) (2013-02-28) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/pull/73) ([soffes](https://github.com/soffes)) - Add quotes to Id in SQL requests [\#69](https://github.com/swanandp/acts_as_list/pull/69) ([noefroidevaux](https://github.com/noefroidevaux)) - Update position when scope changes [\#67](https://github.com/swanandp/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/swanandp/acts_as_list/pull/66) ([barelyknown](https://github.com/barelyknown)) - Updates \#bottom\_item .find syntax to \>= Rails 3 compatible syntax. [\#65](https://github.com/swanandp/acts_as_list/pull/65) ([tvdeyen](https://github.com/tvdeyen)) - add GitHub Flavored Markdown to README [\#63](https://github.com/swanandp/acts_as_list/pull/63) ([phlipper](https://github.com/phlipper)) ## [0.1.9](https://github.com/swanandp/acts_as_list/tree/0.1.9) (2012-12-04) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/0.1.8...0.1.9) **Closed issues:** - Mysql2 error [\#54](https://github.com/swanandp/acts_as_list/issues/54) - Use alternative column name? [\#53](https://github.com/swanandp/acts_as_list/issues/53) **Merged pull requests:** - attr-accessible can be damaging, is not always necessary. [\#60](https://github.com/swanandp/acts_as_list/pull/60) ([graemeworthy](https://github.com/graemeworthy)) - More reliable lower/higher item detection [\#59](https://github.com/swanandp/acts_as_list/pull/59) ([miks](https://github.com/miks)) - Instructions for using an array with scope [\#58](https://github.com/swanandp/acts_as_list/pull/58) ([zukowski](https://github.com/zukowski)) - Attr accessible patch, should solve \#50 [\#51](https://github.com/swanandp/acts_as_list/pull/51) ([fabn](https://github.com/fabn)) - support accepts\_nested\_attributes\_for multi-destroy [\#46](https://github.com/swanandp/acts_as_list/pull/46) ([saberma](https://github.com/saberma)) ## [0.1.8](https://github.com/swanandp/acts_as_list/tree/0.1.8) (2012-08-09) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/0.1.7...0.1.8) ## [0.1.7](https://github.com/swanandp/acts_as_list/tree/0.1.7) (2012-08-09) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/0.1.6...0.1.7) **Closed issues:** - Remove use of update\_attribute [\#44](https://github.com/swanandp/acts_as_list/issues/44) - Order is reversed when adding multiple rows at once [\#34](https://github.com/swanandp/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/swanandp/acts_as_list/pull/47) ([bastien](https://github.com/bastien)) - Replaced usage of update\_attribute with update\_attribute! [\#45](https://github.com/swanandp/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/swanandp/acts_as_list/pull/42) ([servercrunch](https://github.com/servercrunch)) - initialize gem [\#39](https://github.com/swanandp/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/swanandp/acts_as_list/pull/38) ([dubroe](https://github.com/dubroe)) - Prevent SQL error when position\_column is not unique [\#37](https://github.com/swanandp/acts_as_list/pull/37) ([hinrik](https://github.com/hinrik)) - Add installation instructions to README.md [\#35](https://github.com/swanandp/acts_as_list/pull/35) ([mark-rushakoff](https://github.com/mark-rushakoff)) ## [0.1.6](https://github.com/swanandp/acts_as_list/tree/0.1.6) (2012-04-19) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/0.1.5...0.1.6) **Closed issues:** - eval mistakenly resolved the module path [\#32](https://github.com/swanandp/acts_as_list/issues/32) - Duplicated positions when creating parent and children from scratch in 0.1.5 [\#31](https://github.com/swanandp/acts_as_list/issues/31) - add info about v0.1.5 require Rails 3 [\#28](https://github.com/swanandp/acts_as_list/issues/28) - position not updated with move\_higher or move\_lover [\#23](https://github.com/swanandp/acts_as_list/issues/23) **Merged pull requests:** - update ActiveRecord class eval to support ActiveSupport on\_load [\#33](https://github.com/swanandp/acts_as_list/pull/33) ([mergulhao](https://github.com/mergulhao)) - Add :add\_new\_at option [\#30](https://github.com/swanandp/acts_as_list/pull/30) ([mjbellantoni](https://github.com/mjbellantoni)) ## [0.1.5](https://github.com/swanandp/acts_as_list/tree/0.1.5) (2012-02-24) [Full Changelog](https://github.com/swanandp/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/swanandp/acts_as_list/issues/21) - Change bundler dependency from ~\>1.0.0 to ~\>1.0 [\#20](https://github.com/swanandp/acts_as_list/issues/20) - decrement\_positions\_on\_lower\_items method [\#17](https://github.com/swanandp/acts_as_list/issues/17) - New gem release [\#16](https://github.com/swanandp/acts_as_list/issues/16) - acts\_as\_list :scope =\> "doesnt\_seem\_to\_work" [\#12](https://github.com/swanandp/acts_as_list/issues/12) - don't work perfectly with default\_scope [\#11](https://github.com/swanandp/acts_as_list/issues/11) - MySQL: Position column MUST NOT have default [\#10](https://github.com/swanandp/acts_as_list/issues/10) - insert\_at fails on postgresql w/ non-null constraint on postion\_column [\#8](https://github.com/swanandp/acts_as_list/issues/8) **Merged pull requests:** - Efficiency improvement for insert\_at when repositioning an existing item [\#27](https://github.com/swanandp/acts_as_list/pull/27) ([bradediger](https://github.com/bradediger)) - Use before validate instead of before create [\#25](https://github.com/swanandp/acts_as_list/pull/25) ([webervin](https://github.com/webervin)) - Massive test refactorings. [\#24](https://github.com/swanandp/acts_as_list/pull/24) ([splattael](https://github.com/splattael)) - Silent migrations to reduce test noise. [\#22](https://github.com/swanandp/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/swanandp/acts_as_list/pull/18) ([aepstein](https://github.com/aepstein)) - Fix spelling and grammer [\#15](https://github.com/swanandp/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/swanandp/acts_as_list/pull/14) ([aepstein](https://github.com/aepstein)) - Support default\_scope ordering by calling .unscoped [\#13](https://github.com/swanandp/acts_as_list/pull/13) ([tanordheim](https://github.com/tanordheim)) ## [0.1.4](https://github.com/swanandp/acts_as_list/tree/0.1.4) (2011-07-27) [Full Changelog](https://github.com/swanandp/acts_as_list/compare/0.1.3...0.1.4) **Merged pull requests:** - Fix sqlite3 dependency [\#7](https://github.com/swanandp/acts_as_list/pull/7) ([joneslee85](https://github.com/joneslee85)) ## [0.1.3](https://github.com/swanandp/acts_as_list/tree/0.1.3) (2011-06-10) **Closed issues:** - Graph like behaviour [\#5](https://github.com/swanandp/acts_as_list/issues/5) - Updated Gem? [\#4](https://github.com/swanandp/acts_as_list/issues/4) **Merged pull requests:** - Converted into a gem... plus some slight refactors [\#6](https://github.com/swanandp/acts_as_list/pull/6) ([chaffeqa](https://github.com/chaffeqa)) - Fixed test issue for test\_injection: expected SQL was reversed. [\#3](https://github.com/swanandp/acts_as_list/pull/3) ([afriqs](https://github.com/afriqs)) - Added an option to set the top of the position [\#2](https://github.com/swanandp/acts_as_list/pull/2) ([danielcooper](https://github.com/danielcooper)) - minor change to acts\_as\_list's callbacks [\#1](https://github.com/swanandp/acts_as_list/pull/1) ([tiegz](https://github.com/tiegz)) \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*ruby-acts-as-list-0.9.15/Gemfile000066400000000000000000000006361333753042500163760ustar00rootroot00000000000000source "http://rubygems.org" gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21] gemspec gem "rake" gem "appraisal" gem "github_changelog_generator", "1.9.0" group :test do gem "minitest", "~> 5.0" gem "test_after_commit", "~> 0.4.2" gem "timecop" gem "mocha" end group :sqlite do gem "sqlite3", platforms: [:ruby] end group :postgresql do gem "pg", "~> 0.18.0", platforms: [:ruby] end ruby-acts-as-list-0.9.15/MIT-LICENSE000066400000000000000000000020541333753042500165330ustar00rootroot00000000000000Copyright (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. ruby-acts-as-list-0.9.15/README.md000066400000000000000000000213751333753042500163650ustar00rootroot00000000000000# ActsAsList ## Build Status [![Build Status](https://secure.travis-ci.org/swanandp/acts_as_list.png)](https://secure.travis-ci.org/swanandp/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/swanandp/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/swanandp/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 ``` ## 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. ## 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 ``` ## Versions Version `0.9.0` adds `acts_as_list_no_update` (https://github.com/swanandp/acts_as_list/pull/244) and compatibility with not-null and uniqueness constraints on the database (https://github.com/swanandp/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. ## Workflow Status [![WIP Issues](https://badge.waffle.io/swanandp/acts_as_list.png)](http://waffle.io/swanandp/acts_as_list) ## 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 ruby-acts-as-list-0.9.15/Rakefile000066400000000000000000000022561333753042500165500ustar00rootroot00000000000000require "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 = 'swanandp' end ruby-acts-as-list-0.9.15/acts_as_list.gemspec000066400000000000000000000027011333753042500211130ustar00rootroot00000000000000# -*- 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 = ["David Heinemeier Hansson", "Swanand Pagnis", "Quinn Chaffee"] s.email = ["swanand.pagnis@gmail.com"] s.homepage = "http://github.com/swanandp/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.rubyforge_project = "acts_as_list" s.required_ruby_version = ">= 1.9.2" # 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", ">= 3.0" s.add_development_dependency "bundler", ">= 1.0.0" end ruby-acts-as-list-0.9.15/gemfiles/000077500000000000000000000000001333753042500166715ustar00rootroot00000000000000ruby-acts-as-list-0.9.15/gemfiles/rails_3_2.gemfile000066400000000000000000000011441333753042500220000ustar00rootroot00000000000000# This file was generated by Appraisal source "http://rubygems.org" gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21] gem "rake" gem "appraisal" gem "github_changelog_generator", "1.9.0" gem "activerecord", "~> 3.2.22.2" group :test do gem "minitest", "~> 5.0" gem "test_after_commit", "~> 0.4.2" gem "timecop" gem "mocha" gem "after_commit_exception_notification" end group :sqlite do gem "sqlite3", platforms: [:ruby] end group :postgresql do gem "pg", "~> 0.18.0", platforms: [:ruby] end group :mysql do gem "mysql2", "~> 0.3.21", platforms: [:ruby] end gemspec path: "../" ruby-acts-as-list-0.9.15/gemfiles/rails_4_1.gemfile000066400000000000000000000011421333753042500217760ustar00rootroot00000000000000# This file was generated by Appraisal source "http://rubygems.org" gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21] gem "rake" gem "appraisal" gem "github_changelog_generator", "1.9.0" gem "activerecord", "~> 4.1.16" group :test do gem "minitest", "~> 5.0" gem "test_after_commit", "~> 0.4.2" gem "timecop" gem "mocha" gem "after_commit_exception_notification" end group :sqlite do gem "sqlite3", platforms: [:ruby] end group :postgresql do gem "pg", "~> 0.18.0", platforms: [:ruby] end group :mysql do gem "mysql2", "~> 0.3.21", platforms: [:ruby] end gemspec path: "../" ruby-acts-as-list-0.9.15/gemfiles/rails_4_2.gemfile000066400000000000000000000010661333753042500220040ustar00rootroot00000000000000# This file was generated by Appraisal source "http://rubygems.org" gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21] gem "rake" gem "appraisal" gem "github_changelog_generator", "1.9.0" gem "activerecord", "~> 4.2.10" group :test do gem "minitest", "~> 5.0" gem "test_after_commit", "~> 0.4.2" gem "timecop" gem "mocha" end group :sqlite do gem "sqlite3", platforms: [:ruby] end group :postgresql do gem "pg", "~> 0.18.0", platforms: [:ruby] end group :mysql do gem "mysql2", "~> 0.4.10", platforms: [:ruby] end gemspec path: "../" ruby-acts-as-list-0.9.15/gemfiles/rails_5_0.gemfile000066400000000000000000000010651333753042500220020ustar00rootroot00000000000000# This file was generated by Appraisal source "http://rubygems.org" gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21] gem "rake" gem "appraisal" gem "github_changelog_generator", "1.9.0" gem "activerecord", "~> 5.0.6" group :test do gem "minitest", "~> 5.0" gem "test_after_commit", "~> 0.4.2" gem "timecop" gem "mocha" end group :sqlite do gem "sqlite3", platforms: [:ruby] end group :postgresql do gem "pg", "~> 0.18.0", platforms: [:ruby] end group :mysql do gem "mysql2", "~> 0.4.10", platforms: [:ruby] end gemspec path: "../" ruby-acts-as-list-0.9.15/gemfiles/rails_5_1.gemfile000066400000000000000000000010651333753042500220030ustar00rootroot00000000000000# This file was generated by Appraisal source "http://rubygems.org" gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21] gem "rake" gem "appraisal" gem "github_changelog_generator", "1.9.0" gem "activerecord", "~> 5.1.4" group :test do gem "minitest", "~> 5.0" gem "test_after_commit", "~> 0.4.2" gem "timecop" gem "mocha" end group :sqlite do gem "sqlite3", platforms: [:ruby] end group :postgresql do gem "pg", "~> 0.18.0", platforms: [:ruby] end group :mysql do gem "mysql2", "~> 0.4.10", platforms: [:ruby] end gemspec path: "../" ruby-acts-as-list-0.9.15/gemfiles/rails_5_2.gemfile000066400000000000000000000010711333753042500220010ustar00rootroot00000000000000# This file was generated by Appraisal source "http://rubygems.org" gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21] gem "rake" gem "appraisal" gem "github_changelog_generator", "1.9.0" gem "activerecord", "~> 5.2.0.rc1" group :test do gem "minitest", "~> 5.0" gem "test_after_commit", "~> 0.4.2" gem "timecop" gem "mocha" end group :sqlite do gem "sqlite3", platforms: [:ruby] end group :postgresql do gem "pg", "~> 0.18.0", platforms: [:ruby] end group :mysql do gem "mysql2", "~> 0.4.10", platforms: [:ruby] end gemspec path: "../" ruby-acts-as-list-0.9.15/init.rb000066400000000000000000000001411333753042500163620ustar00rootroot00000000000000# frozen_string_literal: true $:.unshift "#{File.dirname(__FILE__)}/lib" require "acts_as_list" ruby-acts-as-list-0.9.15/lib/000077500000000000000000000000001333753042500156445ustar00rootroot00000000000000ruby-acts-as-list-0.9.15/lib/acts_as_list.rb000066400000000000000000000012171333753042500206420ustar00rootroot00000000000000# 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" ruby-acts-as-list-0.9.15/lib/acts_as_list/000077500000000000000000000000001333753042500203145ustar00rootroot00000000000000ruby-acts-as-list-0.9.15/lib/acts_as_list/active_record/000077500000000000000000000000001333753042500231255ustar00rootroot00000000000000ruby-acts-as-list-0.9.15/lib/acts_as_list/active_record/acts/000077500000000000000000000000001333753042500240575ustar00rootroot00000000000000ruby-acts-as-list-0.9.15/lib/acts_as_list/active_record/acts/active_record.rb000066400000000000000000000001731333753042500272160ustar00rootroot00000000000000# frozen_string_literal: true ActiveSupport.on_load :active_record do extend ActiveRecord::Acts::List::ClassMethods end ruby-acts-as-list-0.9.15/lib/acts_as_list/active_record/acts/add_new_at_method_definer.rb000066400000000000000000000003721333753042500315270ustar00rootroot00000000000000# 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 ruby-acts-as-list-0.9.15/lib/acts_as_list/active_record/acts/aux_method_definer.rb000066400000000000000000000003631333753042500302370ustar00rootroot00000000000000# 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 ruby-acts-as-list-0.9.15/lib/acts_as_list/active_record/acts/callback_definer.rb000066400000000000000000000014441333753042500276370ustar00rootroot00000000000000# 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 ruby-acts-as-list-0.9.15/lib/acts_as_list/active_record/acts/list.rb000066400000000000000000000461031333753042500253630ustar00rootroot00000000000000# 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. def acts_as_list(options = {}) configuration = { column: "position", scope: "1 = 1", top_of_list: 1, add_new_at: :bottom } configuration.update(options) if options.is_a?(Hash) caller_class = self ActiveRecord::Acts::List::PositionColumnMethodDefiner.call(caller_class, configuration[:column]) 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 # 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.send(position_column) != self.send(position_column) swap_positions(lower_item, self) 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.send(position_column) != self.send(position_column) swap_positions(higher_item, self) 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? acts_as_list_class.transaction do decrement_positions_on_lower_items assume_bottom_position end 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? acts_as_list_class.transaction do increment_positions_on_higher_items assume_top_position end 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(self.send(position_column).to_i + 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(self.send(position_column).to_i - 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 position_value = send(position_column) acts_as_list_list. where("#{quoted_position_column_with_table_name} <= ?", position_value). 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 position_value = send(position_column) acts_as_list_list. where("#{quoted_position_column_with_table_name} >= ?", position_value). 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? send(position_column).nil? end def default_position acts_as_list_class.columns_hash[position_column.to_s].default end def default_position? default_position && default_position.to_i == send(position_column) end # Sets the new position and saves it def set_list_position(new_position, raise_exception_if_save_fails=false) write_attribute position_column, new_position raise_exception_if_save_fails ? save! : save end private def swap_positions(item1, item2) item1_position = item1.send(position_column) item1.set_list_position(item2.send(position_column)) item2.set_list_position(item1_position) end def acts_as_list_list if ActiveRecord::VERSION::MAJOR < 4 acts_as_list_class.unscoped do acts_as_list_class.where(scope_condition) end else acts_as_list_class.unscope(:where).where(scope_condition) end 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.send(position_column) : 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} < ?", send(position_column).to_i).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 scope.where("#{quoted_position_column_with_table_name} >= ?", position).increment_all 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=nil) return unless in_list? position ||= send(position_column).to_i if sequential_updates? acts_as_list_list.where("#{quoted_position_column_with_table_name} > ?", position).reorder(acts_as_list_order_argument(:asc)).each do |item| item.decrement!(position_column) end 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)).each do |item| item.decrement!(position_column) end 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)).each do |item| item.increment!(position_column) end else items.increment_all end end end def insert_at_position(position, raise_exception_if_save_fails=false) return set_list_position(position, raise_exception_if_save_fails) if new_record? with_lock do if in_list? old_position = send(position_column).to_i 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 new_position = send(position_column).to_i return unless acts_as_list_list.where( "#{quoted_position_column_with_table_name} = #{new_position}" ).count > 1 shuffle_positions_on_intermediate_items old_position, new_position, id end def position_before_save_changed? if ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR >= 1 || ActiveRecord::VERSION::MAJOR > 5 saved_change_to_attribute? position_column else send "#{position_column}_changed?" end end def position_before_save if ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR >= 1 || ActiveRecord::VERSION::MAJOR > 5 attribute_before_last_save position_column else send "#{position_column}_was" 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 send(position_column) && !default_position? && send(position_column) < 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) if ActiveRecord::VERSION::MAJOR >= 4 { position_column => direction } else "#{quoted_position_column_with_table_name} #{direction.to_s.upcase}" end end end end end end ruby-acts-as-list-0.9.15/lib/acts_as_list/active_record/acts/no_update.rb000066400000000000000000000065661333753042500263770ustar00rootroot00000000000000# 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) extracted_klasses.push(*klasses) yield ensure extracted_klasses.clear end def applied_to?(klass) !(klass.ancestors & extracted_klasses).empty? end private def extracted_klasses Thread.current[:act_as_list_no_update] ||= [] end end def act_as_list_no_update? NoUpdate.applied_to?(self.class) end end end end end ruby-acts-as-list-0.9.15/lib/acts_as_list/active_record/acts/position_column_method_definer.rb000066400000000000000000000051761333753042500326720ustar00rootroot00000000000000# frozen_string_literal: true module ActiveRecord::Acts::List::PositionColumnMethodDefiner #:nodoc: def self.call(caller_class, position_column) define_class_methods(caller_class, position_column) 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) 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_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| update_all(updates + touch_record_sql) 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| write_attribute(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 ruby-acts-as-list-0.9.15/lib/acts_as_list/active_record/acts/scope_method_definer.rb000066400000000000000000000042761333753042500305620ustar00rootroot00000000000000# frozen_string_literal: true module ActiveRecord::Acts::List::ScopeMethodDefiner #:nodoc: extend ActiveSupport::Inflector def self.call(caller_class, scope) scope = idify(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 return false if ActiveRecord::VERSION::MAJOR < 4 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 return false if ActiveRecord::VERSION::MAJOR < 4 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(name) return name if name.to_s =~ /_id$/ foreign_key(name).to_sym end end ruby-acts-as-list-0.9.15/lib/acts_as_list/active_record/acts/sequential_updates_method_definer.rb000066400000000000000000000017401333753042500333410ustar00rootroot00000000000000# 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 ActiveRecord::VERSION::MAJOR >= 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 ruby-acts-as-list-0.9.15/lib/acts_as_list/active_record/acts/top_of_list_method_definer.rb000066400000000000000000000005371333753042500317660ustar00rootroot00000000000000# 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 ruby-acts-as-list-0.9.15/lib/acts_as_list/version.rb000066400000000000000000000001741333753042500223300ustar00rootroot00000000000000# frozen_string_literal: true module ActiveRecord module Acts module List VERSION = '0.9.15' end end end ruby-acts-as-list-0.9.15/test/000077500000000000000000000000001333753042500160555ustar00rootroot00000000000000ruby-acts-as-list-0.9.15/test/database.yml000066400000000000000000000004211333753042500203410ustar00rootroot00000000000000sqlite: 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 ruby-acts-as-list-0.9.15/test/helper.rb000066400000000000000000000027371333753042500176720ustar00rootroot00000000000000# 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 # Returns true if ActiveRecord is rails 3, 4 version def rails_3 defined?(ActiveRecord::VERSION) && ActiveRecord::VERSION::MAJOR >= 3 end def rails_4 defined?(ActiveRecord::VERSION) && ActiveRecord::VERSION::MAJOR >= 4 end 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 ruby-acts-as-list-0.9.15/test/shared.rb000066400000000000000000000006321333753042500176510ustar00rootroot00000000000000# 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 ruby-acts-as-list-0.9.15/test/shared_array_scope_list.rb000066400000000000000000000164711333753042500233030ustar00rootroot00000000000000# 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 ruby-acts-as-list-0.9.15/test/shared_list.rb000066400000000000000000000243671333753042500207170ustar00rootroot00000000000000# 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_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. # NOTE: Hotfix for rails3 ActiveRecord list = ListMixin.where(id: 2).first if list.respond_to?(:run_callbacks) # Refactored to work according to Rails3 ActiveRSupport Callbacks list.run_callbacks(:destroy) if rails_3 list.run_callbacks(:before_destroy) if !rails_3 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 end end ruby-acts-as-list-0.9.15/test/shared_list_sub.rb000066400000000000000000000145731333753042500215660ustar00rootroot00000000000000# 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_attributes(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 ruby-acts-as-list-0.9.15/test/shared_no_addition.rb000066400000000000000000000015441333753042500222230ustar00rootroot00000000000000# 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 ruby-acts-as-list-0.9.15/test/shared_quoting.rb000066400000000000000000000006771333753042500214300ustar00rootroot00000000000000# 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 ruby-acts-as-list-0.9.15/test/shared_top_addition.rb000066400000000000000000000071561333753042500224160ustar00rootroot00000000000000# 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 ruby-acts-as-list-0.9.15/test/shared_zero_based.rb000066400000000000000000000056561333753042500220610ustar00rootroot00000000000000# 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 ruby-acts-as-list-0.9.15/test/test_joined_list.rb000066400000000000000000000031011333753042500217370ustar00rootroot00000000000000# 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 ruby-acts-as-list-0.9.15/test/test_list.rb000066400000000000000000000650451333753042500204260ustar00rootroot00000000000000# 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 mixins = [ Mixin, ListMixin, ListMixinSub1, ListMixinSub2, ListWithStringScopeMixin, ArrayScopeListMixin, ZeroBasedMixin, DefaultScopedMixin, DefaultScopedWhereMixin, TopAdditionMixin, NoAdditionMixin, QuotedList ] mixins << EnumArrayScopeListMixin if rails_4 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 ListMixinSub1 < ListMixin end class ListMixinSub2 < ListMixin if rails_3 validates :pos, presence: true else validates_presence_of :pos end end class ListMixinError < ListMixin if rails_3 validates :state, presence: true else validates_presence_of :state end 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 if rails_4 class EnumArrayScopeListMixin < Mixin STATE_VALUES = %w(active archived) enum state: STATE_VALUES acts_as_list column: "pos", scope: [:parent_id, :state] end 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 if ActiveRecord::VERSION::MAJOR < 4 unscoped do order('pos ASC').where(active: false) end else unscope(:where).where(active: false) end end end class SequentialUpdatesDefault < Mixin acts_as_list column: "pos" 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_attributes 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_attributes(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_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_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_attributes(: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_attributes(: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 end if rails_4 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 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_attributes(: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_attributes(: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_attributes(: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_attributes(: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_attributes(: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_attributes(: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 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_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 end ruby-acts-as-list-0.9.15/test/test_no_update_for_extra_classes.rb000066400000000000000000000057261333753042500252170ustar00rootroot00000000000000# 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) } end def test_update @item_1.update_attributes(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_attributes(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_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_attributes(position: 2) @attachment_1.update_attributes(position: 2) @list_1.update_attributes(position: 2) end end ruby-acts-as-list-0.9.15/test/test_no_update_for_scope_destruction.rb000066400000000000000000000045551333753042500261120ustar00rootroot00000000000000# 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 if ActiveRecord::VERSION::MAJOR < 4 DestructionTodoItem.any_instance.expects(:decrement_positions_on_lower_items).once DestructionTadaItem.any_instance.expects(:decrement_positions_on_lower_items).once else DestructionTodoItem.any_instance.expects(:decrement_positions_on_lower_items).never DestructionTadaItem.any_instance.expects(:decrement_positions_on_lower_items).never end 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 ruby-acts-as-list-0.9.15/test/test_no_update_for_subclasses.rb000066400000000000000000000025421333753042500245170ustar00rootroot00000000000000# frozen_string_literal: true require 'helper' class TodoItem < ActiveRecord::Base acts_as_list end class SubTodoItem < TodoItem; end class NoUpdateForSubclassesTestCase < Minitest::Test def setup ActiveRecord::Base.connection.create_table :todo_items do |t| t.column :position, :integer t.column :type, :string end ActiveRecord::Base.connection.schema_cache.clear! [TodoItem, SubTodoItem].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| SubTodoItem.create!(position: counter) end end def test_update @item_1.update_attributes(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 TodoItem.acts_as_list_no_update { @item_1.update_attributes(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 SubTodoItem.acts_as_list_no_update { @item_1.update_attributes(position: 2) } assert_equal 2, @item_1.reload.position assert_equal 2, @item_2.reload.position end end