parlement 0.14 → 0.17

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 (205) hide show
  1. data/CHANGES +41 -1
  2. data/MEMORY +66 -5
  3. data/README +10 -5
  4. data/Rakefile +15 -23
  5. data/app/controllers/account_controller.rb +48 -43
  6. data/app/controllers/{application.rb → application_controller.rb} +15 -12
  7. data/app/controllers/elt_controller.rb +77 -32
  8. data/app/controllers/subscriber_controller.rb +11 -10
  9. data/app/helpers/application_helper.rb +14 -1
  10. data/app/helpers/elt_helper.rb +9 -7
  11. data/app/models/elt.rb +25 -24
  12. data/app/models/mail.rb +44 -47
  13. data/app/models/person_notify.rb +2 -2
  14. data/app/models/user.rb +128 -2
  15. data/app/models/user_notify.rb +15 -15
  16. data/app/views/account/_login.rhtml +39 -39
  17. data/app/views/account/_show.rhtml +22 -30
  18. data/app/views/account/signup.rhtml +2 -2
  19. data/app/views/elt/_choice.rhtml +6 -6
  20. data/app/views/elt/_elt.rhtml +27 -32
  21. data/app/views/elt/choices.rhtml +16 -18
  22. data/app/views/elt/list/_byDate.rhtml +14 -14
  23. data/app/views/elt/list/_byVote.rhtml +15 -15
  24. data/app/views/elt/list/_children.rhtml +48 -40
  25. data/app/views/elt/list/_subscribers.rhtml +1 -1
  26. data/app/views/elt/new.rhtml +22 -21
  27. data/app/views/elt/rss.rxml +4 -11
  28. data/app/views/elt/show.rhtml +65 -61
  29. data/app/views/elt/vote_rss.rxml +4 -11
  30. data/app/views/layouts/top.rhtml +39 -50
  31. data/app/views/person/_listElts.rhtml +1 -1
  32. data/app/views/person/show.rhtml +1 -1
  33. data/{vendor/plugins/login_engine/app → app}/views/user_notify/change_password.rhtml +0 -0
  34. data/{vendor/plugins/login_engine/app → app}/views/user_notify/delete.rhtml +0 -0
  35. data/{vendor/plugins/login_engine/app → app}/views/user_notify/forgot_password.rhtml +0 -0
  36. data/{vendor/plugins/login_engine/app → app}/views/user_notify/pending_delete.rhtml +0 -0
  37. data/{vendor/plugins/login_engine/app → app}/views/user_notify/signup.rhtml +0 -0
  38. data/config/boot.rb +97 -32
  39. data/config/environment.rb +37 -35
  40. data/config/environments/development.rb +2 -3
  41. data/config/environments/production.rb +3 -0
  42. data/config/initializers/string_ruby_1.8.rb +10 -0
  43. data/config/routes.rb +17 -22
  44. data/db/schema.rb +102 -74
  45. data/lib/tasks/rspec.rake +167 -0
  46. data/public/404.html +25 -7
  47. data/public/500.html +26 -7
  48. data/public/dispatch.cgi +0 -0
  49. data/public/dispatch.fcgi +0 -0
  50. data/public/dispatch.rb +0 -0
  51. data/public/images/live_tree_branch_collapsed_icon.gif +0 -0
  52. data/public/images/live_tree_branch_expanded_icon.gif +0 -0
  53. data/public/images/live_tree_leaf_icon.gif +0 -0
  54. data/public/javascripts/application.js +258 -0
  55. data/public/javascripts/controls.js +544 -414
  56. data/public/javascripts/dragdrop.js +229 -198
  57. data/public/javascripts/effects.js +499 -459
  58. data/public/javascripts/prototype.js +2926 -1121
  59. data/public/javascripts/shadedborder.js +68 -50
  60. data/public/stylesheets/default.css +34 -34
  61. data/public/stylesheets/live_tree.css +0 -0
  62. data/public/stylesheets/scaffold.css +6 -6
  63. data/script/about +0 -0
  64. data/script/autospec +6 -0
  65. data/script/benchmarker +0 -0
  66. data/script/breakpointer +0 -0
  67. data/script/console +0 -0
  68. data/script/dbconsole +3 -0
  69. data/script/destroy +0 -0
  70. data/script/generate +0 -0
  71. data/script/plugin +0 -0
  72. data/script/profiler +0 -0
  73. data/script/runner +0 -0
  74. data/script/server +0 -0
  75. data/script/spec +10 -0
  76. data/script/spec_server +9 -0
  77. data/test/unit/attachment_test.rb +4 -4
  78. data/test/unit/choice_test.rb +1 -1
  79. data/test/unit/elt_test.rb +9 -9
  80. data/test/unit/mail_notify_test.rb +2 -2
  81. data/test/unit/mail_test.rb +18 -11
  82. data/test/unit/person_notify_test.rb +1 -1
  83. data/test/unit/person_test.rb +1 -1
  84. data/test/unit/subscriber_test.rb +1 -1
  85. data/test/unit/user_test.rb +81 -0
  86. data/test/unit/visit_test.rb +6 -6
  87. data/vendor/plugins/activerecord_foreign_key_extensions/init.rb +2 -0
  88. data/vendor/plugins/activerecord_foreign_key_extensions/lib/active_record_extensions.rb +182 -0
  89. data/vendor/plugins/activerecord_text_id_extensions/init.rb +2 -0
  90. data/vendor/plugins/activerecord_text_id_extensions/lib/active_record_extensions.rb +24 -0
  91. data/vendor/plugins/acts_as_nested_set/README +15 -0
  92. data/vendor/plugins/acts_as_nested_set/init.rb +1 -0
  93. data/vendor/plugins/acts_as_nested_set/lib/active_record/acts/nested_set.rb +210 -0
  94. data/vendor/plugins/acts_as_nested_set/test/nested_set_test.rb +269 -0
  95. data/vendor/plugins/acts_as_tree/README +26 -0
  96. data/vendor/plugins/acts_as_tree/Rakefile +22 -0
  97. data/vendor/plugins/acts_as_tree/init.rb +1 -0
  98. data/vendor/plugins/acts_as_tree/lib/active_record/acts/tree.rb +96 -0
  99. data/vendor/plugins/{output_compression/CHANGELOG → acts_as_tree/test/abstract_unit.rb} +0 -0
  100. data/vendor/plugins/acts_as_tree/test/acts_as_tree_test.rb +219 -0
  101. data/vendor/plugins/acts_as_tree/test/database.yml +0 -0
  102. data/vendor/plugins/acts_as_tree/test/fixtures/mixin.rb +0 -0
  103. data/vendor/plugins/acts_as_tree/test/fixtures/mixins.yml +0 -0
  104. data/vendor/plugins/acts_as_tree/test/schema.rb +0 -0
  105. data/vendor/plugins/classic_pagination/CHANGELOG +152 -0
  106. data/vendor/plugins/classic_pagination/README +18 -0
  107. data/vendor/plugins/{output_compression/rakefile → classic_pagination/Rakefile} +22 -22
  108. data/vendor/plugins/classic_pagination/init.rb +33 -0
  109. data/vendor/plugins/classic_pagination/install.rb +1 -0
  110. data/vendor/plugins/classic_pagination/lib/pagination.rb +405 -0
  111. data/vendor/plugins/classic_pagination/lib/pagination_helper.rb +135 -0
  112. data/vendor/plugins/classic_pagination/test/fixtures/companies.yml +24 -0
  113. data/vendor/plugins/classic_pagination/test/fixtures/company.rb +9 -0
  114. data/vendor/plugins/classic_pagination/test/fixtures/developer.rb +7 -0
  115. data/vendor/plugins/classic_pagination/test/fixtures/developers.yml +21 -0
  116. data/vendor/plugins/classic_pagination/test/fixtures/developers_projects.yml +13 -0
  117. data/vendor/plugins/classic_pagination/test/fixtures/project.rb +3 -0
  118. data/vendor/plugins/classic_pagination/test/fixtures/projects.yml +7 -0
  119. data/vendor/plugins/classic_pagination/test/fixtures/replies.yml +13 -0
  120. data/vendor/plugins/classic_pagination/test/fixtures/reply.rb +5 -0
  121. data/vendor/plugins/classic_pagination/test/fixtures/schema.sql +42 -0
  122. data/vendor/plugins/classic_pagination/test/fixtures/topic.rb +3 -0
  123. data/vendor/plugins/classic_pagination/test/fixtures/topics.yml +22 -0
  124. data/vendor/plugins/classic_pagination/test/helper.rb +117 -0
  125. data/vendor/plugins/classic_pagination/test/pagination_helper_test.rb +38 -0
  126. data/vendor/plugins/classic_pagination/test/pagination_test.rb +177 -0
  127. data/vendor/plugins/file_column/lib/file_column.rb +1 -1
  128. data/vendor/plugins/file_column/test/file_column_test.rb +0 -0
  129. metadata +151 -197
  130. data/app/helpers/live_tree.rb +0 -238
  131. data/app/views/elt/_form.rhtml +0 -31
  132. data/app/views/elt/show_tree.rhtml +0 -8
  133. data/config/environments/user_environment.rb +0 -1
  134. data/db/ROOT/Titemagli.txt +0 -3
  135. data/db/ROOT/titemagli.txt +0 -9
  136. data/public/javascripts/behaviour.js +0 -254
  137. data/public/javascripts/ie7-load.htc +0 -1
  138. data/public/javascripts/ie7.js +0 -6
  139. data/public/javascripts/live_tree.js +0 -749
  140. data/public/javascripts/mybehaviour.js +0 -225
  141. data/public/javascripts/scriptaculous.js +0 -47
  142. data/public/javascripts/slider.js +0 -283
  143. data/public/stylesheets/blue.css +0 -471
  144. data/vendor/plugins/engines/CHANGELOG +0 -241
  145. data/vendor/plugins/engines/MIT-LICENSE +0 -21
  146. data/vendor/plugins/engines/README +0 -64
  147. data/vendor/plugins/engines/Rakefile +0 -32
  148. data/vendor/plugins/engines/UPGRADING +0 -93
  149. data/vendor/plugins/engines/about.yml +0 -7
  150. data/vendor/plugins/engines/generators/plugin_migration/USAGE +0 -45
  151. data/vendor/plugins/engines/generators/plugin_migration/plugin_migration_generator.rb +0 -79
  152. data/vendor/plugins/engines/generators/plugin_migration/templates/plugin_migration.erb +0 -13
  153. data/vendor/plugins/engines/init.rb +0 -40
  154. data/vendor/plugins/engines/install.rb +0 -32
  155. data/vendor/plugins/engines/lib/engines.rb +0 -323
  156. data/vendor/plugins/engines/lib/engines/deprecated_config_support.rb +0 -135
  157. data/vendor/plugins/engines/lib/engines/plugin.rb +0 -214
  158. data/vendor/plugins/engines/lib/engines/plugin_list.rb +0 -31
  159. data/vendor/plugins/engines/lib/engines/plugin_migrator.rb +0 -60
  160. data/vendor/plugins/engines/lib/engines/rails_extensions.rb +0 -6
  161. data/vendor/plugins/engines/lib/engines/rails_extensions/active_record.rb +0 -19
  162. data/vendor/plugins/engines/lib/engines/rails_extensions/dependencies.rb +0 -143
  163. data/vendor/plugins/engines/lib/engines/rails_extensions/migrations.rb +0 -155
  164. data/vendor/plugins/engines/lib/engines/rails_extensions/public_asset_helpers.rb +0 -116
  165. data/vendor/plugins/engines/lib/engines/rails_extensions/rails.rb +0 -20
  166. data/vendor/plugins/engines/lib/engines/rails_extensions/rails_initializer.rb +0 -86
  167. data/vendor/plugins/engines/lib/engines/rails_extensions/routing.rb +0 -77
  168. data/vendor/plugins/engines/lib/engines/rails_extensions/templates.rb +0 -140
  169. data/vendor/plugins/engines/lib/engines/testing.rb +0 -87
  170. data/vendor/plugins/engines/tasks/engines.rake +0 -149
  171. data/vendor/plugins/login_engine/CHANGELOG +0 -22
  172. data/vendor/plugins/login_engine/README +0 -344
  173. data/vendor/plugins/login_engine/app/controllers/user_controller.rb +0 -262
  174. data/vendor/plugins/login_engine/app/helpers/user_helper.rb +0 -88
  175. data/vendor/plugins/login_engine/app/models/user.rb +0 -7
  176. data/vendor/plugins/login_engine/app/models/user_notify.rb +0 -75
  177. data/vendor/plugins/login_engine/app/views/user/_edit.rhtml +0 -11
  178. data/vendor/plugins/login_engine/app/views/user/_password.rhtml +0 -9
  179. data/vendor/plugins/login_engine/app/views/user/change_password.rhtml +0 -17
  180. data/vendor/plugins/login_engine/app/views/user/edit.rhtml +0 -23
  181. data/vendor/plugins/login_engine/app/views/user/forgot_password.rhtml +0 -18
  182. data/vendor/plugins/login_engine/app/views/user/home.rhtml +0 -7
  183. data/vendor/plugins/login_engine/app/views/user/login.rhtml +0 -17
  184. data/vendor/plugins/login_engine/app/views/user/logout.rhtml +0 -8
  185. data/vendor/plugins/login_engine/app/views/user/signup.rhtml +0 -17
  186. data/vendor/plugins/login_engine/db/migrate/001_initial_schema.rb +0 -25
  187. data/vendor/plugins/login_engine/init_engine.rb +0 -11
  188. data/vendor/plugins/login_engine/install.rb +0 -4
  189. data/vendor/plugins/login_engine/lib/login_engine.rb +0 -62
  190. data/vendor/plugins/login_engine/lib/login_engine/authenticated_system.rb +0 -113
  191. data/vendor/plugins/login_engine/lib/login_engine/authenticated_user.rb +0 -155
  192. data/vendor/plugins/login_engine/public/stylesheets/login_engine.css +0 -81
  193. data/vendor/plugins/login_engine/test/fixtures/users.yml +0 -41
  194. data/vendor/plugins/login_engine/test/functional/user_controller_test.rb +0 -536
  195. data/vendor/plugins/login_engine/test/mocks/mail.rb +0 -14
  196. data/vendor/plugins/login_engine/test/mocks/time.rb +0 -19
  197. data/vendor/plugins/login_engine/test/test_helper.rb +0 -11
  198. data/vendor/plugins/login_engine/test/unit/user_test.rb +0 -114
  199. data/vendor/plugins/output_compression/MIT-LICENSE +0 -20
  200. data/vendor/plugins/output_compression/README +0 -4
  201. data/vendor/plugins/output_compression/init.rb +0 -1
  202. data/vendor/plugins/output_compression/lib/output_compression.rb +0 -84
  203. data/vendor/plugins/output_compression/test/output_test.rb +0 -11
  204. data/vendor/plugins/output_compression/test/test_controller.rb +0 -3
  205. data/vendor/plugins/output_compression/test/test_helper.rb +0 -14
