searchlogic 1.5.10 → 1.6.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.
data/CHANGELOG.rdoc CHANGED
@@ -1,4 +1,10 @@
1
- == 1.5.10 released 2008-12-10
1
+ == 1.6.0 released 2008-12-8
2
+
3
+ * Converted all tree conditions to nested set conditions for performance reasons. A nested set data structure is now required instead of a tree
4
+ * Added DISTINCT to select if you are not provided a select, searching, and have joins.
5
+ * Included :not_like in the list of conditions to be required upon initialization.
6
+
7
+ == 1.5.10 released 2008-12-8
2
8
 
3
9
  * Create class level conditions upon instantiation to avoid conflicts.
4
10
 
data/Manifest CHANGED
@@ -19,6 +19,7 @@ lib/searchlogic/condition/keywords.rb
19
19
  lib/searchlogic/condition/less_than.rb
20
20
  lib/searchlogic/condition/less_than_or_equal_to.rb
21
21
  lib/searchlogic/condition/like.rb
22
+ lib/searchlogic/condition/nested_set.rb
22
23
  lib/searchlogic/condition/nil.rb
23
24
  lib/searchlogic/condition/not_begin_with.rb
24
25
  lib/searchlogic/condition/not_blank.rb
@@ -28,7 +29,6 @@ lib/searchlogic/condition/not_have_keywords.rb
28
29
  lib/searchlogic/condition/not_like.rb
29
30
  lib/searchlogic/condition/not_nil.rb
30
31
  lib/searchlogic/condition/sibling_of.rb
31
- lib/searchlogic/condition/tree.rb
32
32
  lib/searchlogic/conditions/any_or_all.rb
33
33
  lib/searchlogic/conditions/base.rb
34
34
  lib/searchlogic/conditions/groups.rb
@@ -142,7 +142,10 @@ test/fixtures/animals.yml
142
142
  test/fixtures/orders.yml
143
143
  test/fixtures/user_groups.yml
144
144
  test/fixtures/users.yml
145
- test/libs/acts_as_tree.rb
145
+ test/libs/awesome_nested_set/compatability.rb
146
+ test/libs/awesome_nested_set/helper.rb
147
+ test/libs/awesome_nested_set/named_scope.rb
148
+ test/libs/awesome_nested_set.rb
146
149
  test/libs/rexml_fix.rb
147
150
  test/modifier_tests/day_of_month_test.rb
148
151
  test/search_tests/base_test.rb
data/README.rdoc CHANGED
@@ -347,6 +347,8 @@ Each model comes preloaded with class level conditions as well. The difference i
347
347
  :inclusive_descendant_of Same as above but also includes the root
348
348
  :sibling_of Returns all records that have the same parent
349
349
 
350
+ The above conditions are for a nested set data structure, *not* a tree. Since we need to traverse through the tree a nested set is required for performance reasons. Traversing through a large tree is very bad for performance, a nested set solves this problem.
351
+
350
352
  == Modifiers
351
353
 
352
354
  === What are modifiers?
data/lib/searchlogic.rb CHANGED
@@ -48,13 +48,15 @@ require "searchlogic/conditions/base"
48
48
 
49
49
  # Condition
50
50
  require "searchlogic/condition/base"
51
- require "searchlogic/condition/tree"
52
- SEARCHLOGIC_CONDITIONS = [:begins_with, :blank, :child_of, :descendant_of, :ends_with, :equals, :greater_than, :greater_than_or_equal_to, :inclusive_descendant_of, :like, :nil, :not_begin_with, :not_blank, :not_end_with, :not_equal, :not_have_keywords, :not_nil, :keywords, :less_than, :less_than_or_equal_to, :sibling_of]
51
+ require "searchlogic/condition/nested_set"
52
+ SEARCHLOGIC_CONDITIONS = [:begins_with, :blank, :child_of, :descendant_of, :ends_with, :equals, :greater_than, :greater_than_or_equal_to, :inclusive_descendant_of, :like, :nil, :not_begin_with,
53
+ :not_blank, :not_end_with, :not_equal, :not_have_keywords, :not_like, :not_nil, :keywords, :less_than, :less_than_or_equal_to, :sibling_of]
53
54
  SEARCHLOGIC_CONDITIONS.each { |condition| require "searchlogic/condition/#{condition}" }
54
55
 
55
56
  # Modifiers
56
57
  require "searchlogic/modifiers/base"
57
- SEARCHLOGIC_MODIFIERS = [:absolute, :acos, :asin, :atan, :avg, :ceil, :char_length, :cos, :cot, :count, :day_of_month, :day_of_week, :day_of_year, :degrees, :exp, :floor, :hex, :hour, :log, :log10, :log2, :lower, :ltrim, :md5, :microseconds, :milliseconds, :minute, :month, :octal, :radians, :round, :rtrim, :second, :sign, :sin, :square_root, :sum, :tan, :trim, :upper, :week, :year]
58
+ SEARCHLOGIC_MODIFIERS = [:absolute, :acos, :asin, :atan, :avg, :ceil, :char_length, :cos, :cot, :count, :day_of_month, :day_of_week, :day_of_year, :degrees, :exp, :floor, :hex, :hour, :log, :log10,
59
+ :log2, :lower, :ltrim, :md5, :microseconds, :milliseconds, :minute, :month, :octal, :radians, :round, :rtrim, :second, :sign, :sin, :square_root, :sum, :tan, :trim, :upper, :week, :year]
58
60
  SEARCHLOGIC_MODIFIERS.each { |modifier| require "searchlogic/modifiers/#{modifier}" }
59
61
 
60
62
  # Helpers
@@ -121,8 +121,7 @@ module Searchlogic
121
121
  def meaningless?(v)
122
122
  case v
123
123
  when Array
124
- v.each { |i| return false unless meaningless?(i) }
125
- true
124
+ false
126
125
  else
127
126
  !explicitly_set_value? || (self.class.ignore_meaningless_value? && v != false && v.blank?)
128
127
  end
@@ -1,6 +1,6 @@
1
1
  module Searchlogic
2
2
  module Condition
3
- class ChildOf < Tree
3
+ class ChildOf < NestedSet
4
4
  def to_conditions(value)
5
5
  parent_association = klass.reflect_on_association(:parent)
6
6
  foreign_key_name = (parent_association && parent_association.options[:foreign_key]) || "parent_id"
@@ -1,24 +1,11 @@
1
1
  module Searchlogic
2
2
  module Condition
3
- class DescendantOf < Tree
3
+ class DescendantOf < NestedSet
4
4
  def to_conditions(value)
5
- # Wish I knew how to do this in SQL
6
- root = (value.is_a?(klass) ? value : klass.find(value)) rescue return
7
- strs = []
8
- subs = []
9
- all_children_ids(root).each do |child_id|
10
- strs << "#{quoted_table_name}.#{quote_column_name(klass.primary_key)} = ?"
11
- subs << child_id
12
- end
13
- [strs.join(" OR "), *subs]
5
+ condition = InclusiveDescendantOf.new(klass, options)
6
+ condition.value = value
7
+ merge_conditions(["#{quoted_table_name}.#{quote_column_name(klass.primary_key)} != ?", (value.is_a?(klass) ? value.send(klass.primary_key) : value)], condition.sanitize)
14
8
  end
15
-
16
- private
17
- def all_children_ids(record)
18
- ids = record.children.collect { |child| child.send(klass.primary_key) }
19
- record.children.each { |child| ids += all_children_ids(child) }
20
- ids
21
- end
22
9
  end
23
10
  end
24
11
  end
@@ -1,10 +1,9 @@
1
1
  module Searchlogic
2
2
  module Condition
3
- class InclusiveDescendantOf < Tree
3
+ class InclusiveDescendantOf < NestedSet
4
4
  def to_conditions(value)
5
- condition = DescendantOf.new(klass, options)
6
- condition.value = value
7
- merge_conditions(["#{quoted_table_name}.#{quote_column_name(klass.primary_key)} = ?", (value.is_a?(klass) ? value.send(klass.primary_key) : value)], condition.sanitize, :any => true)
5
+ root = (value.is_a?(klass) ? value : klass.find(value)) rescue return
6
+ ["#{quoted_table_name}.#{quote_column_name(klass.left_column_name)} >= ? AND #{quoted_table_name}.#{quote_column_name(klass.right_column_name)} <= ?", root.left, root.right]
8
7
  end
