activerecord-sortable 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. checksums.yaml +7 -0
  2. data/README.rdoc +3 -0
  3. data/Rakefile +33 -0
  4. data/lib/activerecord/sortable.rb +8 -0
  5. data/lib/activerecord/sortable/acts_as_sortable.rb +40 -0
  6. data/lib/activerecord/sortable/acts_as_sortable/instance_methods.rb +66 -0
  7. data/lib/activerecord/sortable/rails.rb +8 -0
  8. data/lib/activerecord/sortable/version.rb +5 -0
  9. data/lib/assets/javascripts/sortable.js +99 -0
  10. data/lib/tasks/sortable_tasks.rake +4 -0
  11. data/spec/dummy/README.rdoc +28 -0
  12. data/spec/dummy/Rakefile +6 -0
  13. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  14. data/spec/dummy/app/assets/javascripts/jquery.simulate.js +312 -0
  15. data/spec/dummy/app/assets/stylesheets/application.css +14 -0
  16. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  17. data/spec/dummy/app/controllers/things_controller.rb +10 -0
  18. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  19. data/spec/dummy/app/models/item.rb +3 -0
  20. data/spec/dummy/app/models/other_thing.rb +5 -0
  21. data/spec/dummy/app/models/thing.rb +3 -0
  22. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  23. data/spec/dummy/app/views/things/_thing.html.erb +3 -0
  24. data/spec/dummy/app/views/things/index.html.erb +7 -0
  25. data/spec/dummy/app/views/things/move.js.erb +4 -0
  26. data/spec/dummy/bin/bundle +3 -0
  27. data/spec/dummy/bin/rails +4 -0
  28. data/spec/dummy/bin/rake +4 -0
  29. data/spec/dummy/config.ru +4 -0
  30. data/spec/dummy/config/application.rb +24 -0
  31. data/spec/dummy/config/boot.rb +5 -0
  32. data/spec/dummy/config/database.yml +25 -0
  33. data/spec/dummy/config/environment.rb +5 -0
  34. data/spec/dummy/config/environments/development.rb +29 -0
  35. data/spec/dummy/config/environments/production.rb +80 -0
  36. data/spec/dummy/config/environments/test.rb +36 -0
  37. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  38. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  39. data/spec/dummy/config/initializers/inflections.rb +16 -0
  40. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  41. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  42. data/spec/dummy/config/initializers/session_store.rb +3 -0
  43. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  44. data/spec/dummy/config/locales/en.yml +23 -0
  45. data/spec/dummy/config/routes.rb +9 -0
  46. data/spec/dummy/db/development.sqlite3 +0 -0
  47. data/spec/dummy/db/migrate/20140512100816_create_things.rb +10 -0
  48. data/spec/dummy/db/migrate/20140525104834_create_other_things.rb +10 -0
  49. data/spec/dummy/db/migrate/20140525111906_create_items.rb +7 -0
  50. data/spec/dummy/db/migrate/20140525112123_add_position_to_items.rb +13 -0
  51. data/spec/dummy/db/schema.rb +40 -0
  52. data/spec/dummy/db/seeds.rb +3 -0
  53. data/spec/dummy/db/test.sqlite3 +0 -0
  54. data/spec/dummy/log/development.log +5000 -0
  55. data/spec/dummy/log/test.log +125455 -0
  56. data/spec/dummy/public/404.html +58 -0
  57. data/spec/dummy/public/422.html +58 -0
  58. data/spec/dummy/public/500.html +57 -0
  59. data/spec/dummy/public/favicon.ico +0 -0
  60. data/spec/dummy/tmp/cache/assets/development/sprockets/02e6da2de0eeda095c3ab81e1033fc1d +0 -0
  61. data/spec/dummy/tmp/cache/assets/development/sprockets/04cf541b9d1ee4cad14421becb051689 +0 -0
  62. data/spec/dummy/tmp/cache/assets/development/sprockets/06a48cadb2e97ab6c9c35285891cd326 +0 -0
  63. data/spec/dummy/tmp/cache/assets/development/sprockets/0bb2483991c2c5f7f83fde21edafb6a9 +0 -0
  64. data/spec/dummy/tmp/cache/assets/development/sprockets/0d3c3b294494fb664545d3c26ee18f14 +0 -0
  65. data/spec/dummy/tmp/cache/assets/development/sprockets/0de48e81714433e3a37eac8d8be3d97e +0 -0
  66. data/spec/dummy/tmp/cache/assets/development/sprockets/10ae91021bb1bbd0930672c779b3b91a +0 -0
  67. data/spec/dummy/tmp/cache/assets/development/sprockets/11820a6ecc13dd7f7094529f02abab32 +0 -0
  68. data/spec/dummy/tmp/cache/assets/development/sprockets/11854112d39135b018adc8440defeda0 +0 -0
  69. data/spec/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  70. data/spec/dummy/tmp/cache/assets/development/sprockets/1510c8e329d6e9494521d212f6436149 +0 -0
  71. data/spec/dummy/tmp/cache/assets/development/sprockets/1f6d40a549a87bc8b9dc59afe0b41143 +0 -0
  72. data/spec/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  73. data/spec/dummy/tmp/cache/assets/development/sprockets/30e32f924769f4b2ef4ec2ed78d62f3d +0 -0
  74. data/spec/dummy/tmp/cache/assets/development/sprockets/3102e67dc53e4d0f35c4581e16261889 +0 -0
  75. data/spec/dummy/tmp/cache/assets/development/sprockets/327b885c1a9d58094da4fda60431a466 +0 -0
  76. data/spec/dummy/tmp/cache/assets/development/sprockets/327f7f624a6fc56d3d434e46aceb3299 +0 -0
  77. data/spec/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  78. data/spec/dummy/tmp/cache/assets/development/sprockets/3754f0eb2c14b3faa11aec78b2dcee56 +0 -0
  79. data/spec/dummy/tmp/cache/assets/development/sprockets/3a8816f3d1d27361f67808fed1cfaf48 +0 -0
  80. data/spec/dummy/tmp/cache/assets/development/sprockets/3c6d7669755b971545e296fc93a9595b +0 -0
  81. data/spec/dummy/tmp/cache/assets/development/sprockets/3e904cfb843c2a95c02c07b4fbc96e6e +0 -0
  82. data/spec/dummy/tmp/cache/assets/development/sprockets/402789e80a4ebf130058eeae9bacd1e3 +0 -0
  83. data/spec/dummy/tmp/cache/assets/development/sprockets/405ef6e6ee991e0b2d850f3443245dc8 +0 -0
  84. data/spec/dummy/tmp/cache/assets/development/sprockets/4249e053e25b3a5632832c437bfd0e4d +0 -0
  85. data/spec/dummy/tmp/cache/assets/development/sprockets/42b8d23a55d28e131ae662ed762cbf08 +0 -0
  86. data/spec/dummy/tmp/cache/assets/development/sprockets/4646b96b524c218d2b80992e633080ff +0 -0
  87. data/spec/dummy/tmp/cache/assets/development/sprockets/47e71eb31da5be13ced7c7a889342d58 +0 -0
  88. data/spec/dummy/tmp/cache/assets/development/sprockets/485c8ac4edde07df3f326e4f4b9f9106 +0 -0
  89. data/spec/dummy/tmp/cache/assets/development/sprockets/49bf73b3490c4a8e62534bd0d5be81c3 +0 -0
  90. data/spec/dummy/tmp/cache/assets/development/sprockets/4c1e2286914bc9b9ad1073e0aaa47a40 +0 -0
  91. data/spec/dummy/tmp/cache/assets/development/sprockets/4c803caf54aa7538b898209acc1b1070 +0 -0
  92. data/spec/dummy/tmp/cache/assets/development/sprockets/562dbca143b36a28d7fc6820500de6cc +0 -0
  93. data/spec/dummy/tmp/cache/assets/development/sprockets/57c480a56fb25b20eb672e4027117550 +0 -0
  94. data/spec/dummy/tmp/cache/assets/development/sprockets/58b84f82ca3fe739a2c21accf3b43e08 +0 -0
  95. data/spec/dummy/tmp/cache/assets/development/sprockets/5b848793a8918c09f06e30db36aeb816 +0 -0
  96. data/spec/dummy/tmp/cache/assets/development/sprockets/5dc29296d9d4d74645d70a5225f6027f +0 -0
  97. data/spec/dummy/tmp/cache/assets/development/sprockets/61832b49e09217de656ff5bd787626c9 +0 -0
  98. data/spec/dummy/tmp/cache/assets/development/sprockets/6386af6a5c718c1ddd97f609e0f7e9bd +0 -0
  99. data/spec/dummy/tmp/cache/assets/development/sprockets/63c434ac472c04632133d9bc2f8ebe44 +0 -0
  100. data/spec/dummy/tmp/cache/assets/development/sprockets/64ef40135516dbbf1737a2d598b22cfa +0 -0
  101. data/spec/dummy/tmp/cache/assets/development/sprockets/67ad3a3d9a5fa9dd07456e131ce9c2a8 +0 -0
  102. data/spec/dummy/tmp/cache/assets/development/sprockets/6c2e45e1a31edecd0e3584aa2d836c58 +0 -0
  103. data/spec/dummy/tmp/cache/assets/development/sprockets/743ab64e3ac90ba4734a5a1af916fd93 +0 -0
  104. data/spec/dummy/tmp/cache/assets/development/sprockets/77088c2f43bec60ff0e030676e96f686 +0 -0
  105. data/spec/dummy/tmp/cache/assets/development/sprockets/777746e36b5f546628033e633d960005 +0 -0
  106. data/spec/dummy/tmp/cache/assets/development/sprockets/7ed5d11be8a68c295f8833e4961891ff +0 -0
  107. data/spec/dummy/tmp/cache/assets/development/sprockets/80d43205b7942ece8d8b09f859813908 +0 -0
  108. data/spec/dummy/tmp/cache/assets/development/sprockets/867c6ad0b26a9665bc431c93887cea11 +0 -0
  109. data/spec/dummy/tmp/cache/assets/development/sprockets/87f7d23ea55311ee3affe8e6ad8f1b57 +0 -0
  110. data/spec/dummy/tmp/cache/assets/development/sprockets/8a23854402292f417575b8ecaa5ced06 +0 -0
  111. data/spec/dummy/tmp/cache/assets/development/sprockets/8b39a75a80c1fb3a07395e674644e2cc +0 -0
  112. data/spec/dummy/tmp/cache/assets/development/sprockets/8fd0bc56f8581ee9a4376dced1efc158 +0 -0
  113. data/spec/dummy/tmp/cache/assets/development/sprockets/a12ecc7f4b8b7b1315023a6f62aa4ab5 +0 -0
  114. data/spec/dummy/tmp/cache/assets/development/sprockets/a2038db6aaa5289fba7fde9aea561a08 +0 -0
  115. data/spec/dummy/tmp/cache/assets/development/sprockets/ad0693eaa72c0eee52a255fb9d030503 +0 -0
  116. data/spec/dummy/tmp/cache/assets/development/sprockets/b0a786e48e0e908ab34cfbb918853146 +0 -0
  117. data/spec/dummy/tmp/cache/assets/development/sprockets/b84a4d17f27ef190e1eed6129eaac20b +0 -0
  118. data/spec/dummy/tmp/cache/assets/development/sprockets/b9296322253567156d260263305d22d2 +0 -0
  119. data/spec/dummy/tmp/cache/assets/development/sprockets/b970eb3cff83ab8da0076e1472860d75 +0 -0
  120. data/spec/dummy/tmp/cache/assets/development/sprockets/bbceab6aa0252d0552d3a37d2d11e98d +0 -0
  121. data/spec/dummy/tmp/cache/assets/development/sprockets/bfca8c57a5fdc4f3f2e41cea232ed987 +0 -0
  122. data/spec/dummy/tmp/cache/assets/development/sprockets/bfce4ec4b73dd7cbfa1a9dcc601c5050 +0 -0
  123. data/spec/dummy/tmp/cache/assets/development/sprockets/bfd7da030142aceda83eb82f60b33487 +0 -0
  124. data/spec/dummy/tmp/cache/assets/development/sprockets/c7e351cd01a8592503c63b37d7d69e4f +0 -0
  125. data/spec/dummy/tmp/cache/assets/development/sprockets/caf21088467d229770a205c793606696 +0 -0
  126. data/spec/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  127. data/spec/dummy/tmp/cache/assets/development/sprockets/d1256d5e44bddbb67bfb24dbab1c3795 +0 -0
  128. data/spec/dummy/tmp/cache/assets/development/sprockets/d2c66e0a8aa45b5f5b17c98c3d39bf91 +0 -0
  129. data/spec/dummy/tmp/cache/assets/development/sprockets/d3fe7bc39b02f61c0f994820aec91f78 +0 -0
  130. data/spec/dummy/tmp/cache/assets/development/sprockets/d4b2ad85aa1a4f1ea28a39b5518f992b +0 -0
  131. data/spec/dummy/tmp/cache/assets/development/sprockets/d5449ece5f819190ad6dd88c1caccffc +0 -0
  132. data/spec/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  133. data/spec/dummy/tmp/cache/assets/development/sprockets/d8b62b055a9e2933d39777275cd166f0 +0 -0
  134. data/spec/dummy/tmp/cache/assets/development/sprockets/dbbb41e059e78bdfbb7ec9285ce21b97 +0 -0
  135. data/spec/dummy/tmp/cache/assets/development/sprockets/e0f01bdea42cc99228735a8a21d95ef2 +0 -0
  136. data/spec/dummy/tmp/cache/assets/development/sprockets/ebd00f71b7dcba8d80316e0b8d5e76e3 +0 -0
  137. data/spec/dummy/tmp/cache/assets/development/sprockets/ebe17685dedaf8f574dde1463bdea118 +0 -0
  138. data/spec/dummy/tmp/cache/assets/development/sprockets/f188fdc5d08872237a3dd0231bbfe4e1 +0 -0
  139. data/spec/dummy/tmp/cache/assets/development/sprockets/f4cbaca85a4be542050fd39cbf5eb6c3 +0 -0
  140. data/spec/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  141. data/spec/dummy/tmp/cache/assets/development/sprockets/fba7f1b57d7cc3253cb40677cbbd8f54 +0 -0
  142. data/spec/dummy/tmp/cache/assets/development/sprockets/fd13af7854ecfb6bedf12ce71539b636 +0 -0
  143. data/spec/dummy/tmp/cache/assets/test/sprockets/02e6da2de0eeda095c3ab81e1033fc1d +0 -0
  144. data/spec/dummy/tmp/cache/assets/test/sprockets/0bb2483991c2c5f7f83fde21edafb6a9 +0 -0
  145. data/spec/dummy/tmp/cache/assets/test/sprockets/11820a6ecc13dd7f7094529f02abab32 +0 -0
  146. data/spec/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  147. data/spec/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  148. data/spec/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  149. data/spec/dummy/tmp/cache/assets/test/sprockets/405ef6e6ee991e0b2d850f3443245dc8 +0 -0
  150. data/spec/dummy/tmp/cache/assets/test/sprockets/485c8ac4edde07df3f326e4f4b9f9106 +0 -0
  151. data/spec/dummy/tmp/cache/assets/test/sprockets/63c434ac472c04632133d9bc2f8ebe44 +0 -0
  152. data/spec/dummy/tmp/cache/assets/test/sprockets/67ad3a3d9a5fa9dd07456e131ce9c2a8 +0 -0
  153. data/spec/dummy/tmp/cache/assets/test/sprockets/77088c2f43bec60ff0e030676e96f686 +0 -0
  154. data/spec/dummy/tmp/cache/assets/test/sprockets/80d43205b7942ece8d8b09f859813908 +0 -0
  155. data/spec/dummy/tmp/cache/assets/test/sprockets/a2038db6aaa5289fba7fde9aea561a08 +0 -0
  156. data/spec/dummy/tmp/cache/assets/test/sprockets/bbceab6aa0252d0552d3a37d2d11e98d +0 -0
  157. data/spec/dummy/tmp/cache/assets/test/sprockets/c7e351cd01a8592503c63b37d7d69e4f +0 -0
  158. data/spec/dummy/tmp/cache/assets/test/sprockets/caf21088467d229770a205c793606696 +0 -0
  159. data/spec/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  160. data/spec/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  161. data/spec/dummy/tmp/cache/assets/test/sprockets/d8b62b055a9e2933d39777275cd166f0 +0 -0
  162. data/spec/dummy/tmp/cache/assets/test/sprockets/ebe17685dedaf8f574dde1463bdea118 +0 -0
  163. data/spec/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  164. data/spec/dummy/tmp/cache/assets/test/sprockets/fba7f1b57d7cc3253cb40677cbbd8f54 +0 -0
  165. data/spec/dummy/tmp/cache/assets/test/sprockets/fd13af7854ecfb6bedf12ce71539b636 +0 -0
  166. data/spec/features/drag_and_drop_spec.rb +65 -0
  167. data/spec/other_thing_spec.rb +5 -0
  168. data/spec/spec_helper.rb +24 -0
  169. data/spec/support/activerecord_sortable.rb +181 -0
  170. data/spec/thing_spec.rb +5 -0
  171. metadata +458 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d99827ae2d5921a9e848eac321ae02850a4f2a6f
