collectiveidea-awesome_nested_set 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  = AwesomeNestedSet
2
2
 
3
- Awesome Nested Set is an implementation of the nested set pattern for ActiveRecord models. It is replacement for acts_as_nested_set and BetterNestedSet, but awesomer.
3
+ Awesome Nested Set is an implementation of the nested set pattern for ActiveRecord models. It is replacement for acts_as_nested_set and BetterNestedSet, but awesomer. It supports Rails 2.1 and later.
4
4
 
5
5
  == What makes this so awesome?
6
6
 
@@ -8,7 +8,7 @@ This is a new implementation of nested set based off of BetterNestedSet that fix
8
8
 
9
9
  == Installation
10
10
 
11
- If you are on Rails 2.1 or later:
11
+ Install as a plugin:
12
12
 
13
13
  script/plugin install git://github.com/collectiveidea/awesome_nested_set.git
14
14
 
@@ -60,5 +60,20 @@ You can learn more about nested sets at:
60
60
  http://api.rubyonrails.com/classes/ActiveRecord/Acts/NestedSet/ClassMethods.html
61
61
  http://opensource.symetrie.com/trac/better_nested_set/
62
62
 
63
+ == How to contribute
63
64
 
64
- Copyright (c) 2008 Collective Idea, released under the MIT license
65
+ If you find what you might think is a bug:
66
+
67
+ 1. Check the GitHub issue tracker to see if anyone else has had the same issue.
68
+ http://github.com/collectiveidea/awesome_nested_set/issues/
69
+ 2. If you don't see anything, create an issue with information on how to reproduce it.
70
+
71
+ If you want to contribute an enhancement or a fix:
72
+
73
+ 1. Fork the project on github.
74
+ http://github.com/collectiveidea/awesome_nested_set/
75
+ 2. Make your changes with tests.
76
+ 3. Commit the changes without making changes to the Rakefile, VERSION, or any other files that aren't related to your enhancement or fix
77
+ 4. Send a pull request.
78
+
79
+ Copyright ©2008 Collective Idea, released under the MIT license
data/Rakefile CHANGED
@@ -28,7 +28,7 @@ task :default => :test
28
28
 
29
29
  desc 'Test the awesome_nested_set plugin.'
30
30
  Rake::TestTask.new(:test) do |t|
31
- t.libs << 'lib'
31
+ t.libs += ['lib', 'test']
32
32
  t.pattern = 'test/**/*_test.rb'
33
33
  t.verbose = true
34
34
  end
@@ -45,7 +45,7 @@ end
45
45
  namespace :test do
46
46
  desc "just rcov minus html output"
47
47
  Rcov::RcovTask.new(:coverage) do |t|
48
- # t.libs << 'test'
48
+ t.libs << 'test'
49
49
  t.test_files = FileList['test/**/*_test.rb']
50
50
  t.output_dir = 'coverage'
51
51
  t.verbose = true
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.0
1
+ 1.3.0
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{awesome_nested_set}
5
- s.version = "1.2.0"
5
+ s.version = "1.3.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Brandon Keepers", "Daniel Morrison"]
9
- s.date = %q{2009-07-15}
9
+ s.date = %q{2009-08-11}
10
10
  s.description = %q{An awesome nested set implementation for Active Record}
11
11
  s.email = %q{info@collectiveidea.com}