@@ -0,0 +1,2 @@
1
+ require 'active_record/schema_dumper'
2
+ require 'active_record_extensions'
@@ -0,0 +1,24 @@
1
+ module ActiveRecord
2
+ class SchemaDumper
3
+ private
4
+ alias old_table_text_id table
5
+
6
+ def table(table, stream)
7
+ text_id = false
8
+ @connection.columns(table).each { |c|
9
+ if c.name == 'id' and c.type.to_s == 'text'
10
+ text_id = true
11
+ end
12
+ }
13
+
14
+ old_table_text_id(table, stream)
15
+
16
+ if text_id
17
+ stream.puts " remove_column :#{table}, :id"
18
+ stream.puts " add_column :#{table}, :id, :text, :null => false, :primary_key => true"
19
+ stream.puts " execute 'ALTER TABLE #{table} ADD CONSTRAINT #{table}_pkey PRIMARY KEY (id)'"
20
+ stream.puts
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ ActsAsNestedSet
2
+ ==============
3
+
4
+ This acts_as extension provides Nested Set functionality. Nested Set is similiar to Tree, but with the added feature that you can select the children and all of their descendents with a single query. A good use case for this is a threaded post system, where you want to display every reply to a comment without multiple selects.
5
+
6
+
7
+ Example
8
+ =======
9
+
10
+ class Product < ActiveRecord::Base
11
+ acts_as_nested_set
12
+ end
13
+
14
+
15
+ Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
@@ -0,0 +1 @@
1
+ ActiveRecord::Base.send :include, ActiveRecord::Acts::NestedSet
@@ -0,0 +1,210 @@
1
+ module ActiveRecord
2
+ module Acts #:nodoc:
3
+ module NestedSet #:nodoc:
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ # This +acts_as+ extension provides Nested Set functionality. Nested Set is similiar to Tree, but with
9
+ # the added feature that you can select the children and all of their descendents with
10
+ # a single query. A good use case for this is a threaded post system, where you want
11
+ # to display every reply to a comment without multiple selects.
12
+ #
13
+ # A Google search for "Nested Set" should point you to in the right direction to explain the
14
+ # database theory. I figured out a bunch of this from
15
+ # http://threebit.net/tutorials/nestedset/tutorial1.html
16
+ #
17
+ # Instead of picturing a leaf node structure with children pointing back to their parent,
18
+ # the best way to imagine how this works is to think of the parent entity surrounding all
19
+ # of its children, and its parent surrounding it, etc. Assuming that they are lined up
20
+ # horizontally, we store the left and right boundries in the database.
21
+ #
22
+ # Imagine:
23
+ # root
24
+ # |_ Child 1
25
+ # |_ Child 1.1
26
+ # |_ Child 1.2
27
+ # |_ Child 2
28
+ # |_ Child 2.1
29
+ # |_ Child 2.2
30
+ #
31
+ # If my cirlces in circles description didn't make sense, check out this sweet
32
+ # ASCII art:
33
+ #
34
+ # ___________________________________________________________________
35
+ # | Root |
36
+ # | ____________________________ ____________________________ |
37
+ # | | Child 1 | | Child 2 | |
38
+ # | | __________ _________ | | __________ _________ | |
39
+ # | | | C 1.1 | | C 1.2 | | | | C 2.1 | | C 2.2 | | |
40
+ # 1 2 3_________4 5________6 7 8 9_________10 11_______12 13 14
41
+ # | |___________________________| |___________________________| |
42
+ # |___________________________________________________________________|
43
+ #
44
+ # The numbers represent the left and right boundries. The table then might
45
+ # look like this:
46
+ # ID | PARENT | LEFT | RIGHT | DATA
47
+ # 1 | 0 | 1 | 14 | root
48
+ # 2 | 1 | 2 | 7 | Child 1
49
+ # 3 | 2 | 3 | 4 | Child 1.1
50
+ # 4 | 2 | 5 | 6 | Child 1.2
51
+ # 5 | 1 | 8 | 13 | Child 2
52
+ # 6 | 5 | 9 | 10 | Child 2.1
53
+ # 7 | 5 | 11 | 12 | Child 2.2
54
+ #
55
+ # So, to get all children of an entry, you
56
+ # SELECT * WHERE CHILD.LEFT IS BETWEEN PARENT.LEFT AND PARENT.RIGHT
57
+ #
58
+ # To get the count, it's <tt>(LEFT - RIGHT + 1)/2</tt>, etc.
59
+ #
60
+ # To get the direct parent, it falls back to using the +PARENT_ID+ field.
61
+ #
62
+ # There are instance methods for all of these.
63
+ #
64
+ # The structure is good if you need to group things together; the downside is that
65
+ # keeping data integrity is a pain, and both adding and removing an entry
66
+ # require a full table write.
67
+ #
68
+ # This sets up a +before_destroy+ callback to prune the tree correctly if one of its
69
+ # elements gets deleted.
70
+ #
71
+ module ClassMethods
72
+ # Configuration options are:
73
+ #
74
+ # * +parent_column+ - specifies the column name to use for keeping the position integer (default: +parent_id+)
75
+ # * +left_column+ - column name for left boundry data, default +lft+
76
+ # * +right_column+ - column name for right boundry data, default +rgt+
77
+ # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
78
+ # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
79
+ # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
80
+ # Example: <tt>acts_as_nested_set :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
81
+ def acts_as_nested_set(options = {})
82
+ configuration = { :parent_column => "parent_id", :left_column => "lft", :right_column => "rgt", :scope => "1 = 1" }
83
+
84
+ configuration.update(options) if options.is_a?(Hash)
85
+
86
+ configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
87
+
88
+ if configuration[:scope].is_a?(Symbol)
89
+ scope_condition_method = %(
90
+ def scope_condition
91
+ if #{configuration[:scope].to_s}.nil?
92
+ "#{configuration[:scope].to_s} IS NULL"
93
+ else
94
+ "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
95
+ end
96
+ end
97
+ )
98
+ else
99
+ scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
100
+ end
101
+
102
+ class_eval <<-EOV
103
+ include ActiveRecord::Acts::NestedSet::InstanceMethods
104
+
105
+ #{scope_condition_method}
106
+
107
+ def left_col_name() "#{configuration[:left_column]}" end
108
+
109
+ def right_col_name() "#{configuration[:right_column]}" end
110
+
111
+ def parent_column() "#{configuration[:parent_column]}" end
112
+
113
+ EOV
114
+ end
115
+ end
116
+
117
+ module InstanceMethods
118
+ # Returns +true+ is this is a root node.
119
+ def root?
120
+ parent_id = self[parent_column]
121
+ (parent_id == 0 || parent_id.nil?) && (self[left_col_name] == 1) && (self[right_col_name] > self[left_col_name])
122
+ end
123
+
124
+ # Returns +true+ is this is a child node
125
+ def child?
126
+ parent_id = self[parent_column]
127
+ !(parent_id == 0 || parent_id.nil?) && (self[left_col_name] > 1) && (self[right_col_name] > self[left_col_name])
128
+ end
129
+
130
+ # Returns +true+ if we have no idea what this is
131
+ def unknown?
132
+ !root? && !child?
133
+ end
134
+
135
+ # Adds a child to this object in the tree. If this object hasn't been initialized,
136
+ # it gets set up as a root node. Otherwise, this method will update all of the
137
+ # other elements in the tree and shift them to the right, keeping everything
138
+ # balanced.
139
+ def add_child( child )
140
+ self.reload
141
+ child.reload
142
+
143
+ if child.root?
144
+ raise "Adding sub-tree isn\'t currently supported"
145
+ else
146
+ if ( (self[left_col_name] == nil) || (self[right_col_name] == nil) )
147
+ # Looks like we're now the root node! Woo
148
+ self[left_col_name] = 1
149
+ self[right_col_name] = 4
150
+
151
+ # What do to do about validation?
152
+ return nil unless self.save
153
+
154
+ child[parent_column] = self.id
155
+ child[left_col_name] = 2
156
+ child[right_col_name]= 3
157
+ return child.save
158
+ else
159
+ # OK, we need to add and shift everything else to the right
160
+ child[parent_column] = self.id
161
+ right_bound = self[right_col_name]
162
+ child[left_col_name] = right_bound
163
+ child[right_col_name] = right_bound + 1
164
+ self[right_col_name] += 2
165
+ self.class.base_class.transaction {
166
+ self.class.base_class.update_all( "#{left_col_name} = (#{left_col_name} + 2)", "#{scope_condition} AND #{left_col_name} >= #{right_bound}" )
167
+ self.class.base_class.update_all( "#{right_col_name} = (#{right_col_name} + 2)", "#{scope_condition} AND #{right_col_name} >= #{right_bound}" )
168
+ self.save
169
+ child.save
170
+ }
171
+ end
172
+ end
173
+ end
174
+
175
+ # Returns the number of nested children of this object.
176
+ def children_count
177
+ return (self[right_col_name] - self[left_col_name] - 1)/2
178
+ end
179
+
180
+ # Returns a set of itself and all of its nested children
181
+ def full_set
182
+ self.class.base_class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} and #{self[right_col_name]})" )
183
+ end
184
+
185
+ # Returns a set of all of its children and nested children
186
+ def all_children
187
+ self.class.base_class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} > #{self[left_col_name]}) and (#{right_col_name} < #{self[right_col_name]})" )
188
+ end
189
+
190
+ # Returns a set of only this entry's immediate children
191
+ def direct_children
192
+ self.class.base_class.find(:all, :conditions => "#{scope_condition} and #{parent_column} = #{self.id}", :order => left_col_name)
193
+ end
194
+
195
+ # Prunes a branch off of the tree, shifting all of the elements on the right
196
+ # back to the left so the counts still work.
197
+ def before_destroy
198
+ return if self[right_col_name].nil? || self[left_col_name].nil?
199
+ dif = self[right_col_name] - self[left_col_name] + 1
200
+
201
+ self.class.base_class.transaction {
202
+ self.class.base_class.delete_all( "#{scope_condition} and #{left_col_name} > #{self[left_col_name]} and #{right_col_name} < #{self[right_col_name]}" )
203
+ self.class.base_class.update_all( "#{left_col_name} = (#{left_col_name} - #{dif})", "#{scope_condition} AND #{left_col_name} >= #{self[right_col_name]}" )
204
+ self.class.base_class.update_all( "#{right_col_name} = (#{right_col_name} - #{dif} )", "#{scope_condition} AND #{right_col_name} >= #{self[right_col_name]}" )
205
+ }
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,269 @@
1
+ require 'test/unit'
2
+
3
+ require 'rubygems'
4
+ require 'active_record'
5
+
6
+ $:.unshift File.dirname(__FILE__) + '/../lib'
7
+ require File.dirname(__FILE__) + '/../init'
8
+
9
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
10
+
11
+ class Mixin < ActiveRecord::Base
12
+ end
13
+
14
+ class NestedSet < Mixin
15
+ acts_as_nested_set :scope => "root_id IS NULL"
16
+
17
+ def self.table_name() "mixins" end
18
+ end
19
+
20
+ class NestedSetWithStringScope < Mixin
21
+ acts_as_nested_set :scope => 'root_id = #{root_id}'
22
+
23
+ def self.table_name() "mixins" end
24
+ end
25
+
26
+ class NestedSetWithSymbolScope < Mixin
27
+ acts_as_nested_set :scope => :root
28
+
29
+ def self.table_name() "mixins" end
30
+ end
31
+
32
+ class NestedSetSuperclass < Mixin
33
+ acts_as_nested_set :scope => :root
34
+
35
+ def self.table_name() "mixins" end
36
+ end
37
+
38
+ class NestedSetSubclass < NestedSetSuperclass
39
+ end
40
+
41
+
42
+ class MixinNestedSetTest < Test::Unit::TestCase
43
+
44
+ def setup
45
+ ActiveRecord::Schema.define(:version => 1) do
46
+ create_table :mixins do |t|
47
+ t.integer :pos, :parent_id, :lft, :rgt, :root_id
48
+ t.timestamps
49
+ end
50
+ end
51
+
52
+ (1..10).each { |counter| NestedSet.create! }
53
+
54
+ [ [0, 1, 10, NestedSetSuperclass],
55
+ [11, 2, 5, NestedSetSubclass],
56
+ [12, 3, 4, NestedSetSuperclass],
57
+ [11, 6, 9, NestedSetSuperclass],
58
+ [13, 7, 8, NestedSetSubclass]
59
+ ].each do |sti|
60
+ sti[3].create! :parent_id => sti[0], :lft => sti[1], :rgt => sti[2], :root_id => 3100
61
+ end
62
+
63
+ [ [0, 1, 20],
64
+ [16, 2, 7],
65
+ [17, 3, 4],
66
+ [17, 5, 6],
67
+ [16, 14, 13],
68
+ [20, 9, 10],
69
+ [20, 11, 12],
70
+ [16, 8, 19],
71
+ [23, 15, 16],
72
+ [23, 17, 18]
73
+ ].each do |set|
74
+ NestedSetWithStringScope.create! :parent_id => set[0], :lft => set[1], :rgt => set[2], :root_id => 42
75
+ end
76
+ end
77
+
78
+ def teardown
79
+ ActiveRecord::Base.connection.tables.each do |table|
80
+ ActiveRecord::Base.connection.drop_table(table)
81
+ end
82
+ end
83
+
84
+ def test_mixing_in_methods
85
+ ns = NestedSet.new
86
+ assert(ns.respond_to?(:all_children))
87
+ assert_equal(ns.scope_condition, "root_id IS NULL")
88
+ check_method_mixins ns
89
+ end
90
+
91
+ def test_string_scope
92
+ ns = NestedSetWithStringScope.new
93
+ ns.root_id = 1
94
+ assert_equal(ns.scope_condition, "root_id = 1")
95
+ ns.root_id = 42
96
+ assert_equal(ns.scope_condition, "root_id = 42")
97
+ check_method_mixins ns
98
+ end
99
+
100
+ def test_symbol_scope
101
+ ns = NestedSetWithSymbolScope.new
102
+ ns.root_id = 1
103
+ assert_equal(ns.scope_condition, "root_id = 1")
104
+ ns.root_id = 42
105
+ assert_equal(ns.scope_condition, "root_id = 42")
106
+ check_method_mixins ns
107
+ end
108
+
109
+ def check_method_mixins(obj)
110
+ [:scope_condition, :left_col_name, :right_col_name, :parent_column, :root?, :add_child,
111
+ :children_count, :full_set, :all_children, :direct_children].each { |symbol| assert(obj.respond_to?(symbol)) }
112
+ end
113
+
114
+ def set(id)
115
+ NestedSet.find(id)
116
+ end
117
+
118
+ def test_adding_children
119
+ assert(set(1).unknown?)
120
+ assert(set(2).unknown?)
121
+ set(1).add_child set(2)
122
+
123
+ # Did we maintain adding the parent_ids?
124
+ assert(set(1).root?)
125
+ assert(set(2).child?)
126
+ assert(set(2).parent_id == set(1).id)
127
+
128
+ # Check boundies
129
+ assert_equal(set(1).lft, 1)
130
+ assert_equal(set(2).lft, 2)
131
+ assert_equal(set(2).rgt, 3)
132
+ assert_equal(set(1).rgt, 4)
133
+
134
+ # Check children cound
135
+ assert_equal(set(1).children_count, 1)
136
+
137
+ set(1).add_child set(3)
138
+
139
+ #check boundries
140
+ assert_equal(set(1).lft, 1)
141
+ assert_equal(set(2).lft, 2)
142
+ assert_equal(set(2).rgt, 3)
143
+ assert_equal(set(3).lft, 4)
144
+ assert_equal(set(3).rgt, 5)
145
+ assert_equal(set(1).rgt, 6)
146
+
147
+ # How is the count looking?
148
+ assert_equal(set(1).children_count, 2)
149
+
150
+ set(2).add_child set(4)
151
+
152
+ # boundries
153
+ assert_equal(set(1).lft, 1)
154
+ assert_equal(set(2).lft, 2)
155
+ assert_equal(set(4).lft, 3)
156
+ assert_equal(set(4).rgt, 4)
157
+ assert_equal(set(2).rgt, 5)
158
+ assert_equal(set(3).lft, 6)
159
+ assert_equal(set(3).rgt, 7)
160
+ assert_equal(set(1).rgt, 8)
161
+
162
+ # Children count
163
+ assert_equal(set(1).children_count, 3)
164
+ assert_equal(set(2).children_count, 1)
165
+ assert_equal(set(3).children_count, 0)
166
+ assert_equal(set(4).children_count, 0)
167
+
168
+ set(2).add_child set(5)
169
+ set(4).add_child set(6)
170
+
171
+ assert_equal(set(2).children_count, 3)
172
+
173
+
174
+ # Children accessors
175
+ assert_equal(set(1).full_set.length, 6)
176
+ assert_equal(set(2).full_set.length, 4)
177
+ assert_equal(set(4).full_set.length, 2)
178
+
179
+ assert_equal(set(1).all_children.length, 5)
180
+ assert_equal(set(6).all_children.length, 0)
181
+
182
+ assert_equal(set(1).direct_children.length, 2)
183
+
184
+ end
185
+
186
+ def test_snipping_tree
187
+ big_tree = NestedSetWithStringScope.find(16)
188
+
189
+ # Make sure we have the right one
190
+ assert_equal(3, big_tree.direct_children.length)
191
+ assert_equal(10, big_tree.full_set.length)
192
+ assert_equal [17, 23, 20], big_tree.direct_children.map(&:id)
193
+
194
+ NestedSetWithStringScope.find(20).destroy
195
+
196
+ big_tree = NestedSetWithStringScope.find(16)
197
+
198
+ assert_equal(9, big_tree.full_set.length)
199
+ assert_equal(2, big_tree.direct_children.length)
200
+
201
+ assert_equal(1, NestedSetWithStringScope.find(16).lft)
202
+ assert_equal(2, NestedSetWithStringScope.find(17).lft)
203
+ assert_equal(3, NestedSetWithStringScope.find(18).lft)
204
+ assert_equal(4, NestedSetWithStringScope.find(18).rgt)
205
+ assert_equal(5, NestedSetWithStringScope.find(19).lft)
206
+ assert_equal(6, NestedSetWithStringScope.find(19).rgt)
207
+ assert_equal(7, NestedSetWithStringScope.find(17).rgt)
208
+ assert_equal(8, NestedSetWithStringScope.find(23).lft)
209
+ assert_equal(15, NestedSetWithStringScope.find(24).lft)
210
+ assert_equal(16, NestedSetWithStringScope.find(24).rgt)
211
+ assert_equal(17, NestedSetWithStringScope.find(25).lft)
212
+ assert_equal(18, NestedSetWithStringScope.find(25).rgt)
213
+ assert_equal(19, NestedSetWithStringScope.find(23).rgt)
214
+ assert_equal(20, NestedSetWithStringScope.find(16).rgt)
215
+ end
216
+
217
+ def test_deleting_root
218
+ NestedSetWithStringScope.find(16).destroy
219
+ assert_equal 15, NestedSetWithStringScope.count
220
+ end
221
+
222
+ def test_common_usage
223
+ NestedSet.find(1).add_child(NestedSet.find(2))
224
+ assert_equal(1, NestedSet.find(1).direct_children.length)
225
+
226
+ NestedSet.find(2).add_child(NestedSet.find(3))
227
+ assert_equal(1, NestedSet.find(1).direct_children.length)
228
+
229
+ # Local cache is now out of date!
230
+ # Problem: the update_alls update all objects up the tree
231
+ assert_equal(2, NestedSet.find(1).all_children.length)
232
+
233
+ assert_equal(1, NestedSet.find(1).lft)
234
+ assert_equal(2, NestedSet.find(2).lft)
235
+ assert_equal(3, NestedSet.find(3).lft)
236
+ assert_equal(4, NestedSet.find(3).rgt)
237
+ assert_equal(5, NestedSet.find(2).rgt)
238
+ assert_equal(6, NestedSet.find(1).rgt)
239
+
240
+ assert(NestedSet.find(1).root?)
241
+
242
+ begin
243
+ NestedSet.find(4).add_child(NestedSet.find(1))
244
+ fail
245
+ rescue
246
+ end
247
+
248
+ assert_equal(2, NestedSet.find(1).all_children.length)
249
+
250
+ NestedSet.find(1).add_child NestedSet.find(4)
251
+
252
+ assert_equal(3, NestedSet.find(1).all_children.length)
253
+ end
254
+
255
+ def test_inheritance
256
+ parent = NestedSetWithStringScope.find(11)
257
+ child = NestedSetWithStringScope.find(12)
258
+ grandchild = NestedSetWithStringScope.find(13)
259
+ assert_equal 5, parent.full_set.size
260
+ assert_equal 2, child.full_set.size
261
+ assert_equal 4, parent.all_children.size
262
+ assert_equal 1, child.all_children.size
263
+ assert_equal 2, parent.direct_children.size
264
+ assert_equal 1, child.direct_children.size
265
+ child.destroy
266
+ assert_equal 3, parent.full_set.size
267
+ end
268
+
269
+ end