9
8
  end
10
9
  end
@@ -1,6 +1,6 @@
1
1
  module Searchlogic
2
2
  module Condition
3
- class Tree < Base # :nodoc:
3
+ class NestedSet < Base # :nodoc:
4
4
  self.join_arrays_with_or = true
5
5
 
6
6
  class << self
@@ -8,7 +8,7 @@ module Searchlogic
8
8
  end
9
9
 
10
10
  def to_conditions(value)
11
- begin_with = BeginWith.new(klass, options)
11
+ begin_with = BeginsWith.new(klass, options)
12
12
  begin_with.value = value
13
13
  conditions = being_with.sanitize
14
14
  return conditions if conditions.blank?
@@ -1,6 +1,6 @@
1
1
  module Searchlogic
2
2
  module Condition
3
- class SiblingOf < Tree
3
+ class SiblingOf < NestedSet
4
4
  def to_conditions(value)
5
5
  parent_association = klass.reflect_on_association(:parent)
6
6
  foreign_key_name = (parent_association && parent_association.options[:foreign_key]) || "parent_id"
@@ -91,7 +91,7 @@ module Searchlogic
91
91
  class_level_conditions.each do |condition_class|
92
92
  condition_class.condition_names_for_model.each_with_index do |condition_name, index|
93
93
  if index == 0
94
- add_condition!(condition_class, condition_name)
94
+ add_condition!(condition_class, condition_name, :column => klass.columns_hash[klass.primary_key])
95
95
  else
96
96
  add_condition_alias!(condition_name, condition_class.condition_names_for_model.first)
97
97
  end
@@ -119,6 +119,11 @@ module Searchlogic #:nodoc:
119
119
  find_options
120
120
  end
121
121
 
122
+ def select
123
+ return @select if klass.connection.adapter_name == "PostgreSQL" # Postgres needs ALL of the column names here, including association columns, etc. Which is very strange, so I disable this feature for Postgres all together.
124
+ @select ||= "DISTINCT #{klass.connection.quote_table_name(klass.table_name)}.*" if !joins.blank? && Config.search.remove_duplicates?
125
+ end
126
+
122
127
  def scope
123
128
  @scope ||= {}
124
129
  end
@@ -22,7 +22,6 @@ module Searchlogic
22
22
  end
23
23
  args << options
24
24
  results = klass.#{method}(*args)
25
- results.uniq! if #{SEARCH_METHODS.include?(method)} && results.is_a?(Array) && !joins.blank? && Config.search.remove_duplicates?
26
25
  results
27
26
  end
28
27
  end
@@ -66,8 +66,8 @@ module Searchlogic
66
66
  end
67
67
 
68
68
  MAJOR = 1
69
- MINOR = 5
70
- TINY = 10
69
+ MINOR = 6
70
+ TINY = 0
71
71
 
72
72
  # The current version as a Version instance
73
73
  CURRENT = new(MAJOR, MINOR, TINY)
data/searchlogic.gemspec CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{searchlogic}
5
- s.version = "1.5.10"
5
+ s.version = "1.6.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Ben Johnson of Binary Logic"]
9
- s.date = %q{2008-12-07}
9
+ s.date = %q{2008-12-08}
10
10
  s.description = %q{Object based ActiveRecord searching, ordering, pagination, and more!}
11
11
  s.email = %q{bjohnson@binarylogic.com}