12
12
  s.extra_rdoc_files = [
@@ -22,10 +22,9 @@ Gem::Specification.new do |s|
22
22
  "awesome_nested_set.gemspec",
23
23
  "init.rb",
24
24
  "lib/awesome_nested_set.rb",
25
- "lib/awesome_nested_set/compatability.rb",
26
25
  "lib/awesome_nested_set/helper.rb",
27
- "lib/awesome_nested_set/named_scope.rb",
28
26
  "rails/init.rb",
27
+ "test/application.rb",
29
28
  "test/awesome_nested_set/helper_test.rb",
30
29
  "test/awesome_nested_set_test.rb",
31
30
  "test/db/database.yml",
@@ -46,6 +45,7 @@ Gem::Specification.new do |s|
46
45
  "test/fixtures/categories.yml",
47
46
  "test/fixtures/departments.yml",
48
47
  "test/fixtures/notes.yml",
48
+ "test/application.rb",
49
49
  "test/awesome_nested_set/helper_test.rb",
50
50
  "test/awesome_nested_set_test.rb",
51
51
  "test/db/schema.rb",
@@ -72,19 +72,23 @@ module CollectiveIdea #:nodoc:
72
72
  include InstanceMethods
73
73
  extend Columns
74
74
  extend ClassMethods
75
+
76
+ belongs_to :parent, :class_name => self.base_class.class_name,
77
+ :foreign_key => parent_column_name
75
78
 
76
79
  attr_accessor :skip_before_destroy
77
80
 
78
81
  # no bulk assignment
79
82
  attr_protected left_column_name.intern,
80
- right_column_name.intern,
81
- parent_column_name.intern
83
+ right_column_name.intern
82
84
 
83
- before_create :set_default_left_and_right
85
+ before_create :set_default_left_and_right
86
+ before_save :store_new_parent
87
+ after_save :move_to_new_parent
84
88
  before_destroy :destroy_descendants
85
89
 
86
90
  # no assignment to structure fields
87
- [left_column_name, right_column_name, parent_column_name].each do |column|
91
+ [left_column_name, right_column_name].each do |column|
88
92
  module_eval <<-"end_eval", __FILE__, __LINE__
89
93
  def #{column}=(x)
90
94
  raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
@@ -283,11 +287,6 @@ module CollectiveIdea #:nodoc:
283
287
  self_and_ancestors.find(:first)
284
288
  end
285
289
 
286
- # Returns the immediate parent
287
- def parent
288
- nested_set_scope.find_by_id(parent_id) if parent_id
289
- end
290
-
291
290
  # Returns the array of all parents and self
292
291
  def self_and_ancestors
293
292
  nested_set_scope.scoped :conditions => [
@@ -434,6 +433,19 @@ module CollectiveIdea #:nodoc:
434
433
  self.class.base_class.scoped options
435
434
  end
436
435
 
436
+ def store_new_parent
437
+ @move_to_new_parent_id = parent_id_changed? ? parent_id : false
438
+ true # force callback to return true
439
+ end
440
+
441
+ def move_to_new_parent
442
+ if @move_to_new_parent_id.nil?
443
+ move_to_root
444
+ elsif @move_to_new_parent_id
445
+ move_to_child_of(@move_to_new_parent_id)
446
+ end
447
+ end
448
+
437
449
  # on creation, set automatically lft and rgt to the end of the tree
438
450
  def set_default_left_and_right
439
451
  maxright = nested_set_scope.maximum(right_column_name) || 0
@@ -1,4 +1,3 @@
1
- require 'awesome_nested_set/compatability'
2
1
  require 'awesome_nested_set'
3
2
 
4
3
  ActiveRecord::Base.class_eval do
@@ -0,0 +1 @@
1
+ # This file is here to satisfy test_help from Rails < 2.3
@@ -1,9 +1,9 @@
1
- require File.dirname(__FILE__) + '/../test_helper'
1
+ require 'test_helper'
2
2
 
3
3
  module CollectiveIdea
4
4
  module Acts #:nodoc:
5
5
  module NestedSet #:nodoc:
6
- class AwesomeNestedSetTest < Test::Unit::TestCase
6
+ class AwesomeNestedSetTest < TestCaseClass
7
7
  include Helper
8
8
  fixtures :categories
9
9
 
@@ -38,4 +38,4 @@ module CollectiveIdea
38
38
  end
39
39
  end
40
40
  end
41
- end
41
+ end
@@ -1,10 +1,10 @@
1
- require File.dirname(__FILE__) + '/test_helper'
1
+ require 'test_helper'
2
2
 
3
3
  class Note < ActiveRecord::Base
4
4
  acts_as_nested_set :scope => [:notable_id, :notable_type]
5
5
  end
6
6
 
7
- class AwesomeNestedSetTest < Test::Unit::TestCase
7
+ class AwesomeNestedSetTest < TestCaseClass
8
8
 
9
9
  class Default < ActiveRecord::Base
10
10
  acts_as_nested_set
@@ -66,15 +66,10 @@ class AwesomeNestedSetTest < Test::Unit::TestCase
66
66
  assert_raises(ActiveRecord::ActiveRecordError) { Category.new.rgt = 1 }
67
67
  end
68
68
 
69
- def test_parent_column_protected_from_assignment
70
- assert_raises(ActiveRecord::ActiveRecordError) { Category.new.parent_id = 1 }
71
- end
72
-
73
69
  def test_colums_protected_on_initialize
74
- c = Category.new(:lft => 1, :rgt => 2, :parent_id => 3)
70
+ c = Category.new(:lft => 1, :rgt => 2)
75
71
  assert_nil c.lft
76
72
  assert_nil c.rgt
77
- assert_nil c.parent_id
78
73
  end
79
74
 
80
75
  def test_scoped_appends_id
@@ -619,4 +614,58 @@ class AwesomeNestedSetTest < Test::Unit::TestCase
619
614
  assert Category.valid?
620
615
  end
621
616
 
617
+ def test_assigning_parent_id_on_create
618
+ category = Category.create!(:name => "Child", :parent_id => categories(:child_2).id)
619
+ assert_equal categories(:child_2), category.parent
620
+ assert_equal categories(:child_2).id, category.parent_id
621
+ assert_not_nil category.left
622
+ assert_not_nil category.right
623
+ assert Category.valid?
624
+ end
625
+
626
+ def test_assigning_parent_on_create
627
+ category = Category.create!(:name => "Child", :parent => categories(:child_2))
628
+ assert_equal categories(:child_2), category.parent
629
+ assert_equal categories(:child_2).id, category.parent_id
630
+ assert_not_nil category.left
631
+ assert_not_nil category.right
632
+ assert Category.valid?
633
+ end
634
+
635
+ def test_assigning_parent_id_to_nil_on_create
636
+ category = Category.create!(:name => "New Root", :parent_id => nil)
637
+ assert_nil category.parent
638
+ assert_nil category.parent_id
639
+ assert_not_nil category.left
640
+ assert_not_nil category.right
641
+ assert Category.valid?
642
+ end
643
+
644
+ def test_assigning_parent_id_on_update
645
+ category = categories(:child_2_1)
646
+ category.parent_id = categories(:child_3).id
647
+ category.save
648
+ assert_equal categories(:child_3), category.parent
649
+ assert_equal categories(:child_3).id, category.parent_id
650
+ assert Category.valid?
651
+ end
652
+
653
+ def test_assigning_parent_on_update
654
+ category = categories(:child_2_1)
655
+ category.parent = categories(:child_3)
656
+ category.save
657
+ assert_equal categories(:child_3), category.parent
658
+ assert_equal categories(:child_3).id, category.parent_id
659
+ assert Category.valid?
660
+ end
661
+
662
+ def test_assigning_parent_id_to_nil_on_update
663
+ category = categories(:child_2_1)
664
+ category.parent_id = nil
665
+ category.save
666
+ assert_nil category.parent
667
+ assert_nil category.parent_id
668
+ assert Category.valid?
669
+ end
670
+
622
671
  end
@@ -1,16 +1,16 @@
1
1
  $:.unshift(File.dirname(__FILE__) + '/../lib')
2
2
  plugin_test_dir = File.dirname(__FILE__)
3
+ RAILS_ROOT = plugin_test_dir
3
4
 
4
5
  require 'rubygems'
5
6
  require 'test/unit'
6
7
  require 'multi_rails_init'
7
- require 'active_record'
8
- require 'action_controller'
9
- require 'action_view'
10
- require 'active_record/fixtures'
8
+ require 'test_help'
11
9
 
12
10
  require plugin_test_dir + '/../init.rb'
13
11
 
12
+ TestCaseClass = ActiveSupport::TestCase rescue Test::Unit::TestCase
13
+
14
14
  ActiveRecord::Base.logger = Logger.new(plugin_test_dir + "/debug.log")
15
15
 
16
16
  ActiveRecord::Base.configurations = YAML::load(IO.read(plugin_test_dir + "/db/database.yml"))
@@ -21,10 +21,10 @@ load(File.join(plugin_test_dir, "db", "schema.rb"))
21
21
  Dir["#{plugin_test_dir}/fixtures/*.rb"].each {|file| require file }
22
22
 
23
23
 
24
- class Test::Unit::TestCase #:nodoc:
24
+ class TestCaseClass #:nodoc:
25
25
  self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
26
26
  self.use_transactional_fixtures = true
27
27
  self.use_instantiated_fixtures = false
28
28
 
29
29
  fixtures :categories, :notes, :departments
30
- end
30
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: collectiveidea-awesome_nested_set
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Keepers
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-07-15 00:00:00 -07:00
13
+ date: 2009-08-11 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -41,10 +41,9 @@ files:
41
41
  - awesome_nested_set.gemspec
42
42
  - init.rb
43
43
  - lib/awesome_nested_set.rb
44
- - lib/awesome_nested_set/compatability.rb
45
44
  - lib/awesome_nested_set/helper.rb
46
- - lib/awesome_nested_set/named_scope.rb
47
45
  - rails/init.rb
46
+ - test/application.rb
48
47
  - test/awesome_nested_set/helper_test.rb
49
48
  - test/awesome_nested_set_test.rb
50
49
  - test/db/database.yml
@@ -56,6 +55,7 @@ files:
56
55
  - test/test_helper.rb
57
56
  has_rdoc: false
58
57
  homepage: http://github.com/collectiveidea/awesome_nested_set
58
+ licenses:
59
59
  post_install_message:
60
60
  rdoc_options:
61
61
  - --main
@@ -79,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
79
  requirements: []
80
80
 
81
81
  rubyforge_project:
82
- rubygems_version: 1.2.0
82
+ rubygems_version: 1.3.5
83
83
  signing_key:
84
84
  specification_version: 3
85
85
  summary: An awesome nested set implementation for Active Record
@@ -88,6 +88,7 @@ test_files:
88
88
  - test/fixtures/categories.yml
89
89
  - test/fixtures/departments.yml
90
90
  - test/fixtures/notes.yml
91
+ - test/application.rb
91
92
  - test/awesome_nested_set/helper_test.rb
92
93
  - test/awesome_nested_set_test.rb
93
94
  - test/db/schema.rb
@@ -1,29 +0,0 @@
1
- # Rails <2.x doesn't define #except
2
- class Hash #:nodoc:
3
- # Returns a new hash without the given keys.
4
- def except(*keys)
5
- clone.except!(*keys)
6
- end unless method_defined?(:except)
7
-
8
- # Replaces the hash without the given keys.
9
- def except!(*keys)
10
- keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
11
- keys.each { |key| delete(key) }
12
- self
13
- end unless method_defined?(:except!)
14
- end
15
-
16
- # NamedScope is new to Rails 2.1
17
- unless defined? ActiveRecord::NamedScope
18
- require 'awesome_nested_set/named_scope'
19
- ActiveRecord::Base.class_eval do
20
- include CollectiveIdea::NamedScope
21
- end
22
- end
23
-
24
- # Rails 1.2.x doesn't define #quoted_table_name
25
- class ActiveRecord::Base #:nodoc:
26
- def self.quoted_table_name
27
- self.connection.quote_column_name(self.table_name)
28
- end unless methods.include?('quoted_table_name')
29
- end
@@ -1,140 +0,0 @@
1
- # Taken from Rails 2.1
2
- module CollectiveIdea #:nodoc:
3
- module NamedScope #:nodoc:
4
- # All subclasses of ActiveRecord::Base have two named_scopes:
5
- # * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
6
- # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly:
7
- #
8
- # Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)
9
- #
10
- # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
11
- # intermediate values (scopes) around as first-class objects is convenient.
12
- def self.included(base)
13
- base.class_eval do
14
- extend ClassMethods
15
- named_scope :scoped, lambda { |scope| scope }
16
- end
17
- end
18
-
19
- module ClassMethods #:nodoc:
20
- def scopes
21
- read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
22
- end
23
-
24
- # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
25
- # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
26
- #
27
- # class Shirt < ActiveRecord::Base
28
- # named_scope :red, :conditions => {:color => 'red'}
29
- # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
30
- # end
31
- #
32
- # The above calls to <tt>named_scope</tt> define class methods <tt>Shirt.red</tt> and <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>,
33
- # in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
34
- #
35
- # Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
36
- # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
37
- # <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
38
- # as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
39
- # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
40
- #
41
- # These named scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
42
- # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
43
- # for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
44
- #
45
- # All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to
46
- # <tt>has_many</tt> associations. If,
47
- #
48
- # class Person < ActiveRecord::Base
49
- # has_many :shirts
50
- # end
51
- #
52
- # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
53
- # only shirts.
54
- #
55
- # Named scopes can also be procedural.
56
- #
57
- # class Shirt < ActiveRecord::Base
58
- # named_scope :colored, lambda { |color|
59
- # { :conditions => { :color => color } }
60
- # }
61
- # end
62
- #
63
- # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
64
- #
65
- # Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
66
- #
67
- # class Shirt < ActiveRecord::Base
68
- # named_scope :red, :conditions => {:color => 'red'} do
69
- # def dom_id
70
- # 'red_shirts'
71
- # end
72
- # end
73
- # end
74
- #
75
- #
76
- # For testing complex named scopes, you can examine the scoping options using the
77
- # <tt>proxy_options</tt> method on the proxy itself.
78
- #
79
- # class Shirt < ActiveRecord::Base
80
- # named_scope :colored, lambda { |color|
81
- # { :conditions => { :color => color } }
82
- # }
83
- # end
84
- #
85
- # expected_options = { :conditions => { :colored => 'red' } }
86
- # assert_equal expected_options, Shirt.colored('red').proxy_options
87
- def named_scope(name, options = {}, &block)
88
- scopes[name] = lambda do |parent_scope, *args|
89
- Scope.new(parent_scope, case options
90
- when Hash
91
- options
92
- when Proc
93
- options.call(*args)
94
- end, &block)
95
- end
96
- (class << self; self end).instance_eval do
97
- define_method name do |*args|
98
- scopes[name].call(self, *args)
99
- end
100
- end
101
- end
102
- end
103
-
104
- class Scope #:nodoc:
105
- attr_reader :proxy_scope, :proxy_options
106
- [].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
107
- delegate :scopes, :with_scope, :to => :proxy_scope
108
-
109
- def initialize(proxy_scope, options, &block)
110
- [options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
111
- extend Module.new(&block) if block_given?
112
- @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
113
- end
114
-
115
- def reload
116
- load_found; self
117
- end
118
-
119
- protected
120
- def proxy_found
121
- @found || load_found
122
- end
123
-
124
- private
125
- def method_missing(method, *args, &block)
126
- if scopes.include?(method)
127
- scopes[method].call(self, *args)
128
- else
129
- with_scope :find => proxy_options do
130
- proxy_scope.send(method, *args, &block)
131
- end
132
- end
133
- end
134
-
135
- def load_found
136
- @found = find(:all)
137
- end
138
- end
139
- end
140
- end