savage-beast 0.1.0

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 (265) hide show
  1. data/.gitignore +2 -0
  2. data/README.rdoc +42 -0
  3. data/Rakefile +25 -0
  4. data/VERSION +1 -0
  5. data/app/controllers/application.rb +6 -0
  6. data/app/controllers/beast_application_controller.rb +3 -0
  7. data/app/controllers/forums_controller.rb +64 -0
  8. data/app/controllers/moderators_controller.rb +10 -0
  9. data/app/controllers/monitorships_controller.rb +22 -0
  10. data/app/controllers/posts_controller.rb +134 -0
  11. data/app/controllers/topics_controller.rb +110 -0
  12. data/app/helpers/count_helper.rb +13 -0
  13. data/app/helpers/forums_helper.rb +15 -0
  14. data/app/helpers/moderators_helper.rb +2 -0
  15. data/app/helpers/monitorships_helper.rb +2 -0
  16. data/app/helpers/posts_helper.rb +3 -0
  17. data/app/helpers/topics_helper.rb +3 -0
  18. data/app/models/forum.rb +26 -0
  19. data/app/models/moderatorship.rb +6 -0
  20. data/app/models/monitorship.rb +5 -0
  21. data/app/models/monitorships_sweeper.rb +9 -0
  22. data/app/models/post.rb +33 -0
  23. data/app/models/posts_sweeper.rb +12 -0
  24. data/app/models/topic.rb +94 -0
  25. data/app/views/forums/_form.html.erb +21 -0
  26. data/app/views/forums/edit.html.erb +12 -0
  27. data/app/views/forums/index.html.erb +77 -0
  28. data/app/views/forums/new.html.erb +10 -0
  29. data/app/views/forums/show.html.erb +94 -0
  30. data/app/views/layouts/_head.html.erb +34 -0
  31. data/app/views/layouts/_post.rss.builder +9 -0
  32. data/app/views/layouts/application.html.erb +29 -0
  33. data/app/views/layouts/beast.html.erb +29 -0
  34. data/app/views/monitorships/create.js.rjs +4 -0
  35. data/app/views/monitorships/destroy.js.rjs +4 -0
  36. data/app/views/posts/_edit.html.erb +38 -0
  37. data/app/views/posts/edit.html.erb +14 -0
  38. data/app/views/posts/edit.js.rjs +6 -0
  39. data/app/views/posts/index.html.erb +53 -0
  40. data/app/views/posts/index.rss.builder +20 -0
  41. data/app/views/posts/monitored.html.erb +55 -0
  42. data/app/views/posts/monitored.rss.builder +15 -0
  43. data/app/views/posts/update.js.rjs +3 -0
  44. data/app/views/topics/_form.html.erb +28 -0
  45. data/app/views/topics/edit.html.erb +10 -0
  46. data/app/views/topics/index.html.erb +2 -0
  47. data/app/views/topics/new.html.erb +18 -0
  48. data/app/views/topics/show.html.erb +183 -0
  49. data/app/views/topics/show.rss.builder +16 -0
  50. data/config/routes.rb +18 -0
  51. data/generators/beast/beast_generator.rb +88 -0
  52. data/generators/beast/templates/migrations/create_savage_tables.rb +73 -0
  53. data/generators/beast/templates/public/images/clearbits/_readme.txt +12 -0
  54. data/generators/beast/templates/public/images/clearbits/add.gif +0 -0
  55. data/generators/beast/templates/public/images/clearbits/addressbook.gif +0 -0
  56. data/generators/beast/templates/public/images/clearbits/alert.gif +0 -0
  57. data/generators/beast/templates/public/images/clearbits/apple.gif +0 -0
  58. data/generators/beast/templates/public/images/clearbits/arrow1_e.gif +0 -0
  59. data/generators/beast/templates/public/images/clearbits/arrow1_n.gif +0 -0
  60. data/generators/beast/templates/public/images/clearbits/arrow1_ne.gif +0 -0
  61. data/generators/beast/templates/public/images/clearbits/arrow1_nw.gif +0 -0
  62. data/generators/beast/templates/public/images/clearbits/arrow1_s.gif +0 -0
  63. data/generators/beast/templates/public/images/clearbits/arrow1_se.gif +0 -0
  64. data/generators/beast/templates/public/images/clearbits/arrow1_sw.gif +0 -0
  65. data/generators/beast/templates/public/images/clearbits/arrow1_w.gif +0 -0
  66. data/generators/beast/templates/public/images/clearbits/arrow2_e.gif +0 -0
  67. data/generators/beast/templates/public/images/clearbits/arrow2_n.gif +0 -0
  68. data/generators/beast/templates/public/images/clearbits/arrow2_ne.gif +0 -0
  69. data/generators/beast/templates/public/images/clearbits/arrow2_nw.gif +0 -0
  70. data/generators/beast/templates/public/images/clearbits/arrow2_s.gif +0 -0
  71. data/generators/beast/templates/public/images/clearbits/arrow2_se.gif +0 -0
  72. data/generators/beast/templates/public/images/clearbits/arrow2_sw.gif +0 -0
  73. data/generators/beast/templates/public/images/clearbits/arrow2_w.gif +0 -0
  74. data/generators/beast/templates/public/images/clearbits/arrow3_e.gif +0 -0
  75. data/generators/beast/templates/public/images/clearbits/arrow3_n.gif +0 -0
  76. data/generators/beast/templates/public/images/clearbits/arrow3_ne.gif +0 -0
  77. data/generators/beast/templates/public/images/clearbits/arrow3_nw.gif +0 -0
  78. data/generators/beast/templates/public/images/clearbits/arrow3_s.gif +0 -0
  79. data/generators/beast/templates/public/images/clearbits/arrow3_se.gif +0 -0
  80. data/generators/beast/templates/public/images/clearbits/arrow3_sw.gif +0 -0
  81. data/generators/beast/templates/public/images/clearbits/arrow3_w.gif +0 -0
  82. data/generators/beast/templates/public/images/clearbits/ascii.gif +0 -0
  83. data/generators/beast/templates/public/images/clearbits/back.gif +0 -0
  84. data/generators/beast/templates/public/images/clearbits/bg_blank.gif +0 -0
  85. data/generators/beast/templates/public/images/clearbits/bg_circle.gif +0 -0
  86. data/generators/beast/templates/public/images/clearbits/bg_rounded.gif +0 -0
  87. data/generators/beast/templates/public/images/clearbits/bg_rounded_ne.gif +0 -0
  88. data/generators/beast/templates/public/images/clearbits/bg_rounded_nw.gif +0 -0
  89. data/generators/beast/templates/public/images/clearbits/bg_rounded_se.gif +0 -0
  90. data/generators/beast/templates/public/images/clearbits/bg_rounded_sw.gif +0 -0
  91. data/generators/beast/templates/public/images/clearbits/bigsmile.gif +0 -0
  92. data/generators/beast/templates/public/images/clearbits/binary.gif +0 -0
  93. data/generators/beast/templates/public/images/clearbits/blah.gif +0 -0
  94. data/generators/beast/templates/public/images/clearbits/bstop.gif +0 -0
  95. data/generators/beast/templates/public/images/clearbits/buy.gif +0 -0
  96. data/generators/beast/templates/public/images/clearbits/calday.gif +0 -0
  97. data/generators/beast/templates/public/images/clearbits/calendar.gif +0 -0
  98. data/generators/beast/templates/public/images/clearbits/camera.gif +0 -0
  99. data/generators/beast/templates/public/images/clearbits/cart.gif +0 -0
  100. data/generators/beast/templates/public/images/clearbits/cd.gif +0 -0
  101. data/generators/beast/templates/public/images/clearbits/cellphone.gif +0 -0
  102. data/generators/beast/templates/public/images/clearbits/chat.gif +0 -0
  103. data/generators/beast/templates/public/images/clearbits/check.gif +0 -0
  104. data/generators/beast/templates/public/images/clearbits/close.gif +0 -0
  105. data/generators/beast/templates/public/images/clearbits/comment.gif +0 -0
  106. data/generators/beast/templates/public/images/clearbits/cube.gif +0 -0
  107. data/generators/beast/templates/public/images/clearbits/day.gif +0 -0
  108. data/generators/beast/templates/public/images/clearbits/denied.gif +0 -0
  109. data/generators/beast/templates/public/images/clearbits/document.gif +0 -0
  110. data/generators/beast/templates/public/images/clearbits/download.gif +0 -0
  111. data/generators/beast/templates/public/images/clearbits/edit.gif +0 -0
  112. data/generators/beast/templates/public/images/clearbits/eject.gif +0 -0
  113. data/generators/beast/templates/public/images/clearbits/equalizer.gif +0 -0
  114. data/generators/beast/templates/public/images/clearbits/first.gif +0 -0
  115. data/generators/beast/templates/public/images/clearbits/flag.gif +0 -0
  116. data/generators/beast/templates/public/images/clearbits/flash.gif +0 -0
  117. data/generators/beast/templates/public/images/clearbits/folder.gif +0 -0
  118. data/generators/beast/templates/public/images/clearbits/forward.gif +0 -0
  119. data/generators/beast/templates/public/images/clearbits/frown.gif +0 -0
  120. data/generators/beast/templates/public/images/clearbits/ftp.gif +0 -0
  121. data/generators/beast/templates/public/images/clearbits/graph.gif +0 -0
  122. data/generators/beast/templates/public/images/clearbits/heart.gif +0 -0
  123. data/generators/beast/templates/public/images/clearbits/home.gif +0 -0
  124. data/generators/beast/templates/public/images/clearbits/html.gif +0 -0
  125. data/generators/beast/templates/public/images/clearbits/ipod.gif +0 -0
  126. data/generators/beast/templates/public/images/clearbits/last.gif +0 -0
  127. data/generators/beast/templates/public/images/clearbits/lock.gif +0 -0
  128. data/generators/beast/templates/public/images/clearbits/loop.gif +0 -0
  129. data/generators/beast/templates/public/images/clearbits/mail.gif +0 -0
  130. data/generators/beast/templates/public/images/clearbits/man.gif +0 -0
  131. data/generators/beast/templates/public/images/clearbits/manman.gif +0 -0
  132. data/generators/beast/templates/public/images/clearbits/music.gif +0 -0
  133. data/generators/beast/templates/public/images/clearbits/mute.gif +0 -0
  134. data/generators/beast/templates/public/images/clearbits/mute_centered.gif +0 -0
  135. data/generators/beast/templates/public/images/clearbits/newwindow.gif +0 -0
  136. data/generators/beast/templates/public/images/clearbits/next.gif +0 -0
  137. data/generators/beast/templates/public/images/clearbits/night.gif +0 -0
  138. data/generators/beast/templates/public/images/clearbits/open.gif +0 -0
  139. data/generators/beast/templates/public/images/clearbits/pause.gif +0 -0
  140. data/generators/beast/templates/public/images/clearbits/phone.gif +0 -0
  141. data/generators/beast/templates/public/images/clearbits/play.gif +0 -0
  142. data/generators/beast/templates/public/images/clearbits/previous.gif +0 -0
  143. data/generators/beast/templates/public/images/clearbits/quicktime.gif +0 -0
  144. data/generators/beast/templates/public/images/clearbits/redo.gif +0 -0
  145. data/generators/beast/templates/public/images/clearbits/reload.gif +0 -0
  146. data/generators/beast/templates/public/images/clearbits/sad.gif +0 -0
  147. data/generators/beast/templates/public/images/clearbits/save.gif +0 -0
  148. data/generators/beast/templates/public/images/clearbits/scream.gif +0 -0
  149. data/generators/beast/templates/public/images/clearbits/search.gif +0 -0
  150. data/generators/beast/templates/public/images/clearbits/seconds.gif +0 -0
  151. data/generators/beast/templates/public/images/clearbits/smile.gif +0 -0
  152. data/generators/beast/templates/public/images/clearbits/smirk.gif +0 -0
  153. data/generators/beast/templates/public/images/clearbits/star.gif +0 -0
  154. data/generators/beast/templates/public/images/clearbits/stop.gif +0 -0
  155. data/generators/beast/templates/public/images/clearbits/subtract.gif +0 -0
  156. data/generators/beast/templates/public/images/clearbits/switch.gif +0 -0
  157. data/generators/beast/templates/public/images/clearbits/target.gif +0 -0
  158. data/generators/beast/templates/public/images/clearbits/tcp.gif +0 -0
  159. data/generators/beast/templates/public/images/clearbits/time.gif +0 -0
  160. data/generators/beast/templates/public/images/clearbits/toggle.gif +0 -0
  161. data/generators/beast/templates/public/images/clearbits/tongue.gif +0 -0
  162. data/generators/beast/templates/public/images/clearbits/tools.gif +0 -0
  163. data/generators/beast/templates/public/images/clearbits/trackback.gif +0 -0
  164. data/generators/beast/templates/public/images/clearbits/trash.gif +0 -0
  165. data/generators/beast/templates/public/images/clearbits/tv.gif +0 -0
  166. data/generators/beast/templates/public/images/clearbits/type.gif +0 -0
  167. data/generators/beast/templates/public/images/clearbits/undo.gif +0 -0
  168. data/generators/beast/templates/public/images/clearbits/unlock.gif +0 -0
  169. data/generators/beast/templates/public/images/clearbits/upload.gif +0 -0
  170. data/generators/beast/templates/public/images/clearbits/user.gif +0 -0
  171. data/generators/beast/templates/public/images/clearbits/video.gif +0 -0
  172. data/generators/beast/templates/public/images/clearbits/volume_high.gif +0 -0
  173. data/generators/beast/templates/public/images/clearbits/volume_low.gif +0 -0
  174. data/generators/beast/templates/public/images/clearbits/wifi.gif +0 -0
  175. data/generators/beast/templates/public/images/clearbits/window.gif +0 -0
  176. data/generators/beast/templates/public/images/clearbits/woman.gif +0 -0
  177. data/generators/beast/templates/public/images/clearbits/womanman.gif +0 -0
  178. data/generators/beast/templates/public/images/clearbits/work.gif +0 -0
  179. data/generators/beast/templates/public/images/clearbits/zoomin.gif +0 -0
  180. data/generators/beast/templates/public/images/clearbits/zoomout.gif +0 -0
  181. data/generators/beast/templates/public/images/feed-icon.png +0 -0
  182. data/generators/beast/templates/public/images/rails.png +0 -0
  183. data/generators/beast/templates/public/images/reply_background.png +0 -0
  184. data/generators/beast/templates/public/images/small_circle.gif +0 -0
  185. data/generators/beast/templates/public/images/spinner.gif +0 -0
  186. data/generators/beast/templates/public/images/spinner_black.gif +0 -0
  187. data/generators/beast/templates/public/images/spinner_bounce.gif +0 -0
  188. data/generators/beast/templates/public/javascripts/beast.js +80 -0
  189. data/generators/beast/templates/public/stylesheets/display.css +941 -0
  190. data/lang/en.yml +206 -0
  191. data/lib/beast.rb +79 -0
  192. data/lib/savage_beast/application_helper.rb +91 -0
  193. data/lib/savage_beast/authentication_system.rb +47 -0
  194. data/lib/savage_beast/user_init.rb +79 -0
  195. data/lib/tasks/capistrano.rake +90 -0
  196. data/lib/tasks/deploy_edge.rake +44 -0
  197. data/lib/tasks/savage_beast.rake +133 -0
  198. data/po/beast.pot +712 -0
  199. data/po/nl/beast.po +700 -0
  200. data/tested_plugins/acts_as_list/README +23 -0
  201. data/tested_plugins/acts_as_list/init.rb +3 -0
  202. data/tested_plugins/acts_as_list/lib/active_record/acts/list.rb +256 -0
  203. data/tested_plugins/acts_as_list/test/list_test.rb +332 -0
  204. data/tested_plugins/gibberish/LICENSE +18 -0
  205. data/tested_plugins/gibberish/README +118 -0
  206. data/tested_plugins/gibberish/Rakefile +14 -0
  207. data/tested_plugins/gibberish/init.rb +3 -0
  208. data/tested_plugins/gibberish/lang/es.yml +3 -0
  209. data/tested_plugins/gibberish/lang/fr.yml +3 -0
  210. data/tested_plugins/gibberish/lib/gibberish.rb +8 -0
  211. data/tested_plugins/gibberish/lib/gibberish/localize.rb +88 -0
  212. data/tested_plugins/gibberish/lib/gibberish/string_ext.rb +17 -0
  213. data/tested_plugins/gibberish/test/gibberish_test.rb +203 -0
  214. data/tested_plugins/gibberish/test/lang/es.yml +1 -0
  215. data/tested_plugins/gibberish/test/lang/fr.yml +1 -0
  216. data/tested_plugins/mislav-will_paginate/CHANGELOG.rdoc +110 -0
  217. data/tested_plugins/mislav-will_paginate/LICENSE +18 -0
  218. data/tested_plugins/mislav-will_paginate/README.rdoc +107 -0
  219. data/tested_plugins/mislav-will_paginate/Rakefile +53 -0
  220. data/tested_plugins/mislav-will_paginate/examples/apple-circle.gif +0 -0
  221. data/tested_plugins/mislav-will_paginate/examples/index.haml +69 -0
  222. data/tested_plugins/mislav-will_paginate/examples/index.html +92 -0
  223. data/tested_plugins/mislav-will_paginate/examples/pagination.css +90 -0
  224. data/tested_plugins/mislav-will_paginate/examples/pagination.sass +91 -0
  225. data/tested_plugins/mislav-will_paginate/init.rb +1 -0
  226. data/tested_plugins/mislav-will_paginate/lib/will_paginate.rb +78 -0
  227. data/tested_plugins/mislav-will_paginate/lib/will_paginate/array.rb +16 -0
  228. data/tested_plugins/mislav-will_paginate/lib/will_paginate/collection.rb +146 -0
  229. data/tested_plugins/mislav-will_paginate/lib/will_paginate/core_ext.rb +32 -0
  230. data/tested_plugins/mislav-will_paginate/lib/will_paginate/finder.rb +264 -0
  231. data/tested_plugins/mislav-will_paginate/lib/will_paginate/named_scope.rb +170 -0
  232. data/tested_plugins/mislav-will_paginate/lib/will_paginate/named_scope_patch.rb +37 -0
  233. data/tested_plugins/mislav-will_paginate/lib/will_paginate/version.rb +9 -0
  234. data/tested_plugins/mislav-will_paginate/lib/will_paginate/view_helpers.rb +402 -0
  235. data/tested_plugins/mislav-will_paginate/test/boot.rb +21 -0
  236. data/tested_plugins/mislav-will_paginate/test/collection_test.rb +143 -0
  237. data/tested_plugins/mislav-will_paginate/test/console +8 -0
  238. data/tested_plugins/mislav-will_paginate/test/database.yml +22 -0
  239. data/tested_plugins/mislav-will_paginate/test/finder_test.rb +476 -0
  240. data/tested_plugins/mislav-will_paginate/test/fixtures/admin.rb +3 -0
  241. data/tested_plugins/mislav-will_paginate/test/fixtures/developer.rb +14 -0
  242. data/tested_plugins/mislav-will_paginate/test/fixtures/developers_projects.yml +13 -0
  243. data/tested_plugins/mislav-will_paginate/test/fixtures/project.rb +15 -0
  244. data/tested_plugins/mislav-will_paginate/test/fixtures/projects.yml +6 -0
  245. data/tested_plugins/mislav-will_paginate/test/fixtures/replies.yml +29 -0
  246. data/tested_plugins/mislav-will_paginate/test/fixtures/reply.rb +7 -0
  247. data/tested_plugins/mislav-will_paginate/test/fixtures/schema.rb +38 -0
  248. data/tested_plugins/mislav-will_paginate/test/fixtures/topic.rb +10 -0
  249. data/tested_plugins/mislav-will_paginate/test/fixtures/topics.yml +30 -0
  250. data/tested_plugins/mislav-will_paginate/test/fixtures/user.rb +2 -0
  251. data/tested_plugins/mislav-will_paginate/test/fixtures/users.yml +35 -0
  252. data/tested_plugins/mislav-will_paginate/test/helper.rb +40 -0
  253. data/tested_plugins/mislav-will_paginate/test/lib/activerecord_test_case.rb +43 -0
  254. data/tested_plugins/mislav-will_paginate/test/lib/activerecord_test_connector.rb +75 -0
  255. data/tested_plugins/mislav-will_paginate/test/lib/load_fixtures.rb +11 -0
  256. data/tested_plugins/mislav-will_paginate/test/lib/view_test_process.rb +178 -0
  257. data/tested_plugins/mislav-will_paginate/test/tasks.rake +59 -0
  258. data/tested_plugins/mislav-will_paginate/test/view_test.rb +365 -0
  259. data/tested_plugins/white_list/README +29 -0
  260. data/tested_plugins/white_list/Rakefile +22 -0
  261. data/tested_plugins/white_list/init.rb +2 -0
  262. data/tested_plugins/white_list/lib/white_list_helper.rb +97 -0
  263. data/tested_plugins/white_list/test/white_list_test.rb +132 -0
  264. data/tested_plugins/white_list_formatted_content/init.rb +27 -0
  265. metadata +322 -0