12
- s.extra_rdoc_files = ["CHANGELOG.rdoc", "lib/searchlogic/active_record/associations.rb", "lib/searchlogic/active_record/base.rb", "lib/searchlogic/active_record/connection_adapters/mysql_adapter.rb", "lib/searchlogic/active_record/connection_adapters/postgresql_adapter.rb", "lib/searchlogic/active_record/connection_adapters/sqlite_adapter.rb", "lib/searchlogic/condition/base.rb", "lib/searchlogic/condition/begins_with.rb", "lib/searchlogic/condition/blank.rb", "lib/searchlogic/condition/child_of.rb", "lib/searchlogic/condition/descendant_of.rb", "lib/searchlogic/condition/ends_with.rb", "lib/searchlogic/condition/equals.rb", "lib/searchlogic/condition/greater_than.rb", "lib/searchlogic/condition/greater_than_or_equal_to.rb", "lib/searchlogic/condition/inclusive_descendant_of.rb", "lib/searchlogic/condition/keywords.rb", "lib/searchlogic/condition/less_than.rb", "lib/searchlogic/condition/less_than_or_equal_to.rb", "lib/searchlogic/condition/like.rb", "lib/searchlogic/condition/nil.rb", "lib/searchlogic/condition/not_begin_with.rb", "lib/searchlogic/condition/not_blank.rb", "lib/searchlogic/condition/not_end_with.rb", "lib/searchlogic/condition/not_equal.rb", "lib/searchlogic/condition/not_have_keywords.rb", "lib/searchlogic/condition/not_like.rb", "lib/searchlogic/condition/not_nil.rb", "lib/searchlogic/condition/sibling_of.rb", "lib/searchlogic/condition/tree.rb", "lib/searchlogic/conditions/any_or_all.rb", "lib/searchlogic/conditions/base.rb", "lib/searchlogic/conditions/groups.rb", "lib/searchlogic/conditions/magic_methods.rb", "lib/searchlogic/conditions/multiparameter_attributes.rb", "lib/searchlogic/conditions/protection.rb", "lib/searchlogic/config/helpers.rb", "lib/searchlogic/config/search.rb", "lib/searchlogic/config.rb", "lib/searchlogic/core_ext/hash.rb", "lib/searchlogic/core_ext/object.rb", "lib/searchlogic/helpers/control_types/link.rb", "lib/searchlogic/helpers/control_types/links.rb", "lib/searchlogic/helpers/control_types/remote_link.rb", "lib/searchlogic/helpers/control_types/remote_links.rb", "lib/searchlogic/helpers/control_types/remote_select.rb", "lib/searchlogic/helpers/control_types/select.rb", "lib/searchlogic/helpers/form.rb", "lib/searchlogic/helpers/utilities.rb", "lib/searchlogic/modifiers/absolute.rb", "lib/searchlogic/modifiers/acos.rb", "lib/searchlogic/modifiers/asin.rb", "lib/searchlogic/modifiers/atan.rb", "lib/searchlogic/modifiers/avg.rb", "lib/searchlogic/modifiers/base.rb", "lib/searchlogic/modifiers/ceil.rb", "lib/searchlogic/modifiers/char_length.rb", "lib/searchlogic/modifiers/cos.rb", "lib/searchlogic/modifiers/cot.rb", "lib/searchlogic/modifiers/count.rb", "lib/searchlogic/modifiers/day_of_month.rb", "lib/searchlogic/modifiers/day_of_week.rb", "lib/searchlogic/modifiers/day_of_year.rb", "lib/searchlogic/modifiers/degrees.rb", "lib/searchlogic/modifiers/exp.rb", "lib/searchlogic/modifiers/floor.rb", "lib/searchlogic/modifiers/hex.rb", "lib/searchlogic/modifiers/hour.rb", "lib/searchlogic/modifiers/log.rb", "lib/searchlogic/modifiers/log10.rb", "lib/searchlogic/modifiers/log2.rb", "lib/searchlogic/modifiers/lower.rb", "lib/searchlogic/modifiers/ltrim.rb", "lib/searchlogic/modifiers/md5.rb", "lib/searchlogic/modifiers/microseconds.rb", "lib/searchlogic/modifiers/milliseconds.rb", "lib/searchlogic/modifiers/minute.rb", "lib/searchlogic/modifiers/month.rb", "lib/searchlogic/modifiers/octal.rb", "lib/searchlogic/modifiers/radians.rb", "lib/searchlogic/modifiers/round.rb", "lib/searchlogic/modifiers/rtrim.rb", "lib/searchlogic/modifiers/second.rb", "lib/searchlogic/modifiers/sign.rb", "lib/searchlogic/modifiers/sin.rb", "lib/searchlogic/modifiers/square_root.rb", "lib/searchlogic/modifiers/sum.rb", "lib/searchlogic/modifiers/tan.rb", "lib/searchlogic/modifiers/trim.rb", "lib/searchlogic/modifiers/upper.rb", "lib/searchlogic/modifiers/week.rb", "lib/searchlogic/modifiers/year.rb", "lib/searchlogic/search/base.rb", "lib/searchlogic/search/conditions.rb", "lib/searchlogic/search/ordering.rb", "lib/searchlogic/search/pagination.rb", "lib/searchlogic/search/protection.rb", "lib/searchlogic/search/searching.rb", "lib/searchlogic/shared/utilities.rb", "lib/searchlogic/shared/virtual_classes.rb", "lib/searchlogic/version.rb", "lib/searchlogic.rb", "README.rdoc", "TODO.rdoc"]
13
- s.files = ["CHANGELOG.rdoc", "init.rb", "lib/searchlogic/active_record/associations.rb", "lib/searchlogic/active_record/base.rb", "lib/searchlogic/active_record/connection_adapters/mysql_adapter.rb", "lib/searchlogic/active_record/connection_adapters/postgresql_adapter.rb", "lib/searchlogic/active_record/connection_adapters/sqlite_adapter.rb", "lib/searchlogic/condition/base.rb", "lib/searchlogic/condition/begins_with.rb", "lib/searchlogic/condition/blank.rb", "lib/searchlogic/condition/child_of.rb", "lib/searchlogic/condition/descendant_of.rb", "lib/searchlogic/condition/ends_with.rb", "lib/searchlogic/condition/equals.rb", "lib/searchlogic/condition/greater_than.rb", "lib/searchlogic/condition/greater_than_or_equal_to.rb", "lib/searchlogic/condition/inclusive_descendant_of.rb", "lib/searchlogic/condition/keywords.rb", "lib/searchlogic/condition/less_than.rb", "lib/searchlogic/condition/less_than_or_equal_to.rb", "lib/searchlogic/condition/like.rb", "lib/searchlogic/condition/nil.rb", "lib/searchlogic/condition/not_begin_with.rb", "lib/searchlogic/condition/not_blank.rb", "lib/searchlogic/condition/not_end_with.rb", "lib/searchlogic/condition/not_equal.rb", "lib/searchlogic/condition/not_have_keywords.rb", "lib/searchlogic/condition/not_like.rb", "lib/searchlogic/condition/not_nil.rb", "lib/searchlogic/condition/sibling_of.rb", "lib/searchlogic/condition/tree.rb", "lib/searchlogic/conditions/any_or_all.rb", "lib/searchlogic/conditions/base.rb", "lib/searchlogic/conditions/groups.rb", "lib/searchlogic/conditions/magic_methods.rb", "lib/searchlogic/conditions/multiparameter_attributes.rb", "lib/searchlogic/conditions/protection.rb", "lib/searchlogic/config/helpers.rb", "lib/searchlogic/config/search.rb", "lib/searchlogic/config.rb", "lib/searchlogic/core_ext/hash.rb", "lib/searchlogic/core_ext/object.rb", "lib/searchlogic/helpers/control_types/link.rb", "lib/searchlogic/helpers/control_types/links.rb", "lib/searchlogic/helpers/control_types/remote_link.rb", "lib/searchlogic/helpers/control_types/remote_links.rb", "lib/searchlogic/helpers/control_types/remote_select.rb", "lib/searchlogic/helpers/control_types/select.rb", "lib/searchlogic/helpers/form.rb", "lib/searchlogic/helpers/utilities.rb", "lib/searchlogic/modifiers/absolute.rb", "lib/searchlogic/modifiers/acos.rb", "lib/searchlogic/modifiers/asin.rb", "lib/searchlogic/modifiers/atan.rb", "lib/searchlogic/modifiers/avg.rb", "lib/searchlogic/modifiers/base.rb", "lib/searchlogic/modifiers/ceil.rb", "lib/searchlogic/modifiers/char_length.rb", "lib/searchlogic/modifiers/cos.rb", "lib/searchlogic/modifiers/cot.rb", "lib/searchlogic/modifiers/count.rb", "lib/searchlogic/modifiers/day_of_month.rb", "lib/searchlogic/modifiers/day_of_week.rb", "lib/searchlogic/modifiers/day_of_year.rb", "lib/searchlogic/modifiers/degrees.rb", "lib/searchlogic/modifiers/exp.rb", "lib/searchlogic/modifiers/floor.rb", "lib/searchlogic/modifiers/hex.rb", "lib/searchlogic/modifiers/hour.rb", "lib/searchlogic/modifiers/log.rb", "lib/searchlogic/modifiers/log10.rb", "lib/searchlogic/modifiers/log2.rb", "lib/searchlogic/modifiers/lower.rb", "lib/searchlogic/modifiers/ltrim.rb", "lib/searchlogic/modifiers/md5.rb", "lib/searchlogic/modifiers/microseconds.rb", "lib/searchlogic/modifiers/milliseconds.rb", "lib/searchlogic/modifiers/minute.rb", "lib/searchlogic/modifiers/month.rb", "lib/searchlogic/modifiers/octal.rb", "lib/searchlogic/modifiers/radians.rb", "lib/searchlogic/modifiers/round.rb", "lib/searchlogic/modifiers/rtrim.rb", "lib/searchlogic/modifiers/second.rb", "lib/searchlogic/modifiers/sign.rb", "lib/searchlogic/modifiers/sin.rb", "lib/searchlogic/modifiers/square_root.rb", "lib/searchlogic/modifiers/sum.rb", "lib/searchlogic/modifiers/tan.rb", "lib/searchlogic/modifiers/trim.rb", "lib/searchlogic/modifiers/upper.rb", "lib/searchlogic/modifiers/week.rb", "lib/searchlogic/modifiers/year.rb", "lib/searchlogic/search/base.rb", "lib/searchlogic/search/conditions.rb", "lib/searchlogic/search/ordering.rb", "lib/searchlogic/search/pagination.rb", "lib/searchlogic/search/protection.rb", "lib/searchlogic/search/searching.rb", "lib/searchlogic/shared/utilities.rb", "lib/searchlogic/shared/virtual_classes.rb", "lib/searchlogic/version.rb", "lib/searchlogic.rb", "Manifest", "MIT-LICENSE", "Rakefile", "README.rdoc", "test/active_record_tests/associations_test.rb", "test/active_record_tests/base_test.rb", "test/condition_tests/base_test.rb", "test/condition_tests/begins_with_test.rb", "test/condition_tests/blank_test.rb", "test/condition_tests/child_of_test.rb", "test/condition_tests/descendant_of_test.rb", "test/condition_tests/ends_with_test.rb", "test/condition_tests/equals_test.rb", "test/condition_tests/greater_than_or_equal_to_test.rb", "test/condition_tests/greater_than_test.rb", "test/condition_tests/inclusive_descendant_of_test.rb", "test/condition_tests/keywords_test.rb", "test/condition_tests/less_than_or_equal_to_test.rb", "test/condition_tests/less_than_test.rb", "test/condition_tests/like_test.rb", "test/condition_tests/nil_test.rb", "test/condition_tests/not_begin_with_test.rb", "test/condition_tests/not_blank_test.rb", "test/condition_tests/not_end_with_test.rb", "test/condition_tests/not_equal_test.rb", "test/condition_tests/not_have_keywords_test.rb", "test/condition_tests/not_like_test.rb", "test/condition_tests/not_nil_test.rb", "test/condition_tests/sibling_of_test.rb", "test/conditions_tests/any_or_all_test.rb", "test/conditions_tests/base_test.rb", "test/conditions_tests/groups_test.rb", "test/conditions_tests/magic_methods_test.rb", "test/conditions_tests/multiparameter_attributes_test.rb", "test/conditions_tests/protection_test.rb", "test/config_test.rb", "test/fixtures/accounts.yml", "test/fixtures/animals.yml", "test/fixtures/orders.yml", "test/fixtures/user_groups.yml", "test/fixtures/users.yml", "test/libs/acts_as_tree.rb", "test/libs/rexml_fix.rb", "test/modifier_tests/day_of_month_test.rb", "test/search_tests/base_test.rb", "test/search_tests/conditions_test.rb", "test/search_tests/ordering_test.rb", "test/search_tests/pagination_test.rb", "test/search_tests/protection_test.rb", "test/test_helper.rb", "TODO.rdoc", "searchlogic.gemspec"]
12
+ s.extra_rdoc_files = ["CHANGELOG.rdoc", "lib/searchlogic/active_record/associations.rb", "lib/searchlogic/active_record/base.rb", "lib/searchlogic/active_record/connection_adapters/mysql_adapter.rb", "lib/searchlogic/active_record/connection_adapters/postgresql_adapter.rb", "lib/searchlogic/active_record/connection_adapters/sqlite_adapter.rb", "lib/searchlogic/condition/base.rb", "lib/searchlogic/condition/begins_with.rb", "lib/searchlogic/condition/blank.rb", "lib/searchlogic/condition/child_of.rb", "lib/searchlogic/condition/descendant_of.rb", "lib/searchlogic/condition/ends_with.rb", "lib/searchlogic/condition/equals.rb", "lib/searchlogic/condition/greater_than.rb", "lib/searchlogic/condition/greater_than_or_equal_to.rb", "lib/searchlogic/condition/inclusive_descendant_of.rb", "lib/searchlogic/condition/keywords.rb", "lib/searchlogic/condition/less_than.rb", "lib/searchlogic/condition/less_than_or_equal_to.rb", "lib/searchlogic/condition/like.rb", "lib/searchlogic/condition/nested_set.rb", "lib/searchlogic/condition/nil.rb", "lib/searchlogic/condition/not_begin_with.rb", "lib/searchlogic/condition/not_blank.rb", "lib/searchlogic/condition/not_end_with.rb", "lib/searchlogic/condition/not_equal.rb", "lib/searchlogic/condition/not_have_keywords.rb", "lib/searchlogic/condition/not_like.rb", "lib/searchlogic/condition/not_nil.rb", "lib/searchlogic/condition/sibling_of.rb", "lib/searchlogic/conditions/any_or_all.rb", "lib/searchlogic/conditions/base.rb", "lib/searchlogic/conditions/groups.rb", "lib/searchlogic/conditions/magic_methods.rb", "lib/searchlogic/conditions/multiparameter_attributes.rb", "lib/searchlogic/conditions/protection.rb", "lib/searchlogic/config/helpers.rb", "lib/searchlogic/config/search.rb", "lib/searchlogic/config.rb", "lib/searchlogic/core_ext/hash.rb", "lib/searchlogic/core_ext/object.rb", "lib/searchlogic/helpers/control_types/link.rb", "lib/searchlogic/helpers/control_types/links.rb", "lib/searchlogic/helpers/control_types/remote_link.rb", "lib/searchlogic/helpers/control_types/remote_links.rb", "lib/searchlogic/helpers/control_types/remote_select.rb", "lib/searchlogic/helpers/control_types/select.rb", "lib/searchlogic/helpers/form.rb", "lib/searchlogic/helpers/utilities.rb", "lib/searchlogic/modifiers/absolute.rb", "lib/searchlogic/modifiers/acos.rb", "lib/searchlogic/modifiers/asin.rb", "lib/searchlogic/modifiers/atan.rb", "lib/searchlogic/modifiers/avg.rb", "lib/searchlogic/modifiers/base.rb", "lib/searchlogic/modifiers/ceil.rb", "lib/searchlogic/modifiers/char_length.rb", "lib/searchlogic/modifiers/cos.rb", "lib/searchlogic/modifiers/cot.rb", "lib/searchlogic/modifiers/count.rb", "lib/searchlogic/modifiers/day_of_month.rb", "lib/searchlogic/modifiers/day_of_week.rb", "lib/searchlogic/modifiers/day_of_year.rb", "lib/searchlogic/modifiers/degrees.rb", "lib/searchlogic/modifiers/exp.rb", "lib/searchlogic/modifiers/floor.rb", "lib/searchlogic/modifiers/hex.rb", "lib/searchlogic/modifiers/hour.rb", "lib/searchlogic/modifiers/log.rb", "lib/searchlogic/modifiers/log10.rb", "lib/searchlogic/modifiers/log2.rb", "lib/searchlogic/modifiers/lower.rb", "lib/searchlogic/modifiers/ltrim.rb", "lib/searchlogic/modifiers/md5.rb", "lib/searchlogic/modifiers/microseconds.rb", "lib/searchlogic/modifiers/milliseconds.rb", "lib/searchlogic/modifiers/minute.rb", "lib/searchlogic/modifiers/month.rb", "lib/searchlogic/modifiers/octal.rb", "lib/searchlogic/modifiers/radians.rb", "lib/searchlogic/modifiers/round.rb", "lib/searchlogic/modifiers/rtrim.rb", "lib/searchlogic/modifiers/second.rb", "lib/searchlogic/modifiers/sign.rb", "lib/searchlogic/modifiers/sin.rb", "lib/searchlogic/modifiers/square_root.rb", "lib/searchlogic/modifiers/sum.rb", "lib/searchlogic/modifiers/tan.rb", "lib/searchlogic/modifiers/trim.rb", "lib/searchlogic/modifiers/upper.rb", "lib/searchlogic/modifiers/week.rb", "lib/searchlogic/modifiers/year.rb", "lib/searchlogic/search/base.rb", "lib/searchlogic/search/conditions.rb", "lib/searchlogic/search/ordering.rb", "lib/searchlogic/search/pagination.rb", "lib/searchlogic/search/protection.rb", "lib/searchlogic/search/searching.rb", "lib/searchlogic/shared/utilities.rb", "lib/searchlogic/shared/virtual_classes.rb", "lib/searchlogic/version.rb", "lib/searchlogic.rb", "README.rdoc", "TODO.rdoc"]
13
+ s.files = ["CHANGELOG.rdoc", "init.rb", "lib/searchlogic/active_record/associations.rb", "lib/searchlogic/active_record/base.rb", "lib/searchlogic/active_record/connection_adapters/mysql_adapter.rb", "lib/searchlogic/active_record/connection_adapters/postgresql_adapter.rb", "lib/searchlogic/active_record/connection_adapters/sqlite_adapter.rb", "lib/searchlogic/condition/base.rb", "lib/searchlogic/condition/begins_with.rb", "lib/searchlogic/condition/blank.rb", "lib/searchlogic/condition/child_of.rb", "lib/searchlogic/condition/descendant_of.rb", "lib/searchlogic/condition/ends_with.rb", "lib/searchlogic/condition/equals.rb", "lib/searchlogic/condition/greater_than.rb", "lib/searchlogic/condition/greater_than_or_equal_to.rb", "lib/searchlogic/condition/inclusive_descendant_of.rb", "lib/searchlogic/condition/keywords.rb", "lib/searchlogic/condition/less_than.rb", "lib/searchlogic/condition/less_than_or_equal_to.rb", "lib/searchlogic/condition/like.rb", "lib/searchlogic/condition/nested_set.rb", "lib/searchlogic/condition/nil.rb", "lib/searchlogic/condition/not_begin_with.rb", "lib/searchlogic/condition/not_blank.rb", "lib/searchlogic/condition/not_end_with.rb", "lib/searchlogic/condition/not_equal.rb", "lib/searchlogic/condition/not_have_keywords.rb", "lib/searchlogic/condition/not_like.rb", "lib/searchlogic/condition/not_nil.rb", "lib/searchlogic/condition/sibling_of.rb", "lib/searchlogic/conditions/any_or_all.rb", "lib/searchlogic/conditions/base.rb", "lib/searchlogic/conditions/groups.rb", "lib/searchlogic/conditions/magic_methods.rb", "lib/searchlogic/conditions/multiparameter_attributes.rb", "lib/searchlogic/conditions/protection.rb", "lib/searchlogic/config/helpers.rb", "lib/searchlogic/config/search.rb", "lib/searchlogic/config.rb", "lib/searchlogic/core_ext/hash.rb", "lib/searchlogic/core_ext/object.rb", "lib/searchlogic/helpers/control_types/link.rb", "lib/searchlogic/helpers/control_types/links.rb", "lib/searchlogic/helpers/control_types/remote_link.rb", "lib/searchlogic/helpers/control_types/remote_links.rb", "lib/searchlogic/helpers/control_types/remote_select.rb", "lib/searchlogic/helpers/control_types/select.rb", "lib/searchlogic/helpers/form.rb", "lib/searchlogic/helpers/utilities.rb", "lib/searchlogic/modifiers/absolute.rb", "lib/searchlogic/modifiers/acos.rb", "lib/searchlogic/modifiers/asin.rb", "lib/searchlogic/modifiers/atan.rb", "lib/searchlogic/modifiers/avg.rb", "lib/searchlogic/modifiers/base.rb", "lib/searchlogic/modifiers/ceil.rb", "lib/searchlogic/modifiers/char_length.rb", "lib/searchlogic/modifiers/cos.rb", "lib/searchlogic/modifiers/cot.rb", "lib/searchlogic/modifiers/count.rb", "lib/searchlogic/modifiers/day_of_month.rb", "lib/searchlogic/modifiers/day_of_week.rb", "lib/searchlogic/modifiers/day_of_year.rb", "lib/searchlogic/modifiers/degrees.rb", "lib/searchlogic/modifiers/exp.rb", "lib/searchlogic/modifiers/floor.rb", "lib/searchlogic/modifiers/hex.rb", "lib/searchlogic/modifiers/hour.rb", "lib/searchlogic/modifiers/log.rb", "lib/searchlogic/modifiers/log10.rb", "lib/searchlogic/modifiers/log2.rb", "lib/searchlogic/modifiers/lower.rb", "lib/searchlogic/modifiers/ltrim.rb", "lib/searchlogic/modifiers/md5.rb", "lib/searchlogic/modifiers/microseconds.rb", "lib/searchlogic/modifiers/milliseconds.rb", "lib/searchlogic/modifiers/minute.rb", "lib/searchlogic/modifiers/month.rb", "lib/searchlogic/modifiers/octal.rb", "lib/searchlogic/modifiers/radians.rb", "lib/searchlogic/modifiers/round.rb", "lib/searchlogic/modifiers/rtrim.rb", "lib/searchlogic/modifiers/second.rb", "lib/searchlogic/modifiers/sign.rb", "lib/searchlogic/modifiers/sin.rb", "lib/searchlogic/modifiers/square_root.rb", "lib/searchlogic/modifiers/sum.rb", "lib/searchlogic/modifiers/tan.rb", "lib/searchlogic/modifiers/trim.rb", "lib/searchlogic/modifiers/upper.rb", "lib/searchlogic/modifiers/week.rb", "lib/searchlogic/modifiers/year.rb", "lib/searchlogic/search/base.rb", "lib/searchlogic/search/conditions.rb", "lib/searchlogic/search/ordering.rb", "lib/searchlogic/search/pagination.rb", "lib/searchlogic/search/protection.rb", "lib/searchlogic/search/searching.rb", "lib/searchlogic/shared/utilities.rb", "lib/searchlogic/shared/virtual_classes.rb", "lib/searchlogic/version.rb", "lib/searchlogic.rb", "Manifest", "MIT-LICENSE", "Rakefile", "README.rdoc", "test/active_record_tests/associations_test.rb", "test/active_record_tests/base_test.rb", "test/condition_tests/base_test.rb", "test/condition_tests/begins_with_test.rb", "test/condition_tests/blank_test.rb", "test/condition_tests/child_of_test.rb", "test/condition_tests/descendant_of_test.rb", "test/condition_tests/ends_with_test.rb", "test/condition_tests/equals_test.rb", "test/condition_tests/greater_than_or_equal_to_test.rb", "test/condition_tests/greater_than_test.rb", "test/condition_tests/inclusive_descendant_of_test.rb", "test/condition_tests/keywords_test.rb", "test/condition_tests/less_than_or_equal_to_test.rb", "test/condition_tests/less_than_test.rb", "test/condition_tests/like_test.rb", "test/condition_tests/nil_test.rb", "test/condition_tests/not_begin_with_test.rb", "test/condition_tests/not_blank_test.rb", "test/condition_tests/not_end_with_test.rb", "test/condition_tests/not_equal_test.rb", "test/condition_tests/not_have_keywords_test.rb", "test/condition_tests/not_like_test.rb", "test/condition_tests/not_nil_test.rb", "test/condition_tests/sibling_of_test.rb", "test/conditions_tests/any_or_all_test.rb", "test/conditions_tests/base_test.rb", "test/conditions_tests/groups_test.rb", "test/conditions_tests/magic_methods_test.rb", "test/conditions_tests/multiparameter_attributes_test.rb", "test/conditions_tests/protection_test.rb", "test/config_test.rb", "test/fixtures/accounts.yml", "test/fixtures/animals.yml", "test/fixtures/orders.yml", "test/fixtures/user_groups.yml", "test/fixtures/users.yml", "test/libs/awesome_nested_set/compatability.rb", "test/libs/awesome_nested_set/helper.rb", "test/libs/awesome_nested_set/named_scope.rb", "test/libs/awesome_nested_set.rb", "test/libs/rexml_fix.rb", "test/modifier_tests/day_of_month_test.rb", "test/search_tests/base_test.rb", "test/search_tests/conditions_test.rb", "test/search_tests/ordering_test.rb", "test/search_tests/pagination_test.rb", "test/search_tests/protection_test.rb", "test/test_helper.rb", "TODO.rdoc", "searchlogic.gemspec"]
14
14
  s.has_rdoc = true
