##// END OF EJS Templates
awesome_nested_set: import git 2-1-stable branch revision 3d5ac746542b564 (#6579)...
Toshi MARUYAMA -
r12869:e22c5c3f80df
parent child
Show More
@@ -1,19 +1,18
1 language: ruby
1 language: ruby
2 script: bundle exec rspec spec
2 script: bundle exec rspec spec
3 env:
3 env:
4 - DB=sqlite3
4 - DB=sqlite3
5 - DB=sqlite3mem
5 - DB=sqlite3mem
6 - DB=postgresql
6 - DB=postgresql
7 - DB=mysql
7 - DB=mysql
8 rvm:
8 rvm:
9 - 2.0.0
9 - 2.0.0
10 - 1.9.3
10 - 1.9.3
11 - rbx-19mode
11 - rbx
12 - jruby-19mode
12 - jruby-19mode
13 - 1.8.7
13 - 1.8.7
14 - rbx-18mode
15 - jruby-18mode
14 - jruby-18mode
16 gemfile:
15 gemfile:
17 - gemfiles/Gemfile.rails-3.0.rb
16 - gemfiles/Gemfile.rails-3.0.rb
18 - gemfiles/Gemfile.rails-3.1.rb
17 - gemfiles/Gemfile.rails-3.1.rb
19 - gemfiles/Gemfile.rails-3.2.rb
18 - gemfiles/Gemfile.rails-3.2.rb
@@ -1,32 +1,38
1 gem 'combustion', :github => 'pat/combustion', :branch => 'master'
1 gem 'combustion', :github => 'pat/combustion', :branch => 'master'
2
2
3 source 'https://rubygems.org'
3 source 'https://rubygems.org'
4
4
5 gemspec :path => File.expand_path('../', __FILE__)
5 gemspec :path => File.expand_path('../', __FILE__)
6
6
7 platforms :jruby do
7 platforms :jruby do
8 gem 'activerecord-jdbcsqlite3-adapter'
8 gem 'activerecord-jdbcsqlite3-adapter'
9 gem 'activerecord-jdbcmysql-adapter'
9 gem 'activerecord-jdbcmysql-adapter'
10 gem 'jdbc-mysql'
10 gem 'jdbc-mysql'
11 gem 'activerecord-jdbcpostgresql-adapter'
11 gem 'activerecord-jdbcpostgresql-adapter'
12 gem 'jruby-openssl'
12 gem 'jruby-openssl'
13 end
13 end
14
14
15 platforms :ruby do
15 platforms :ruby do
16 gem 'sqlite3'
16 gem 'sqlite3'
17 gem 'mysql2', (MYSQL2_VERSION if defined? MYSQL2_VERSION)
17 gem 'mysql2', (MYSQL2_VERSION if defined? MYSQL2_VERSION)
18 gem 'pg'
18 gem 'pg'
19 end
19 end
20
20
21 RAILS_VERSION = nil unless defined? RAILS_VERSION
21 RAILS_VERSION = nil unless defined? RAILS_VERSION
22 gem 'railties', RAILS_VERSION
22 gem 'railties', RAILS_VERSION
23 gem 'activerecord', RAILS_VERSION
23 gem 'activerecord', RAILS_VERSION
24 gem 'actionpack', RAILS_VERSION
24 gem 'actionpack', RAILS_VERSION
25
25
26 platforms :rbx do
27 gem 'rubysl', '~> 2.0'
28 gem 'rubysl-test-unit'
29 end
30
31
26 # Add Oracle Adapters
32 # Add Oracle Adapters
27 # gem 'ruby-oci8'
33 # gem 'ruby-oci8'
28 # gem 'activerecord-oracle_enhanced-adapter'
34 # gem 'activerecord-oracle_enhanced-adapter'
29
35
30 # Debuggers
36 # Debuggers
31 gem 'pry'
37 gem 'pry'
32 gem 'pry-nav'
38 gem 'pry-nav'
@@ -1,133 +1,135
1 require 'awesome_nested_set/columns'
1 require 'awesome_nested_set/columns'
2 require 'awesome_nested_set/model'
2 require 'awesome_nested_set/model'
3
3
4 module CollectiveIdea #:nodoc:
4 module CollectiveIdea #:nodoc:
5 module Acts #:nodoc:
5 module Acts #:nodoc:
6 module NestedSet #:nodoc:
6 module NestedSet #:nodoc:
7
7
8 # This acts provides Nested Set functionality. Nested Set is a smart way to implement
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
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
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!
11 # sql queries. But everything is done here by this module!
12 #
12 #
13 # Nested sets are appropriate each time you want either an orderd tree (menus,
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).
14 # commercial categories) or an efficient way of querying big trees (threaded posts).
15 #
15 #
16 # == API
16 # == API
17 #
17 #
18 # Methods names are aligned with acts_as_tree as much as possible to make replacment from one
18 # Methods names are aligned with acts_as_tree as much as possible to make replacment from one
19 # by another easier.
19 # by another easier.
20 #
20 #
21 # item.children.create(:name => "child1")
21 # item.children.create(:name => "child1")
22 #
22 #
23
23
24 # Configuration options are:
24 # Configuration options are:
25 #
25 #
26 # * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
26 # * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
27 # * +:left_column+ - column name for left boundry data, default "lft"
27 # * +:left_column+ - column name for left boundry data, default "lft"
28 # * +:right_column+ - column name for right boundry data, default "rgt"
28 # * +:right_column+ - column name for right boundry data, default "rgt"
29 # * +:depth_column+ - column name for the depth data, default "depth"
29 # * +:depth_column+ - column name for the depth data, default "depth"
30 # * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
30 # * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
31 # (if it hasn't been already) and use that as the foreign key restriction. You
31 # (if it hasn't been already) and use that as the foreign key restriction. You
32 # can also pass an array to scope by multiple attributes.
32 # can also pass an array to scope by multiple attributes.
33 # Example: <tt>acts_as_nested_set :scope => [:notable_id, :notable_type]</tt>
33 # Example: <tt>acts_as_nested_set :scope => [:notable_id, :notable_type]</tt>
34 # * +:dependent+ - behavior for cascading destroy. If set to :destroy, all the
34 # * +:dependent+ - behavior for cascading destroy. If set to :destroy, all the
35 # child objects are destroyed alongside this object by calling their destroy
35 # child objects are destroyed alongside this object by calling their destroy
36 # method. If set to :delete_all (default), all the child objects are deleted
36 # method. If set to :delete_all (default), all the child objects are deleted
37 # without calling their destroy method.
37 # without calling their destroy method.
38 # * +:counter_cache+ adds a counter cache for the number of children.
38 # * +:counter_cache+ adds a counter cache for the number of children.
39 # defaults to false.
39 # defaults to false.
40 # Example: <tt>acts_as_nested_set :counter_cache => :children_count</tt>
40 # Example: <tt>acts_as_nested_set :counter_cache => :children_count</tt>
41 # * +:order_column+ on which column to do sorting, by default it is the left_column_name
41 # * +:order_column+ on which column to do sorting, by default it is the left_column_name
42 # Example: <tt>acts_as_nested_set :order_column => :position</tt>
42 # Example: <tt>acts_as_nested_set :order_column => :position</tt>
43 #
43 #
44 # See CollectiveIdea::Acts::NestedSet::Model::ClassMethods for a list of class methods and
44 # See CollectiveIdea::Acts::NestedSet::Model::ClassMethods for a list of class methods and
45 # CollectiveIdea::Acts::NestedSet::Model for a list of instance methods added
45 # CollectiveIdea::Acts::NestedSet::Model for a list of instance methods added
46 # to acts_as_nested_set models
46 # to acts_as_nested_set models
47 def acts_as_nested_set(options = {})
47 def acts_as_nested_set(options = {})
48 acts_as_nested_set_parse_options! options
48 acts_as_nested_set_parse_options! options
49
49
50 include Model
50 include Model
51 include Columns
51 include Columns
52 extend Columns
52 extend Columns
53
53
54 acts_as_nested_set_relate_parent!
54 acts_as_nested_set_relate_parent!
55 acts_as_nested_set_relate_children!
55 acts_as_nested_set_relate_children!
56
56
57 attr_accessor :skip_before_destroy
57 attr_accessor :skip_before_destroy
58
58
59 acts_as_nested_set_prevent_assignment_to_reserved_columns!
59 acts_as_nested_set_prevent_assignment_to_reserved_columns!
60 acts_as_nested_set_define_callbacks!
60 acts_as_nested_set_define_callbacks!
61 end
61 end
62
62
63 private
63 private
64 def acts_as_nested_set_define_callbacks!
64 def acts_as_nested_set_define_callbacks!
65 # on creation, set automatically lft and rgt to the end of the tree
65 # on creation, set automatically lft and rgt to the end of the tree
66 before_create :set_default_left_and_right
66 before_create :set_default_left_and_right
67 before_save :store_new_parent
67 before_save :store_new_parent
68 after_save :move_to_new_parent, :set_depth!
68 after_save :move_to_new_parent, :set_depth!
69 before_destroy :destroy_descendants
69 before_destroy :destroy_descendants
70
70
71 define_model_callbacks :move
71 define_model_callbacks :move
72 end
72 end
73
73
74 def acts_as_nested_set_relate_children!
74 def acts_as_nested_set_relate_children!
75 has_many_children_options = {
75 has_many_children_options = {
76 :class_name => self.base_class.to_s,
76 :class_name => self.base_class.to_s,
77 :foreign_key => parent_column_name,
77 :foreign_key => parent_column_name,
78 :order => quoted_order_column_name,
78 :order => quoted_order_column_name,
79 :inverse_of => (:parent unless acts_as_nested_set_options[:polymorphic]),
79 :inverse_of => (:parent unless acts_as_nested_set_options[:polymorphic]),
80 }
80 }
81
81
82 # Add callbacks, if they were supplied.. otherwise, we don't want them.
82 # Add callbacks, if they were supplied.. otherwise, we don't want them.
83 [:before_add, :after_add, :before_remove, :after_remove].each do |ar_callback|
83 [:before_add, :after_add, :before_remove, :after_remove].each do |ar_callback|
84 has_many_children_options.update(ar_callback => acts_as_nested_set_options[ar_callback]) if acts_as_nested_set_options[ar_callback]
84 has_many_children_options.update(ar_callback => acts_as_nested_set_options[ar_callback]) if acts_as_nested_set_options[ar_callback]
85 end
85 end
86
86
87 has_many :children, has_many_children_options
87 has_many :children, has_many_children_options
88 end
88 end
89
89
90 def acts_as_nested_set_relate_parent!
90 def acts_as_nested_set_relate_parent!
91 belongs_to :parent, :class_name => self.base_class.to_s,
91 belongs_to :parent, :class_name => self.base_class.to_s,
92 :foreign_key => parent_column_name,
92 :foreign_key => parent_column_name,
93 :counter_cache => acts_as_nested_set_options[:counter_cache],
93 :counter_cache => acts_as_nested_set_options[:counter_cache],
94 :inverse_of => (:children unless acts_as_nested_set_options[:polymorphic]),
94 :inverse_of => (:children unless acts_as_nested_set_options[:polymorphic]),
95 :polymorphic => acts_as_nested_set_options[:polymorphic]
95 :polymorphic => acts_as_nested_set_options[:polymorphic],
96 :touch => acts_as_nested_set_options[:touch]
96 end
97 end
97
98
98 def acts_as_nested_set_default_options
99 def acts_as_nested_set_default_options
99 {
100 {
100 :parent_column => 'parent_id',
101 :parent_column => 'parent_id',
101 :left_column => 'lft',
102 :left_column => 'lft',
102 :right_column => 'rgt',
103 :right_column => 'rgt',
103 :depth_column => 'depth',
104 :depth_column => 'depth',
104 :dependent => :delete_all, # or :destroy
105 :dependent => :delete_all, # or :destroy
105 :polymorphic => false,
106 :polymorphic => false,
106 :counter_cache => false
107 :counter_cache => false,
108 :touch => false
107 }.freeze
109 }.freeze
108 end
110 end
109
111
110 def acts_as_nested_set_parse_options!(options)
112 def acts_as_nested_set_parse_options!(options)
111 options = acts_as_nested_set_default_options.merge(options)
113 options = acts_as_nested_set_default_options.merge(options)
112
114
113 if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
115 if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
114 options[:scope] = "#{options[:scope]}_id".intern
116 options[:scope] = "#{options[:scope]}_id".intern
115 end
117 end
116
118
117 class_attribute :acts_as_nested_set_options
119 class_attribute :acts_as_nested_set_options
118 self.acts_as_nested_set_options = options
120 self.acts_as_nested_set_options = options
119 end
121 end
120
122
121 def acts_as_nested_set_prevent_assignment_to_reserved_columns!
123 def acts_as_nested_set_prevent_assignment_to_reserved_columns!
122 # no assignment to structure fields
124 # no assignment to structure fields
123 [left_column_name, right_column_name, depth_column_name].each do |column|
125 [left_column_name, right_column_name, depth_column_name].each do |column|
124 module_eval <<-"end_eval", __FILE__, __LINE__
126 module_eval <<-"end_eval", __FILE__, __LINE__
125 def #{column}=(x)
127 def #{column}=(x)
126 raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
128 raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
127 end
129 end
128 end_eval
130 end_eval
129 end
131 end
130 end
132 end
131 end
133 end
132 end
134 end
133 end
135 end
@@ -1,41 +1,43
1 require 'awesome_nested_set/tree'
1 require 'awesome_nested_set/tree'
2
2
3 module CollectiveIdea
3 module CollectiveIdea
4 module Acts
4 module Acts
5 module NestedSet
5 module NestedSet
6 module Model
6 module Model
7 module Rebuildable
7 module Rebuildable
8
8
9
9
10 # Rebuilds the left & rights if unset or invalid.
10 # Rebuilds the left & rights if unset or invalid.
11 # Also very useful for converting from acts_as_tree.
11 # Also very useful for converting from acts_as_tree.
12 def rebuild!(validate_nodes = true)
12 def rebuild!(validate_nodes = true)
13 # default_scope with order may break database queries so we do all operation without scope
13 # default_scope with order may break database queries so we do all operation without scope
14 unscoped do
14 unscoped do
15 Tree.new(self, validate_nodes).rebuild!
15 Tree.new(self, validate_nodes).rebuild!
16 end
16 end
17 end
17 end
18
18
19 private
19 private
20 def scope_for_rebuild
20 def scope_for_rebuild
21 scope = proc {}
21 scope = proc {}
22
22
23 if acts_as_nested_set_options[:scope]
23 if acts_as_nested_set_options[:scope]
24 scope = proc {|node|
24 scope = proc {|node|
25 scope_column_names.inject("") {|str, column_name|
25 scope_column_names.inject("") {|str, column_name|
26 str << "AND #{connection.quote_column_name(column_name)} = #{connection.quote(node.send(column_name))} "
26 column_value = node.send(column_name)
27 cond = column_value.nil? ? "IS NULL" : "= #{connection.quote(column_value)}"
28 str << "AND #{connection.quote_column_name(column_name)} #{cond} "
27 }
29 }
28 }
30 }
29 end
31 end
30 scope
32 scope
31 end
33 end
32
34
33 def order_for_rebuild
35 def order_for_rebuild
34 "#{quoted_left_column_full_name}, #{quoted_right_column_full_name}, #{primary_key}"
36 "#{quoted_left_column_full_name}, #{quoted_right_column_full_name}, #{primary_key}"
35 end
37 end
36 end
38 end
37
39
38 end
40 end
39 end
41 end
40 end
42 end
41 end
43 end
General Comments 0
You need to be logged in to leave comments. Login now