@@ -0,0 +1,23 @@
1
+ ActsAsList
2
+ ==========
3
+
4
+ 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.
5
+
6
+
7
+ Example
8
+ =======
9
+
10
+ class TodoList < ActiveRecord::Base
11
+ has_many :todo_items, :order => "position"
12
+ end
13
+
14
+ class TodoItem < ActiveRecord::Base
15
+ belongs_to :todo_list
16
+ acts_as_list :scope => :todo_list
17
+ end
18
+
19
+ todo_list.first.move_to_bottom
20
+ todo_list.last.move_higher
21
+
22
+
23
+ Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
@@ -0,0 +1,3 @@
1
+ $:.unshift "#{File.dirname(__FILE__)}/lib"
2
+ require 'active_record/acts/list'
3
+ ActiveRecord::Base.class_eval { include ActiveRecord::Acts::List }
@@ -0,0 +1,256 @@
1
+ module ActiveRecord
2
+ module Acts #:nodoc:
3
+ module List #:nodoc:
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
9
+ # The class that has this specified needs to have a +position+ column defined as an integer on
10
+ # the mapped database table.
11
+ #
12
+ # Todo list example:
13
+ #
14
+ # class TodoList < ActiveRecord::Base
15
+ # has_many :todo_items, :order => "position"
16
+ # end
17
+ #
18
+ # class TodoItem < ActiveRecord::Base
19
+ # belongs_to :todo_list
20
+ # acts_as_list :scope => :todo_list
21
+ # end
22
+ #
23
+ # todo_list.first.move_to_bottom
24
+ # todo_list.last.move_higher
25
+ module ClassMethods
26
+ # Configuration options are:
27
+ #
28
+ # * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
29
+ # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
30
+ # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
31
+ # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
32
+ # Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
33
+ def acts_as_list(options = {})
34
+ configuration = { :column => "position", :scope => "1 = 1" }
35
+ configuration.update(options) if options.is_a?(Hash)
36
+
37
+ configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
38
+
39
+ if configuration[:scope].is_a?(Symbol)
40
+ scope_condition_method = %(
41
+ def scope_condition
42
+ if #{configuration[:scope].to_s}.nil?
43
+ "#{configuration[:scope].to_s} IS NULL"
44
+ else
45
+ "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
46
+ end
47
+ end
48
+ )
49
+ else
50
+ scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
51
+ end
52
+
53
+ class_eval <<-EOV
54
+ include ActiveRecord::Acts::List::InstanceMethods
55
+
56
+ def acts_as_list_class
57
+ ::#{self.name}
58
+ end
59
+
60
+ def position_column
61
+ '#{configuration[:column]}'
62
+ end
63
+
64
+ #{scope_condition_method}
65
+
66
+ before_destroy :remove_from_list
67
+ before_create :add_to_list_bottom
68
+ EOV
69
+ end
70
+ end
71
+
72
+ # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
73
+ # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
74
+ # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
75
+ # the first in the list of all chapters.
76
+ module InstanceMethods
77
+ # Insert the item at the given position (defaults to the top position of 1).
78
+ def insert_at(position = 1)
79
+ insert_at_position(position)
80
+ end
81
+
82
+ # Swap positions with the next lower item, if one exists.
83
+ def move_lower
84
+ return unless lower_item
85
+
86
+ acts_as_list_class.transaction do
87
+ lower_item.decrement_position
88
+ increment_position
89
+ end
90
+ end
91
+
92
+ # Swap positions with the next higher item, if one exists.
93
+ def move_higher
94
+ return unless higher_item
95
+
96
+ acts_as_list_class.transaction do
97
+ higher_item.increment_position
98
+ decrement_position
99
+ end
100
+ end
101
+
102
+ # Move to the bottom of the list. If the item is already in the list, the items below it have their
103
+ # position adjusted accordingly.
104
+ def move_to_bottom
105
+ return unless in_list?
106
+ acts_as_list_class.transaction do
107
+ decrement_positions_on_lower_items
108
+ assume_bottom_position
109
+ end
110
+ end
111
+
112
+ # Move to the top of the list. If the item is already in the list, the items above it have their
113
+ # position adjusted accordingly.
114
+ def move_to_top
115
+ return unless in_list?
116
+ acts_as_list_class.transaction do
117
+ increment_positions_on_higher_items
118
+ assume_top_position
119
+ end
120
+ end
121
+
122
+ # Removes the item from the list.
123
+ def remove_from_list
124
+ if in_list?
125
+ decrement_positions_on_lower_items
126
+ update_attribute position_column, nil
127
+ end
128
+ end
129
+
130
+ # Increase the position of this item without adjusting the rest of the list.
131
+ def increment_position
132
+ return unless in_list?
133
+ update_attribute position_column, self.send(position_column).to_i + 1
134
+ end
135
+
136
+ # Decrease the position of this item without adjusting the rest of the list.
137
+ def decrement_position
138
+ return unless in_list?
139
+ update_attribute position_column, self.send(position_column).to_i - 1
140
+ end
141
+
142
+ # Return +true+ if this object is the first in the list.
143
+ def first?
144
+ return false unless in_list?
145
+ self.send(position_column) == 1
146
+ end
147
+
148
+ # Return +true+ if this object is the last in the list.
149
+ def last?
150
+ return false unless in_list?
151
+ self.send(position_column) == bottom_position_in_list
152
+ end
153
+
154
+ # Return the next higher item in the list.
155
+ def higher_item
156
+ return nil unless in_list?
157
+ acts_as_list_class.find(:first, :conditions =>
158
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
159
+ )
160
+ end
161
+
162
+ # Return the next lower item in the list.
163
+ def lower_item
164
+ return nil unless in_list?
165
+ acts_as_list_class.find(:first, :conditions =>
166
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
167
+ )
168
+ end
169
+
170
+ # Test if this record is in a list
171
+ def in_list?
172
+ !send(position_column).nil?
173
+ end
174
+
175
+ private
176
+ def add_to_list_top
177
+ increment_positions_on_all_items
178
+ end
179
+
180
+ def add_to_list_bottom
181
+ self[position_column] = bottom_position_in_list.to_i + 1
182
+ end
183
+
184
+ # Overwrite this method to define the scope of the list changes
185
+ def scope_condition() "1" end
186
+
187
+ # Returns the bottom position number in the list.
188
+ # bottom_position_in_list # => 2
189
+ def bottom_position_in_list(except = nil)
190
+ item = bottom_item(except)
191
+ item ? item.send(position_column) : 0
192
+ end
193
+
194
+ # Returns the bottom item
195
+ def bottom_item(except = nil)
196
+ conditions = scope_condition
197
+ conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
198
+ acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
199
+ end
200
+
201
+ # Forces item to assume the bottom position in the list.
202
+ def assume_bottom_position
203
+ update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
204
+ end
205
+
206
+ # Forces item to assume the top position in the list.
207
+ def assume_top_position
208
+ update_attribute(position_column, 1)
209
+ end
210
+
211
+ # This has the effect of moving all the higher items up one.
212
+ def decrement_positions_on_higher_items(position)
213
+ acts_as_list_class.update_all(
214
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
215
+ )
216
+ end
217
+
218
+ # This has the effect of moving all the lower items up one.
219
+ def decrement_positions_on_lower_items
220
+ return unless in_list?
221
+ acts_as_list_class.update_all(
222
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
223
+ )
224
+ end
225
+
226
+ # This has the effect of moving all the higher items down one.
227
+ def increment_positions_on_higher_items
228
+ return unless in_list?
229
+ acts_as_list_class.update_all(
230
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
231
+ )
232
+ end
233
+
234
+ # This has the effect of moving all the lower items down one.
235
+ def increment_positions_on_lower_items(position)
236
+ acts_as_list_class.update_all(
237
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
238
+ )
239
+ end
240
+
241
+ # Increments position (<tt>position_column</tt>) of all items in the list.
242
+ def increment_positions_on_all_items
243
+ acts_as_list_class.update_all(
244
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
245
+ )
246
+ end
247
+
248
+ def insert_at_position(position)
249
+ remove_from_list
250
+ increment_positions_on_lower_items(position)
251
+ self.update_attribute(position_column, position)
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,332 @@
1
+ require 'test/unit'
2
+
3
+ require 'rubygems'
4
+ gem 'activerecord', '>= 1.15.4.7794'
5
+ require 'active_record'
6
+
7
+ require "#{File.dirname(__FILE__)}/../init"
8
+
9
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
10
+
11
+ def setup_db
12
+ ActiveRecord::Schema.define(:version => 1) do
13
+ create_table :mixins do |t|
14
+ t.column :pos, :integer
15
+ t.column :parent_id, :integer
16
+ t.column :created_at, :datetime
17
+ t.column :updated_at, :datetime
18
+ end
19
+ end
20
+ end
21
+
22
+ def teardown_db
23
+ ActiveRecord::Base.connection.tables.each do |table|
24
+ ActiveRecord::Base.connection.drop_table(table)
25
+ end
26
+ end
27
+
28
+ class Mixin < ActiveRecord::Base
29
+ end
30
+
31
+ class ListMixin < Mixin
32
+ acts_as_list :column => "pos", :scope => :parent
33
+
34
+ def self.table_name() "mixins" end
35
+ end
36
+
37
+ class ListMixinSub1 < ListMixin
38
+ end
39
+
40
+ class ListMixinSub2 < ListMixin
41
+ end
42
+
43
+ class ListWithStringScopeMixin < ActiveRecord::Base
44
+ acts_as_list :column => "pos", :scope => 'parent_id = #{parent_id}'
45
+
46
+ def self.table_name() "mixins" end
47
+ end
48
+
49
+
50
+ class ListTest < Test::Unit::TestCase
51
+
52
+ def setup
53
+ setup_db
54
+ (1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 5 }
55
+ end
56
+
57
+ def teardown
58
+ teardown_db
59
+ end
60
+
61
+ def test_reordering
62
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
63
+
64
+ ListMixin.find(2).move_lower
65
+ assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
66
+
67
+ ListMixin.find(2).move_higher
68
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
69
+
70
+ ListMixin.find(1).move_to_bottom
71
+ assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
72
+
73
+ ListMixin.find(1).move_to_top
74
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
75
+
76
+ ListMixin.find(2).move_to_bottom
77
+ assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
78
+
79
+ ListMixin.find(4).move_to_top
80
+ assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
81
+ end
82
+
83
+ def test_move_to_bottom_with_next_to_last_item
84
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
85
+ ListMixin.find(3).move_to_bottom
86
+ assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
87
+ end
88
+
89
+ def test_next_prev
90
+ assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
91
+ assert_nil ListMixin.find(1).higher_item
92
+ assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
93
+ assert_nil ListMixin.find(4).lower_item
94
+ end
95
+
96
+ def test_injection
97
+ item = ListMixin.new(:parent_id => 1)
98
+ assert_equal "parent_id = 1", item.scope_condition
99
+ assert_equal "pos", item.position_column
100
+ end
101
+
102
+ def test_insert
103
+ new = ListMixin.create(:parent_id => 20)
104
+ assert_equal 1, new.pos
105
+ assert new.first?
106
+ assert new.last?
107
+
108
+ new = ListMixin.create(:parent_id => 20)
109
+ assert_equal 2, new.pos
110
+ assert !new.first?
111
+ assert new.last?
112
+
113
+ new = ListMixin.create(:parent_id => 20)
114
+ assert_equal 3, new.pos
115
+ assert !new.first?
116
+ assert new.last?
117
+
118
+ new = ListMixin.create(:parent_id => 0)
119
+ assert_equal 1, new.pos
120
+ assert new.first?
121
+ assert new.last?
122
+ end
123
+
124
+ def test_insert_at
125
+ new = ListMixin.create(:parent_id => 20)
126
+ assert_equal 1, new.pos
127
+
128
+ new = ListMixin.create(:parent_id => 20)
129
+ assert_equal 2, new.pos
130
+
131
+ new = ListMixin.create(:parent_id => 20)
132
+ assert_equal 3, new.pos
133
+
134
+ new4 = ListMixin.create(:parent_id => 20)
135
+ assert_equal 4, new4.pos
136
+
137
+ new4.insert_at(3)
138
+ assert_equal 3, new4.pos
139
+
140
+ new.reload
141
+ assert_equal 4, new.pos
142
+
143
+ new.insert_at(2)
144
+ assert_equal 2, new.pos
145
+
146
+ new4.reload
147
+ assert_equal 4, new4.pos
148
+
149
+ new5 = ListMixin.create(:parent_id => 20)
150
+ assert_equal 5, new5.pos
151
+
152
+ new5.insert_at(1)
153
+ assert_equal 1, new5.pos
154
+
155
+ new4.reload
156
+ assert_equal 5, new4.pos
157
+ end
158
+
159
+ def test_delete_middle
160
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
161
+
162
+ ListMixin.find(2).destroy
163
+
164
+ assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
165
+
166
+ assert_equal 1, ListMixin.find(1).pos
167
+ assert_equal 2, ListMixin.find(3).pos
168
+ assert_equal 3, ListMixin.find(4).pos
169
+
170
+ ListMixin.find(1).destroy
171
+
172
+ assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
173
+
174
+ assert_equal 1, ListMixin.find(3).pos
175
+ assert_equal 2, ListMixin.find(4).pos
176
+ end
177
+
178
+ def test_with_string_based_scope
179
+ new = ListWithStringScopeMixin.create(:parent_id => 500)
180
+ assert_equal 1, new.pos
181
+ assert new.first?
182
+ assert new.last?
183
+ end
184
+
185
+ def test_nil_scope
186
+ new1, new2, new3 = ListMixin.create, ListMixin.create, ListMixin.create
187
+ new2.move_higher
188
+ assert_equal [new2, new1, new3], ListMixin.find(:all, :conditions => 'parent_id IS NULL', :order => 'pos')
189
+ end
190
+
191
+
192
+ def test_remove_from_list_should_then_fail_in_list?
193
+ assert_equal true, ListMixin.find(1).in_list?
194
+ ListMixin.find(1).remove_from_list
195
+ assert_equal false, ListMixin.find(1).in_list?
196
+ end
197
+
198
+ def test_remove_from_list_should_set_position_to_nil
199
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
200
+
201
+ ListMixin.find(2).remove_from_list
202
+
203
+ assert_equal [2, 1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
204
+
205
+ assert_equal 1, ListMixin.find(1).pos
206
+ assert_equal nil, ListMixin.find(2).pos
207
+ assert_equal 2, ListMixin.find(3).pos
208
+ assert_equal 3, ListMixin.find(4).pos
209
+ end
210
+
211
+ def test_remove_before_destroy_does_not_shift_lower_items_twice
212
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
213
+
214
+ ListMixin.find(2).remove_from_list
215
+ ListMixin.find(2).destroy
216
+
217
+ assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
218
+
219
+ assert_equal 1, ListMixin.find(1).pos
220
+ assert_equal 2, ListMixin.find(3).pos
221
+ assert_equal 3, ListMixin.find(4).pos
222
+ end
223
+
224
+ end
225
+
226
+ class ListSubTest < Test::Unit::TestCase
227
+
228
+ def setup
229
+ setup_db
230
+ (1..4).each { |i| ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).create! :pos => i, :parent_id => 5000 }
231
+ end
232
+
233
+ def teardown
234
+ teardown_db
235
+ end
236
+
237
+ def test_reordering
238
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
239
+
240
+ ListMixin.find(2).move_lower
241
+ assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
242
+
243
+ ListMixin.find(2).move_higher
244
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
245
+
246
+ ListMixin.find(1).move_to_bottom
247
+ assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
248
+
249
+ ListMixin.find(1).move_to_top
250
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
251
+
252
+ ListMixin.find(2).move_to_bottom
253
+ assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
254
+
255
+ ListMixin.find(4).move_to_top
256
+ assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
257
+ end
258
+
259
+ def test_move_to_bottom_with_next_to_last_item
260
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
261
+ ListMixin.find(3).move_to_bottom
262
+ assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
263
+ end
264
+
265
+ def test_next_prev
266
+ assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
267
+ assert_nil ListMixin.find(1).higher_item
268
+ assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
269
+ assert_nil ListMixin.find(4).lower_item
270
+ end
271
+
272
+ def test_injection
273
+ item = ListMixin.new("parent_id"=>1)
274
+ assert_equal "parent_id = 1", item.scope_condition
275
+ assert_equal "pos", item.position_column
276
+ end
277
+
278
+ def test_insert_at
279
+ new = ListMixin.create("parent_id" => 20)
280
+ assert_equal 1, new.pos
281
+
282
+ new = ListMixinSub1.create("parent_id" => 20)
283
+ assert_equal 2, new.pos
284
+
285
+ new = ListMixinSub2.create("parent_id" => 20)
286
+ assert_equal 3, new.pos
287
+
288
+ new4 = ListMixin.create("parent_id" => 20)
289
+ assert_equal 4, new4.pos
290
+
291
+ new4.insert_at(3)
292
+ assert_equal 3, new4.pos
293
+
294
+ new.reload
295
+ assert_equal 4, new.pos
296
+
297
+ new.insert_at(2)
298
+ assert_equal 2, new.pos
299
+
300
+ new4.reload
301
+ assert_equal 4, new4.pos
302
+
303
+ new5 = ListMixinSub1.create("parent_id" => 20)
304
+ assert_equal 5, new5.pos
305
+
306
+ new5.insert_at(1)
307
+ assert_equal 1, new5.pos
308
+
309
+ new4.reload
310
+ assert_equal 5, new4.pos
311
+ end
312
+
313
+ def test_delete_middle
314
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
315
+
316
+ ListMixin.find(2).destroy
317
+
318
+ assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
319
+
320
+ assert_equal 1, ListMixin.find(1).pos
321
+ assert_equal 2, ListMixin.find(3).pos
322
+ assert_equal 3, ListMixin.find(4).pos
323
+
324
+ ListMixin.find(1).destroy
325
+
326
+ assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
327
+
328
+ assert_equal 1, ListMixin.find(3).pos
329
+ assert_equal 2, ListMixin.find(4).pos
330
+ end
331
+
332
+ end