15
15
  s.homepage = %q{http://github.com/binarylogic/searchlogic}
16
16
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Searchlogic", "--main", "README.rdoc"]
@@ -12,8 +12,8 @@ module ActiveRecordTests
12
12
  assert_equal User, search.klass
13
13
  assert_equal({:conditions => "\"users\".account_id = #{binary_logic.id}"}, search.scope)
14
14
 
15
- assert_equal [jennifer, ben], search.all
16
- assert_equal jennifer, search.first
15
+ assert_equal [ben, jennifer], search.all
16
+ assert_equal ben, search.first
17
17
  assert_equal ((ben.id + jennifer.id) / 2.0), search.average("id")
18
18
  assert_equal 2, search.count
19
19
 
@@ -70,10 +70,9 @@ module ActiveRecordTests
70
70
  assert_kind_of Searchlogic::Search::Base, search
71
71
  assert_equal User, search.klass
72
72
  assert_equal({:conditions => "\"user_groups_users\".user_group_id = #{neco.id} ", :joins => "INNER JOIN \"user_groups_users\" ON \"users\".id = \"user_groups_users\".user_id"}, search.scope)
73
+ assert_equal [drew, ben], search.all
73
74
 
74
- assert_equal [ben, drew], search.all
75
-
76
- assert_equal ben, search.first
75
+ assert_equal drew, search.first
77
76
  assert_equal ((ben.id + drew.id) / 2.0).to_s, search.average("id").to_s
78
77
  assert_equal 2, search.count
79
78
 
@@ -4,13 +4,9 @@ module ConditionTests
4
4
  class DescendantOfTest < ActiveSupport::TestCase
5
5
  def test_sanitize
6
6
  ben = users(:ben)
7
- drew = users(:drew)
8
- jennifer = users(:jennifer)
9
- tren = users(:tren)
10
-
11
7
  condition = Searchlogic::Condition::DescendantOf.new(User)
12
8
  condition.value = ben
13
- assert_equal ["\"users\".\"id\" = ? OR \"users\".\"id\" = ? OR \"users\".\"id\" = ?", drew.id, tren.id, jennifer.id], condition.sanitize
9
+ assert_equal ["\"users\".\"id\" != ? AND \"users\".\"lft\" >= ? AND \"users\".\"rgt\" <= ?", ben.id, ben.left, ben.right], condition.sanitize
14
10
  end
15
11
  end
16
12
  end
@@ -4,13 +4,9 @@ module ConditionTests
4
4
  class InclusiveDescendantOfTest < ActiveSupport::TestCase
5
5
  def test_sanitize
6
6
  ben = users(:ben)
7
- drew = users(:drew)
8
- jennifer = users(:jennifer)
9
- tren = users(:tren)
10
-
11
7
  condition = Searchlogic::Condition::InclusiveDescendantOf.new(User)
12
8
  condition.value = ben
13
- assert_equal ["\"users\".\"id\" = ? OR \"users\".\"id\" = ? OR \"users\".\"id\" = ? OR \"users\".\"id\" = ?", ben.id, drew.id, tren.id, jennifer.id], condition.sanitize
9
+ assert_equal ["\"users\".\"lft\" >= ? AND \"users\".\"rgt\" <= ?", ben.left, ben.right], condition.sanitize
14
10
  end
15
11
  end
16
12
  end
@@ -4,13 +4,14 @@ module ConditionsTests
4
4
  class MagicMethodsTest < ActiveSupport::TestCase
5
5
  def test_class_level_conditions
6
6
  ben = users(:ben)
7
- drew = users(:drew)
8
- jennifer = users(:jennifer)
9
- tren = users(:tren)
10
7
 
11
8
  conditions = Searchlogic::Cache::UserConditions.new
9
+ conditions.descendant_of = "21"
10
+ assert_equal 21, conditions.descendant_of
11
+ conditions.descendant_of = ["21", "22"]
12
+ assert_equal [21, 22], conditions.descendant_of
12
13
  conditions.descendant_of = ben
13
- assert_equal ["\"users\".\"id\" = ? OR \"users\".\"id\" = ? OR \"users\".\"id\" = ?", drew.id, tren.id, jennifer.id], conditions.sanitize
14
+ assert_equal ["\"users\".\"id\" != ? AND \"users\".\"lft\" >= ? AND \"users\".\"rgt\" <= ?", ben.id, ben.left, ben.right], conditions.sanitize
14
15
  end
15
16
 
16
17
  def test_virtual_columns
@@ -1,11 +1,11 @@
1
1
  bens_order:
2
- user: ben
2
+ user_id: 1
3
3
  total: 500.23
4
4
  description: A bunch of apple products, etc.
5
5
  receipt: some binary text
6
6
 
7
7
  drews_order:
8
- user: drew
8
+ user_id: 2
9
9
  total: 2.12
10
10
  description: Some more apple projects, ipod, etc
11
11
  receipt: some more binary text
@@ -1,7 +1,5 @@
1
1
  neco:
2
2
  name: NECO
3
- users: ben, drew
4
3
 
5
4
  johnsons:
6
- name: Johnsons
7
- users: ben, jennifer
5
+ name: Johnsons
@@ -1,28 +1,43 @@
1
1
  ben:
2
+ id: 1
2
3
  account: binary_logic
4
+ lft: 1
5
+ rgt: 8
3
6
  first_name: Ben
4
7
  last_name: Johnson
5
8
  active: true
6
9
  bio: Totally awesome!
10
+ user_groups: neco, johnsons
7
11
 
8
12
  drew:
13
+ id: 2
9
14
  account: neco
10
- parent: ben
15
+ parent_id: 1
16
+ lft: 2
17
+ rgt: 5
11
18
  first_name: Drew
12
19
  last_name: Mills
13
20
  active: false
14
21
  bio: Totally not awesome!
22
+ user_groups: neco
15
23
 
16
24
  jennifer:
25
+ id: 3
17
26
  account: binary_logic
18
- parent: drew
27
+ parent_id: 2
28
+ lft: 3
29
+ rgt: 4
19
30
  first_name: Jennifer
20
31
  last_name: Hopkins
21
32
  active: false
22
33
  bio: Totally not awesome at all!
34
+ user_groups: johnsons
23
35
 
24
36
  tren:
25
- parent: ben
37
+ id: 4
38
+ parent_id: 1
39
+ lft: 6
40
+ rgt: 7
26
41
  first_name: Tren
27
42
  last_name: Garfield
28
43
  active: false
@@ -0,0 +1,545 @@
1
+ module CollectiveIdea #:nodoc:
2
+ module Acts #:nodoc:
3
+ module NestedSet #:nodoc:
4
+ def self.included(base)
5
+ base.extend(SingletonMethods)
6
+ end
7
+
8
+ # This acts provides Nested Set functionality. Nested Set is a smart way to implement
9
+ # an _ordered_ tree, with the added feature that you can select the children and all of their
10
+ # descendants with a single query. The drawback is that insertion or move need some complex
11
+ # sql queries. But everything is done here by this module!
12
+ #
13
+ # Nested sets are appropriate each time you want either an orderd tree (menus,
14
+ # commercial categories) or an efficient way of querying big trees (threaded posts).
15
+ #
16
+ # == API
17
+ #
18
+ # Methods names are aligned with acts_as_tree as much as possible, to make replacment from one
19
+ # by another easier, except for the creation:
20
+ #
21
+ # in acts_as_tree:
22
+ # item.children.create(:name => "child1")
23
+ #
24
+ # in acts_as_nested_set:
25
+ # # adds a new item at the "end" of the tree, i.e. with child.left = max(tree.right)+1
26
+ # child = MyClass.new(:name => "child1")
27
+ # child.save
28
+ # # now move the item to its right place
29
+ # child.move_to_child_of my_item
30
+ #
31
+ # You can pass an id or an object to:
32
+ # * <tt>#move_to_child_of</tt>
33
+ # * <tt>#move_to_right_of</tt>
34
+ # * <tt>#move_to_left_of</tt>
35
+ #
36
+ module SingletonMethods
37
+ # Configuration options are:
38
+ #
39
+ # * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
40
+ # * +:left_column+ - column name for left boundry data, default "lft"
41
+ # * +:right_column+ - column name for right boundry data, default "rgt"
42
+ # * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
43
+ # (if it hasn't been already) and use that as the foreign key restriction. You
44
+ # can also pass an array to scope by multiple attributes.
45
+ # Example: <tt>acts_as_nested_set :scope => [:notable_id, :notable_type]</tt>
46
+ # * +:dependent+ - behavior for cascading destroy. If set to :destroy, all the
47
+ # child objects are destroyed alongside this object by calling their destroy
48
+ # method. If set to :delete_all (default), all the child objects are deleted
49
+ # without calling their destroy method.
50
+ #
51
+ # See CollectiveIdea::Acts::NestedSet::ClassMethods for a list of class methods and
52
+ # CollectiveIdea::Acts::NestedSet::InstanceMethods for a list of instance methods added
53
+ # to acts_as_nested_set models
54
+ def acts_as_nested_set(options = {})
55
+ options = {
56
+ :parent_column => 'parent_id',
57
+ :left_column => 'lft',
58
+ :right_column => 'rgt',
59
+ :dependent => :delete_all, # or :destroy
60
+ }.merge(options)
61
+
62
+ if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
63
+ options[:scope] = "#{options[:scope]}_id".intern
64
+ end
65
+
66
+ write_inheritable_attribute :acts_as_nested_set_options, options
67
+ class_inheritable_reader :acts_as_nested_set_options
68
+
69
+ include InstanceMethods
70
+ include Comparable
71
+ include Columns
72
+ extend Columns
73
+ extend ClassMethods
74
+
75
+ # no bulk assignment
76
+ attr_protected left_column_name.intern,
77
+ right_column_name.intern,
78
+ parent_column_name.intern
79
+
80
+ before_create :set_default_left_and_right
81
+ before_destroy :prune_from_tree
82
+
83
+ # no assignment to structure fields
84
+ [left_column_name, right_column_name, parent_column_name].each do |column|
85
+ module_eval <<-"end_eval", __FILE__, __LINE__
86
+ def #{column}=(x)
87
+ raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
88
+ end
89
+ end_eval
90
+ end
91
+
92
+ named_scope :roots, :conditions => {parent_column_name => nil}, :order => quoted_left_column_name
93
+ named_scope :leaves, :conditions => "#{quoted_right_column_name} - #{quoted_left_column_name} = 1", :order => quoted_left_column_name
94
+ if self.respond_to?(:define_callbacks)
95
+ define_callbacks("before_move", "after_move")
96
+ end
97
+
98
+
99
+ end
100
+
101
+ end
102
+
103
+ module ClassMethods
104
+
105
+ # Returns the first root
106
+ def root
107
+ roots.find(:first)
108
+ end
109
+
110
+ def valid?
111
+ left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid?
112
+ end
113
+
114
+ def left_and_rights_valid?
115
+ count(
116
+ :joins => "LEFT OUTER JOIN #{quoted_table_name} AS parent ON " +
117
+ "#{quoted_table_name}.#{quoted_parent_column_name} = parent.#{primary_key}",
118
+ :conditions =>
119
+ "#{quoted_table_name}.#{quoted_left_column_name} IS NULL OR " +
120
+ "#{quoted_table_name}.#{quoted_right_column_name} IS NULL OR " +
121
+ "#{quoted_table_name}.#{quoted_left_column_name} >= " +
122
+ "#{quoted_table_name}.#{quoted_right_column_name} OR " +
123
+ "(#{quoted_table_name}.#{quoted_parent_column_name} IS NOT NULL AND " +
124
+ "(#{quoted_table_name}.#{quoted_left_column_name} <= parent.#{quoted_left_column_name} OR " +
125
+ "#{quoted_table_name}.#{quoted_right_column_name} >= parent.#{quoted_right_column_name}))"
126
+ ) == 0
127
+ end
128
+
129
+ def no_duplicates_for_columns?
130
+ scope_string = Array(acts_as_nested_set_options[:scope]).map do |c|
131
+ connection.quote_column_name(c)
132
+ end.push(nil).join(", ")
133
+ [quoted_left_column_name, quoted_right_column_name].all? do |column|
134
+ # No duplicates
135
+ find(:first,
136
+ :select => "#{scope_string}#{column}, COUNT(#{column})",
137
+ :group => "#{scope_string}#{column}
138
+ HAVING COUNT(#{column}) > 1").nil?
139
+ end
140
+ end
141
+
142
+ # Wrapper for each_root_valid? that can deal with scope.
143
+ def all_roots_valid?
144
+ if acts_as_nested_set_options[:scope]
145
+ roots(:group => scope_column_names).group_by{|record| scope_column_names.collect{|col| record.send(col.to_sym)}}.all? do |scope, grouped_roots|
146
+ each_root_valid?(grouped_roots)
147
+ end
148
+ else
149
+ each_root_valid?(roots)
150
+ end
151
+ end
152
+
153
+ def each_root_valid?(roots_to_validate)
154
+ left = right = 0
155
+ roots_to_validate.all? do |root|
156
+ returning(root.left > left && root.right > right) do
157
+ left = root.left
158
+ right = root.right
159
+ end
160
+ end
161
+ end
162
+
163
+ # Rebuilds the left & rights if unset or invalid. Also very useful for converting from acts_as_tree.
164
+ def rebuild!
165
+ # Don't rebuild a valid tree.
166
+ return true if valid?
167
+
168
+ scope = lambda{}
169
+ if acts_as_nested_set_options[:scope]
170
+ scope = lambda{|node|
171
+ scope_column_names.inject(""){|str, column_name|
172
+ str << "AND #{connection.quote_column_name(column_name)} = #{connection.quote(node.send(column_name.to_sym))} "
173
+ }
174
+ }
175
+ end
176
+ indices = {}
177
+
178
+ set_left_and_rights = lambda do |node|
179
+ # set left
180
+ node[left_column_name] = indices[scope.call(node)] += 1
181
+ # find
182
+ find(:all, :conditions => ["#{quoted_parent_column_name} = ? #{scope.call(node)}", node], :order => "#{quoted_left_column_name}, #{quoted_right_column_name}, id").each{|n| set_left_and_rights.call(n) }
183
+ # set right
184
+ node[right_column_name] = indices[scope.call(node)] += 1
185
+ node.save!
186
+ end
187
+
188
+ # Find root node(s)
189
+ root_nodes = find(:all, :conditions => "#{quoted_parent_column_name} IS NULL", :order => "#{quoted_left_column_name}, #{quoted_right_column_name}, id").each do |root_node|
190
+ # setup index for this scope
191
+ indices[scope.call(root_node)] ||= 0
192
+ set_left_and_rights.call(root_node)
193
+ end
194
+ end
195
+ end
196
+
197
+ # Mixed into both classes and instances to provide easy access to the column names
198
+ module Columns
199
+ def left_column_name
200
+ acts_as_nested_set_options[:left_column]
201
+ end
202
+
203
+ def right_column_name
204
+ acts_as_nested_set_options[:right_column]
205
+ end
206
+
207
+ def parent_column_name
208
+ acts_as_nested_set_options[:parent_column]
209
+ end
210
+
211
+ def scope_column_names
212
+ Array(acts_as_nested_set_options[:scope])
213
+ end
214
+
215
+ def quoted_left_column_name
216
+ connection.quote_column_name(left_column_name)
217
+ end
218
+
219
+ def quoted_right_column_name
220
+ connection.quote_column_name(right_column_name)
221
+ end
222
+
223
+ def quoted_parent_column_name
224
+ connection.quote_column_name(parent_column_name)
225
+ end
226
+
227
+ def quoted_scope_column_names
228
+ scope_column_names.collect {|column_name| connection.quote_column_name(column_name) }
229
+ end
230
+ end
231
+
232
+ # Any instance method that returns a collection makes use of Rails 2.1's named_scope (which is bundled for Rails 2.0), so it can be treated as a finder.
233
+ #
234
+ # category.self_and_descendants.count
235
+ # category.ancestors.find(:all, :conditions => "name like '%foo%'")
236
+ module InstanceMethods
237
+ # Value of the parent column
238
+ def parent_id
239
+ self[parent_column_name]
240
+ end
241
+
242
+ # Value of the left column
243
+ def left
244
+ self[left_column_name]
245
+ end
246
+
247
+ # Value of the right column
248
+ def right
249
+ self[right_column_name]
250
+ end
251
+
252
+ # Returns true if this is a root node.
253
+ def root?
254
+ parent_id.nil?
255
+ end
256
+
257
+ def leaf?
258
+ right - left == 1
259
+ end
260
+
261
+ # Returns true is this is a child node
262
+ def child?
263
+ !parent_id.nil?
264
+ end
265
+
266
+ # order by left column
267
+ def <=>(x)
268
+ left <=> x.left
269
+ end
270
+
271
+ # Returns root
272
+ def root
273
+ self_and_ancestors.find(:first)
274
+ end
275
+
276
+ # Returns the immediate parent
277
+ def parent
278
+ nested_set_scope.find_by_id(parent_id) if parent_id
279
+ end
280
+
281
+ # Returns the array of all parents and self
282
+ def self_and_ancestors
283
+ nested_set_scope.scoped :conditions => [
284
+ "#{self.class.table_name}.#{quoted_left_column_name} <= ? AND #{self.class.table_name}.#{quoted_right_column_name} >= ?", left, right
285
+ ]
286
+ end
287
+
288
+ # Returns an array of all parents
289
+ def ancestors
290
+ without_self self_and_ancestors
291
+ end
292
+
293
+ # Returns the array of all children of the parent, including self
294
+ def self_and_siblings
295
+ nested_set_scope.scoped :conditions => {parent_column_name => parent_id}
296
+ end
297
+
298
+ # Returns the array of all children of the parent, except self
299
+ def siblings
300
+ without_self self_and_siblings
301
+ end
302
+
303
+ # Returns a set of all of its nested children which do not have children
304
+ def leaves
305
+ descendants.scoped :conditions => "#{self.class.table_name}.#{quoted_right_column_name} - #{self.class.table_name}.#{quoted_left_column_name} = 1"
306
+ end
307
+
308
+ # Returns the level of this object in the tree
309
+ # root level is 0
310
+ def level
311
+ parent_id.nil? ? 0 : ancestors.count
312
+ end
313
+
314
+ # Returns a set of itself and all of its nested children
315
+ def self_and_descendants
316
+ nested_set_scope.scoped :conditions => [
317
+ "#{self.class.table_name}.#{quoted_left_column_name} >= ? AND #{self.class.table_name}.#{quoted_right_column_name} <= ?", left, right
318
+ ]
319
+ end
320
+
321
+ # Returns a set of all of its children and nested children
322
+ def descendants
323
+ without_self self_and_descendants
324
+ end
325
+
326
+ # Returns a set of only this entry's immediate children
327
+ def children
328
+ nested_set_scope.scoped :conditions => {parent_column_name => self}
329
+ end
330
+
331
+ def is_descendant_of?(other)
332
+ other.left < self.left && self.left < other.right && same_scope?(other)
333
+ end
334
+
335
+ def is_or_is_descendant_of?(other)
336
+ other.left <= self.left && self.left < other.right && same_scope?(other)
337
+ end
338
+
339
+ def is_ancestor_of?(other)
340
+ self.left < other.left && other.left < self.right && same_scope?(other)
341
+ end
342
+
343
+ def is_or_is_ancestor_of?(other)
344
+ self.left <= other.left && other.left < self.right && same_scope?(other)
345
+ end
346
+
347
+ # Check if other model is in the same scope
348
+ def same_scope?(other)
349
+ Array(acts_as_nested_set_options[:scope]).all? do |attr|
350
+ self.send(attr) == other.send(attr)
351
+ end
352
+ end
353
+
354
+ # Find the first sibling to the left
355
+ def left_sibling
356
+ siblings.find(:first, :conditions => ["#{self.class.table_name}.#{quoted_left_column_name} < ?", left],
357
+ :order => "#{self.class.table_name}.#{quoted_left_column_name} DESC")
358
+ end
359
+
360
+ # Find the first sibling to the right
361
+ def right_sibling
362
+ siblings.find(:first, :conditions => ["#{self.class.table_name}.#{quoted_left_column_name} > ?", left])
363
+ end
364
+
365
+ # Shorthand method for finding the left sibling and moving to the left of it.
366
+ def move_left
367
+ move_to_left_of left_sibling
368
+ end
369
+
370
+ # Shorthand method for finding the right sibling and moving to the right of it.
371
+ def move_right
372
+ move_to_right_of right_sibling
373
+ end
374
+
375
+ # Move the node to the left of another node (you can pass id only)
376
+ def move_to_left_of(node)
377
+ move_to node, :left
378
+ end
379
+
380
+ # Move the node to the left of another node (you can pass id only)
381
+ def move_to_right_of(node)
382
+ move_to node, :right
383
+ end
384
+
385
+ # Move the node to the child of another node (you can pass id only)
386
+ def move_to_child_of(node)
387
+ move_to node, :child
388
+ end
389
+
390
+ # Move the node to root nodes
391
+ def move_to_root
392
+ move_to nil, :root
393
+ end
394
+
395
+ def move_possible?(target)
396
+ self != target && # Can't target self
397
+ same_scope?(target) && # can't be in different scopes
398
+ # !(left..right).include?(target.left..target.right) # this needs tested more
399
+ # detect impossible move
400
+ !((left <= target.left && right >= target.left) or (left <= target.right && right >= target.right))
401
+ end
402
+
403
+ def to_text
404
+ self_and_descendants.map do |node|
405
+ "#{'*'*(node.level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
406
+ end.join("\n")
407
+ end
408
+
409
+ protected
410
+
411
+ def without_self(scope)
412
+ scope.scoped :conditions => ["#{self.class.table_name}.#{self.class.primary_key} != ?", self]
413
+ end
414
+
415
+ # All nested set queries should use this nested_set_scope, which performs finds on
416
+ # the base ActiveRecord class, using the :scope declared in the acts_as_nested_set
417
+ # declaration.
418
+ def nested_set_scope
419
+ options = {:order => quoted_left_column_name}
420
+ scopes = Array(acts_as_nested_set_options[:scope])
421
+ options[:conditions] = scopes.inject({}) do |conditions,attr|
422
+ conditions.merge attr => self[attr]
423
+ end unless scopes.empty?
424
+ self.class.base_class.scoped options
425
+ end
426
+
427
+ # on creation, set automatically lft and rgt to the end of the tree
428
+ def set_default_left_and_right
429
+ maxright = nested_set_scope.maximum(right_column_name) || 0
430
+ # adds the new node to the right of all existing nodes
431
+ self[left_column_name] = maxright + 1
432
+ self[right_column_name] = maxright + 2
433
+ end
434
+
435
+ # Prunes a branch off of the tree, shifting all of the elements on the right
436
+ # back to the left so the counts still work.
437
+ def prune_from_tree
438
+ return if right.nil? || left.nil?
439
+ diff = right - left + 1
440
+
441
+ delete_method = acts_as_nested_set_options[:dependent] == :destroy ?
442
+ :destroy_all : :delete_all
443
+
444
+ self.class.base_class.transaction do
445
+ nested_set_scope.send(delete_method,
446
+ ["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?",
447
+ left, right]
448
+ )
449
+ nested_set_scope.update_all(
450
+ ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff],
451
+ ["#{quoted_left_column_name} >= ?", right]
452
+ )
453
+ nested_set_scope.update_all(
454
+ ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff],
455
+ ["#{quoted_right_column_name} >= ?", right]
456
+ )
457
+ end
458
+ end
459
+
460
+ # reload left, right, and parent
461
+ def reload_nested_set
462
+ reload(:select => "#{quoted_left_column_name}, " +
463
+ "#{quoted_right_column_name}, #{quoted_parent_column_name}")
464
+ end
465
+
466
+ def move_to(target, position)
467
+ raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if self.new_record?
468
+ return if callback(:before_move) == false
469
+ transaction do
470
+ if target.is_a? self.class.base_class
471
+ target.reload_nested_set
472
+ elsif position != :root
473
+ # load object if node is not an object
474
+ target = nested_set_scope.find(target)
475
+ end
476
+ self.reload_nested_set
477
+
478
+ unless position == :root || move_possible?(target)
479
+ raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
480
+ end
481
+
482
+ bound = case position
483
+ when :child; target[right_column_name]
484
+ when :left; target[left_column_name]
485
+ when :right; target[right_column_name] + 1
486
+ when :root; 1
487
+ else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
488
+ end
489
+
490
+ if bound > self[right_column_name]
491
+ bound = bound - 1
492
+ other_bound = self[right_column_name] + 1
493
+ else
494
+ other_bound = self[left_column_name] - 1
495
+ end
496
+
497
+ # there would be no change
498
+ return if bound == self[right_column_name] || bound == self[left_column_name]
499
+
500
+ # we have defined the boundaries of two non-overlapping intervals,
501
+ # so sorting puts both the intervals and their boundaries in order
502
+ a, b, c, d = [self[left_column_name], self[right_column_name], bound, other_bound].sort
503
+
504
+ new_parent = case position
505
+ when :child; target.id
506
+ when :root; nil
507
+ else target[parent_column_name]
508
+ end
509
+
510
+ self.class.base_class.update_all([
511
+ "#{quoted_left_column_name} = CASE " +
512
+ "WHEN #{quoted_left_column_name} BETWEEN :a AND :b " +
513
+ "THEN #{quoted_left_column_name} + :d - :b " +
514
+ "WHEN #{quoted_left_column_name} BETWEEN :c AND :d " +
515
+ "THEN #{quoted_left_column_name} + :a - :c " +
516
+ "ELSE #{quoted_left_column_name} END, " +
517
+ "#{quoted_right_column_name} = CASE " +
518
+ "WHEN #{quoted_right_column_name} BETWEEN :a AND :b " +
519
+ "THEN #{quoted_right_column_name} + :d - :b " +
520
+ "WHEN #{quoted_right_column_name} BETWEEN :c AND :d " +
521
+ "THEN #{quoted_right_column_name} + :a - :c " +
522
+ "ELSE #{quoted_right_column_name} END, " +
523
+ "#{quoted_parent_column_name} = CASE " +
524
+ "WHEN #{self.class.base_class.primary_key} = :id THEN :new_parent " +
525
+ "ELSE #{quoted_parent_column_name} END",
526
+ {:a => a, :b => b, :c => c, :d => d, :id => self.id, :new_parent => new_parent}
527
+ ], nested_set_scope.proxy_options[:conditions])
528
+ end
529
+ target.reload_nested_set if target
530
+ self.reload_nested_set
531
+ callback(:after_move)
532
+ end
533
+
534
+ end
535
+
536
+ end
537
+ end
538
+ end
539
+
540
+
541
+ require File.dirname(__FILE__) + '/awesome_nested_set/compatability'
542
+
543
+ ActiveRecord::Base.class_eval do
544
+ include CollectiveIdea::Acts::NestedSet
545
+ end