4
+ data.tar.gz: b838570af229fa92372b6459666f4469f67e70ac
5
+ SHA512:
6
+ metadata.gz: 0ac4f1819d8e111a271364995d75cd43d7fe2315cfc89140efd16fda08313ddb0f3a09e31741da779a81830c377c4c342e40422fafdb22ef54f4ce2891ce689d
7
+ data.tar.gz: 03d4aa1bd6843d6d2a9e2fadddefd570d3d74950072a56d8f041c872274eff3cf5c3acdd713298a7fbd340a69bccd7c35c5fcde08519cf8b61c5805b4cddda5d
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = Sortable
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+
8
+ require 'rdoc/task'
9
+
10
+ RDoc::Task.new(:rdoc) do |rdoc|
11
+ rdoc.rdoc_dir = 'rdoc'
12
+ rdoc.title = 'Sortable'
13
+ rdoc.options << '--line-numbers'
14
+ rdoc.rdoc_files.include('README.rdoc')
15
+ rdoc.rdoc_files.include('lib/**/*.rb')
16
+ end
17
+
18
+
19
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
20
+
21
+ load 'rails/tasks/engine.rake'
22
+
23
+ Bundler::GemHelper.install_tasks
24
+
25
+ Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
26
+
27
+ require 'rspec/core'
28
+ require 'rspec/core/rake_task'
29
+
30
+ desc "Run all specs in spec directory (excluding plugin specs)"
31
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
32
+
33
+ task :default => :spec
@@ -0,0 +1,8 @@
1
+ require 'activerecord/sortable/acts_as_sortable'
2
+
3
+ require 'activerecord/sortable/rails' if defined?(::Rails)
4
+
5
+ module ActiveRecord
6
+ module Sortable
7
+ end
8
+ end
@@ -0,0 +1,40 @@
1
+ require 'activerecord/sortable/acts_as_sortable/instance_methods'
2
+
3
+ require 'active_support'
4
+
5
+ module ActiveRecord
6
+ module Sortable
7
+ module ActsAsSortable
8
+ extend ::ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ def acts_as_sortable(&block)
12
+ options = {
13
+ :relation => ->(instance) {instance.class},
14
+ :append => false,
15
+ :position_column => :position
16
+ }
17
+ block.call(options) if block_given?
18
+
19
+ cattr_accessor :sortable_relation, :instance_reader => false, :instance_writer => false
20
+ cattr_accessor :sortable_append, :instance_reader => true, :instance_writer => false
21
+ cattr_accessor :sortable_position_column, :instance_reader => true, :instance_writer => false
22
+
23
+ self.sortable_relation = options[:relation]
24
+ self.sortable_append = options[:append]
25
+ self.sortable_position_column = options[:position_column]
26
+
27
+ scope "ordered_by_#{self.sortable_position_column}_asc".to_sym, -> { order(self.sortable_position_column => :asc) }
28
+
29
+ before_create :sortable_set_default_position
30
+ after_destroy :sortable_shift_on_destroy
31
+
32
+ include ActiveRecord::Sortable::ActsAsSortable::InstanceMethods
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ require 'active_record'
40
+ ActiveRecord::Base.send :include, ActiveRecord::Sortable::ActsAsSortable
@@ -0,0 +1,66 @@
1
+ module ActiveRecord
2
+ module Sortable
3
+ module ActsAsSortable
4
+ module InstanceMethods
5
+
6
+ def move_to!(new_position)
7
+ new_position = Integer(new_position)
8
+
9
+ transaction do
10
+ if new_position > self.send(sortable_position_column)
11
+ sortable_relation
12
+ .where(["#{sortable_position_column} > ? AND #{sortable_position_column} <= ?", self.send(sortable_position_column), new_position])
13
+ .update_all sortable_updates_with_timestamps("#{sortable_position_column} = #{sortable_position_column} - 1")
14
+ else
15
+ sortable_relation
16
+ .where(["#{sortable_position_column} >= ? AND #{sortable_position_column} < ?", new_position, self.send(sortable_position_column)])
17
+ .update_all sortable_updates_with_timestamps("#{sortable_position_column} = #{sortable_position_column} + 1")
18
+ end
19
+
20
+ self.update_attribute(sortable_position_column, new_position)
21
+ end
22
+ end
23
+
24
+
25
+ private
26
+
27
+ def sortable_relation
28
+ self.class.sortable_relation.call(self)
29
+ end
30
+
31
+ def sortable_set_default_position
32
+ sortable_assign_default_position unless self.send(sortable_position_column).present?
33
+
34
+ true
35
+ end
36
+
37
+ def sortable_assign_default_position
38
+ if sortable_append
39
+ next_position = (max_position = sortable_relation.pluck(self.sortable_position_column).max).present? ? max_position + 1 : 0
40
+ self.send("#{sortable_position_column}=".to_sym, next_position)
41
+ else
42
+ sortable_relation.update_all sortable_updates_with_timestamps("#{sortable_position_column} = #{sortable_position_column} + 1")
43
+ self.send("#{sortable_position_column}=".to_sym, 0)
44
+ end
45
+ end
46
+
47
+ def sortable_updates_with_timestamps(base_query)
48
+ current_time = current_time_from_proper_timezone
49
+
50
+ query_items = [base_query]
51
+
52
+ update_values = timestamp_attributes_for_update_in_model.map do |attr|
53
+ query_items << "#{attr} = ?"
54
+ current_time
55
+ end
56
+ updates = [query_items.join(', ')] + update_values
57
+ end
58
+
59
+ def sortable_shift_on_destroy
60
+ sortable_relation.where(["#{sortable_position_column} > ?", self.send(sortable_position_column)]).update_all sortable_updates_with_timestamps("#{sortable_position_column} = #{sortable_position_column} - 1")
61
+ true
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,8 @@
1
+ module ActiveRecord
2
+ module Sortable
3
+ module Rails
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ module Sortable
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,99 @@
1
+ (function(window, $){
2
+ if(!$){throw new Error('jQuery required by Sortable')};
3
+
4
+
5
+ function PositionsUpdater(node, current_node, new_position, old_position) {
6
+ this.node = node;
7
+ this.current_node = current_node;
8
+ this.new_position = new_position;
9
+ this.old_position = old_position;
10
+ };
11
+
12
+ PositionsUpdater.prototype.update = function() {
13
+ var step = this.new_position > this.old_position ? -1 : 1;
14
+ var start_position = step === -1 ? this.old_position + 1 : this.new_position;
15
+ var stop_position = step === -1 ? this.new_position : this.old_position - 1;
16
+ var target, data_position;
17
+ var current_node_role = this.current_node.data('role');
18
+
19
+ $('*[data-position]', this.node)
20
+ .filter(function(){
21
+ var position = $(this).data('position');
22
+ var role = $(this).data('role');
23
+
24
+ return (current_node_role !== role) && position >= start_position && position <= stop_position;
25
+ })
26
+ .each(function(){
27
+ var target = $(this);
28
+
29
+ var data_position = target.data('position');
30
+ var new_position = data_position + step;
31
+
32
+ target.attr('data-position', new_position).data('position', new_position);
33
+ });
34
+ };
35
+
36
+
37
+ function ServerSorter(node, current_node, new_position, positions_updater) {
38
+ this.node = node;
39
+ this.current_node = current_node;
40
+ this.new_position = new_position;
41
+ this.positions_updater = positions_updater;
42
+ };
43
+
44
+ ServerSorter.prototype.sort = function() {
45
+ $.ajax({
46
+ type: 'POST',
47
+ dataType: 'script',
48
+ url: this.current_node.data('move-url'),
49
+ data: {
50
+ position: this.new_position
51
+ },
52
+ success: $.proxy(this.sort_by_server_success, this),
53
+ error: $.proxy(this.sort_by_server_error, this)
54
+ });
55
+ };
56
+
57
+ ServerSorter.prototype.sort_by_server_success = function() {
58
+ this.positions_updater.update();
59
+ this.node.trigger('sortable:sort_success').trigger('sortable:stop').sortable('enable');
60
+ };
61
+
62
+ ServerSorter.prototype.sort_by_server_error = function() {
63
+ this.node.sortable('cancel').trigger('sortable:sort_error').trigger('sortable:stop').sortable('enable');
64
+ };
65
+
66
+
67
+ function Sortable(node) {
68
+ this.node = node;
69
+
70
+ this.node.sortable({
71
+ axis: 'y',
72
+ update: $.proxy(this.sortable_stop_handler, this)
73
+ });
74
+ };
75
+
76
+ Sortable.prototype.sortable_stop_handler = function(e, ui) {
77
+ this.node.sortable('disable').trigger('sortable:start');
78
+
79
+ var node = ui.item;
80
+ var new_position = node.index();
81
+ var old_position = parseInt(node.data('position'));
82
+
83
+ var positions_updater = new PositionsUpdater(this.node, node, new_position, old_position);
84
+
85
+ server_sorter = new ServerSorter(this.node, node, new_position, positions_updater);
86
+ server_sorter.sort();
87
+ };
88
+
89
+
90
+ $.fn.activerecord_sortable = function() {
91
+ var target = $(this);
92
+ target.each(function(){
93
+ var self = $(this);
94
+ var sortable_instance = new Sortable(self);
95
+
96
+ self.data('sortable-instance', sortable_instance);
97
+ });
98
+ }
99
+ })(window, window.jQuery);
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :sortable do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Dummy::Application.load_tasks
@@ -0,0 +1,9 @@
1
+ //= require jquery
2
+ //= require jquery_ujs
3
+ //= require sortable
4
+
5
+ //= require jquery.ui.sortable
6
+
7
+ $(document).ready(function(){
8
+ $('*[data-role=activerecord_sortable]').activerecord_sortable();
9
+ });
@@ -0,0 +1,312 @@
1
+ /*!
2
+ * jQuery Simulate v@VERSION - simulate browser mouse and keyboard events
3
+ * https://github.com/jquery/jquery-simulate
4
+ *
5
+ * Copyright 2012 jQuery Foundation and other contributors
6
+ * Released under the MIT license.
7
+ * http://jquery.org/license
8
+ *
9
+ * Date: @DATE
10
+ */
11
+
12
+ ;(function( $, undefined ) {
13
+
14
+ var rkeyEvent = /^key/,
15
+ rmouseEvent = /^(?:mouse|contextmenu)|click/;
16
+
17
+ $.fn.simulate = function( type, options ) {
18
+ return this.each(function() {
19
+ new $.simulate( this, type, options );
20
+ });
21
+ };
22
+
23
+ $.simulate = function( elem, type, options ) {
24
+ var method = $.camelCase( "simulate-" + type );
25
+
26
+ this.target = elem;
27
+ this.options = options;
28
+
29
+ if ( this[ method ] ) {
30
+ this[ method ]();
31
+ } else {
32
+ this.simulateEvent( elem, type, options );
33
+ }
34
+ };
35
+
36
+ $.extend( $.simulate, {
37
+
38
+ keyCode: {
39
+ BACKSPACE: 8,
40
+ COMMA: 188,
41
+ DELETE: 46,
42
+ DOWN: 40,
43
+ END: 35,
44
+ ENTER: 13,
45
+ ESCAPE: 27,
46
+ HOME: 36,
47
+ LEFT: 37,
48
+ NUMPAD_ADD: 107,
49
+ NUMPAD_DECIMAL: 110,
50
+ NUMPAD_DIVIDE: 111,
51
+ NUMPAD_ENTER: 108,
52
+ NUMPAD_MULTIPLY: 106,
53
+ NUMPAD_SUBTRACT: 109,
54
+ PAGE_DOWN: 34,
55
+ PAGE_UP: 33,
56
+ PERIOD: 190,
57
+ RIGHT: 39,
58
+ SPACE: 32,
59
+ TAB: 9,
60
+ UP: 38
61
+ },
62
+
63
+ buttonCode: {
64
+ LEFT: 0,
65
+ MIDDLE: 1,
66
+ RIGHT: 2
67
+ }
68
+ });
69
+
70
+ $.extend( $.simulate.prototype, {
71
+
72
+ simulateEvent: function( elem, type, options ) {
73
+ var event = this.createEvent( type, options );
74
+ this.dispatchEvent( elem, type, event, options );
75
+ },
76
+
77
+ createEvent: function( type, options ) {
78
+ if ( rkeyEvent.test( type ) ) {
79
+ return this.keyEvent( type, options );
80
+ }
81
+
82
+ if ( rmouseEvent.test( type ) ) {
83
+ return this.mouseEvent( type, options );
84
+ }
85
+ },
86
+
87
+ mouseEvent: function( type, options ) {
88
+ var event, eventDoc, doc, body;
89
+ options = $.extend({
90
+ bubbles: true,
91
+ cancelable: (type !== "mousemove"),
92
+ view: window,
93
+ detail: 0,
94
+ screenX: 0,
95
+ screenY: 0,
96
+ clientX: 1,
97
+ clientY: 1,
98
+ ctrlKey: false,
99
+ altKey: false,
100
+ shiftKey: false,
101
+ metaKey: false,
102
+ button: 0,
103
+ relatedTarget: undefined
104
+ }, options );
105
+
106
+ if ( document.createEvent ) {
107
+ event = document.createEvent( "MouseEvents" );
108
+ event.initMouseEvent( type, options.bubbles, options.cancelable,
109
+ options.view, options.detail,
110
+ options.screenX, options.screenY, options.clientX, options.clientY,
111
+ options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
112
+ options.button, options.relatedTarget || document.body.parentNode );
113
+
114
+ // IE 9+ creates events with pageX and pageY set to 0.
115
+ // Trying to modify the properties throws an error,
116
+ // so we define getters to return the correct values.
117
+ if ( event.pageX === 0 && event.pageY === 0 && Object.defineProperty ) {
118
+ eventDoc = event.relatedTarget.ownerDocument || document;
119
+ doc = eventDoc.documentElement;
120
+ body = eventDoc.body;
121
+
122
+ Object.defineProperty( event, "pageX", {
123
+ get: function() {
124
+ return options.clientX +
125
+ ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
126
+ ( doc && doc.clientLeft || body && body.clientLeft || 0 );
127
+ }
128
+ });
129
+ Object.defineProperty( event, "pageY", {
130
+ get: function() {
131
+ return options.clientY +
132
+ ( doc && doc.scrollTop || body && body.scrollTop || 0 ) -
133
+ ( doc && doc.clientTop || body && body.clientTop || 0 );
134
+ }
135
+ });
136
+ }
137
+ } else if ( document.createEventObject ) {
138
+ event = document.createEventObject();
139
+ $.extend( event, options );
140
+ // standards event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ff974877(v=vs.85).aspx
141
+ // old IE event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ms533544(v=vs.85).aspx
142
+ // so we actually need to map the standard back to oldIE
143
+ event.button = {
144
+ 0: 1,
145
+ 1: 4,
146
+ 2: 2
147
+ }[ event.button ] || event.button;
148
+ }
149
+
150
+ return event;
151
+ },
152
+
153
+ keyEvent: function( type, options ) {
154
+ var event;
155
+ options = $.extend({
156
+ bubbles: true,
157
+ cancelable: true,
158
+ view: window,
159
+ ctrlKey: false,
160
+ altKey: false,
161
+ shiftKey: false,
162
+ metaKey: false,
163
+ keyCode: 0,
164
+ charCode: undefined
165
+ }, options );
166
+
167
+ if ( document.createEvent ) {
168
+ try {
169
+ event = document.createEvent( "KeyEvents" );
170
+ event.initKeyEvent( type, options.bubbles, options.cancelable, options.view,
171
+ options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
172
+ options.keyCode, options.charCode );
173
+ // initKeyEvent throws an exception in WebKit
174
+ // see: http://stackoverflow.com/questions/6406784/initkeyevent-keypress-only-works-in-firefox-need-a-cross-browser-solution
175
+ // and also https://bugs.webkit.org/show_bug.cgi?id=13368
176
+ // fall back to a generic event until we decide to implement initKeyboardEvent
177
+ } catch( err ) {
178
+ event = document.createEvent( "Events" );
179
+ event.initEvent( type, options.bubbles, options.cancelable );
180
+ $.extend( event, {
181
+ view: options.view,
182
+ ctrlKey: options.ctrlKey,
183
+ altKey: options.altKey,
184
+ shiftKey: options.shiftKey,
185
+ metaKey: options.metaKey,
186
+ keyCode: options.keyCode,
187
+ charCode: options.charCode
188
+ });
189
+ }
190
+ } else if ( document.createEventObject ) {
191
+ event = document.createEventObject();
192
+ $.extend( event, options );
193
+ }
194
+
195
+ if ( !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ) || (({}).toString.call( window.opera ) === "[object Opera]") ) {
196
+ event.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode;
197
+ event.charCode = undefined;
198
+ }
199
+
200
+ return event;
201
+ },
202
+
203
+ dispatchEvent: function( elem, type, event ) {
204
+ if ( elem.dispatchEvent ) {
205
+ elem.dispatchEvent( event );
206
+ } else if ( elem.fireEvent ) {
207
+ elem.fireEvent( "on" + type, event );
208
+ }
209
+ },
210
+
211
+ simulateFocus: function() {
212
+ var focusinEvent,
213
+ triggered = false,
214
+ element = $( this.target );
215
+
216
+ function trigger() {
217
+ triggered = true;
218
+ }
219
+
220
+ element.bind( "focus", trigger );
221
+ element[ 0 ].focus();
222
+
223
+ if ( !triggered ) {
224
+ focusinEvent = $.Event( "focusin" );
225
+ focusinEvent.preventDefault();
226
+ element.trigger( focusinEvent );
227
+ element.triggerHandler( "focus" );
228
+ }
229
+ element.unbind( "focus", trigger );
230
+ },
231
+
232
+ simulateBlur: function() {
233
+ var focusoutEvent,
234
+ triggered = false,
235
+ element = $( this.target );
236
+
237
+ function trigger() {
238
+ triggered = true;
239
+ }
240
+
241
+ element.bind( "blur", trigger );
242
+ element[ 0 ].blur();
243
+
244
+ // blur events are async in IE
245
+ setTimeout(function() {
246
+ // IE won't let the blur occur if the window is inactive
247
+ if ( element[ 0 ].ownerDocument.activeElement === element[ 0 ] ) {
248
+ element[ 0 ].ownerDocument.body.focus();
249
+ }
250
+
251
+ // Firefox won't trigger events if the window is inactive
252
+ // IE doesn't trigger events if we had to manually focus the body
253
+ if ( !triggered ) {
254
+ focusoutEvent = $.Event( "focusout" );
255
+ focusoutEvent.preventDefault();
256
+ element.trigger( focusoutEvent );
257
+ element.triggerHandler( "blur" );
258
+ }
259
+ element.unbind( "blur", trigger );
260
+ }, 1 );
261
+ }
262
+ });
263
+
264
+
265
+
266
+ /** complex events **/
267
+
268
+ function findCenter( elem ) {
269
+ var offset,
270
+ document = $( elem.ownerDocument );
271
+ elem = $( elem );
272
+ offset = elem.offset();
273
+
274
+ return {
275
+ x: offset.left + elem.outerWidth() / 2 - document.scrollLeft(),
276
+ y: offset.top + elem.outerHeight() / 2 - document.scrollTop()
277
+ };
278
+ }
279
+
280
+ $.extend( $.simulate.prototype, {
281
+ simulateDrag: function() {
282
+ var i = 0,
283
+ target = this.target,
284
+ options = this.options,
285
+ center = findCenter( target ),
286
+ x = Math.floor( center.x ),
287
+ y = Math.floor( center.y ),
288
+ dx = options.dx || 0,
289
+ dy = options.dy || 0,
290
+ moves = options.moves || 3,
291
+ coord = { clientX: x, clientY: y };
292
+
293
+ this.simulateEvent( target, "mousedown", coord );
294
+
295
+ for ( ; i < moves ; i++ ) {
296
+ x += dx / moves;
297
+ y += dy / moves;
298
+
299
+ coord = {
300
+ clientX: Math.round( x ),
301
+ clientY: Math.round( y )
302
+ };
303
+
304
+ this.simulateEvent( document, "mousemove", coord );
305
+ }
306
+
307
+ this.simulateEvent( target, "mouseup", coord );
308
+ this.simulateEvent( target, "click", coord );
309
+ }
310
+ });
311
+
312
+ })( jQuery );