closure_tree 1.0.0.beta1 → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
@@ -34,7 +34,7 @@ Note that closure_tree is being developed for Rails 3.1.0.rc1
34
34
 
35
35
  class CreateTagHierarchy < ActiveRecord::Migration
36
36
  def change
37
- create_table :tags_hierarchy do |t|
37
+ create_table :tags_hierarchy, :id => false do |t|
38
38
  t.integer :ancestor_id, :null => false # ID of the parent/grandparent/great-grandparent/... tag
39
39
  t.integer :descendant_id, :null => false # ID of the target tag
40
40
  t.integer :generations, :null => false # Number of generations between the ancestor and the descendant. Parent/child = 1, for example.
@@ -79,6 +79,18 @@ Then:
79
79
  puts grandparent.self_and_descendants.collect{ |t| t.name }.join(" > ")
80
80
  "grandparent > parent > child"
81
81
 
82
+ child.ancestry_names
83
+ ["grandparent", "parent", "child"]
84
+
85
+ === <code>find_or_create_by_path</code>
86
+
87
+ If your model has a single string column that can build a given node (like a "name" or a "title"),
88
+ you can use that to find_or_create:
89
+
90
+ Tag.find_or_create_by_path %w{one two three}
91
+
92
+ You can customize the column name with the :name_column option in <code>acts_as_tree</code>.
93
+
82
94
  == Accessing Data
83
95
 
84
96
  === Class methods
@@ -6,7 +6,8 @@ module ClosureTree #:nodoc:
6
6
  self.closure_tree_options = {
7
7
  :parent_column_name => 'parent_id',
8
8
  :dependent => :delete_all, # or :destroy
9
- :hierarchy_table_suffix => '_hierarchies'
9
+ :hierarchy_table_suffix => '_hierarchies',
10
+ :name_column => 'name'
10
11
  }.merge(options)
11
12
 
12
13
  include ClosureTree::Columns
@@ -90,6 +91,12 @@ module ClosureTree #:nodoc:
90
91
  [self].concat ancestors.to_a
91
92
  end
92
93
 
94
+ # Returns an array, root first, of self_and_ancestors' +name_column+ values
95
+ # (so child.ancestry_names == +%w{grandparent parent child}+
96
+ def ancestry_names
97
+ self_and_ancestors.reverse.collect { |n| n.send name_column.to_sym }
98
+ end
99
+
93
100
  def self_and_descendants
94
101
  [self].concat descendants.to_a
95
102
  end
@@ -121,8 +128,26 @@ module ClosureTree #:nodoc:
121
128
  new_parent.add_child self
122
129
  end
123
130
 
131
+ # Find a child node whose +ancestry_names+ minus self.ancestry_names is +path+
132
+ def find_by_path path
133
+ _find_or_create_by_path path, "find_by_#{name_column}".to_sym
134
+ end
135
+
136
+ # Find a child node whose +ancestry_names+ minus self.ancestry_names is +path+
137
+ def find_or_create_by_path path
138
+ _find_or_create_by_path path, "find_or_create_by_#{name_column}".to_sym
139
+ end
140
+
124
141
  protected
125
142
 
143
+ def _find_or_create_by_path path, methodname
144
+ node = self
145
+ while (name = path.shift and node)
146
+ node = node.children.send(methodname, name)
147
+ end
148
+ node
149
+ end
150
+
126
151
  def without_self(scope)
127
152
  scope.where(["#{quoted_table_name}.#{self.class.primary_key} != ?", self])
128
153
  end
@@ -145,6 +170,29 @@ module ClosureTree #:nodoc:
145
170
  nil
146
171
  end
147
172
 
173
+ # Find the node whose +ancestry_names+ is +path+
174
+ def find_by_path path
175
+ self.where(name_sym => path.last).each do |n|
176
+ return n if path == n.ancestry_names
177
+ end
178
+ nil
179
+ end
180
+
181
+ # Find or create nodes such that the +ancestry_names+ is +path+
182
+ def find_or_create_by_path path
183
+ # short-circuit if we can:
184
+ n = find_by_path path
185
+ return n if n
186
+
187
+ name = path.shift
188
+ node = roots.where(name_sym => name).first
189
+ node = create!(name_sym => name) unless node
190
+ while (name = path.shift)
191
+ node = node.children.send("find_or_create_by_#{name_column}".to_sym, name)
192
+ end
193
+ node
194
+ end
195
+
148
196
  private
149
197
  def rebuild_node_and_children node
150
198
  node.parent.add_child node if node.parent
@@ -162,6 +210,18 @@ module ClosureTree #:nodoc:
162
210
  closure_tree_options[:parent_column_name]
163
211
  end
164
212
 
213
+ def has_name?
214
+ ct_class.new.attributes.include? closure_tree_options[:name_column]
215
+ end
216
+
217
+ def name_column
218
+ closure_tree_options[:name_column]
219
+ end
220
+
221
+ def name_sym
222
+ name_column.to_sym
223
+ end
224
+
165
225
  def hierarchy_table_name
166
226
  ct_table_name + closure_tree_options[:hierarchy_table_suffix]
167
227
  end
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = "1.0.0.beta1" unless defined?(::ClosureTree::VERSION)
2
+ VERSION = "1.0.0.beta2" unless defined?(::ClosureTree::VERSION)
3
3
  end
@@ -28,6 +28,12 @@ test:
28
28
  password:
29
29
  socket: /tmp/mysql.sock
30
30
 
31
+ test_sqlite:
32
+ adapter: sqlite3
33
+ database: db/test.sqlite3
34
+ pool: 5
35
+ timeout: 5000
36
+
31
37
  production:
32
38
  adapter: mysql2
33
39
  encoding: utf8
@@ -56,6 +56,29 @@ class TagTest < ActiveSupport::TestCase
56
56
  assert_equal [tags(:child), tags(:parent), tags(:grandparent)], tags(:child).self_and_ancestors
57
57
  end
58
58
 
59
+ def test_ancestry_path
60
+ assert_equal %w{grandparent parent child}, tags(:child).ancestry_names
61
+ end
62
+
63
+ def test_find_by_path
64
+ # class method:
65
+ assert_equal tags(:child), Tag.find_by_path(%w{grandparent parent child})
66
+ assert_equal tags(:child), Tag.find_or_create_by_path(%w{grandparent parent child})
67
+ # instance method:
68
+ assert_equal tags(:child), tags(:parent).find_by_path(%w{child})
69
+ assert_equal tags(:child), tags(:grandparent).find_by_path(%w{parent child})
70
+ assert_nil tags(:parent).find_by_path(%w{child larvae})
71
+ end
72
+
73
+ def test_find_or_create_by_path
74
+ # class method:
75
+ assert_equal %w{events anniversary}, Tag.find_or_create_by_path(%w{events anniversary}).ancestry_names
76
+ a = Tag.find_or_create_by_path(%w{a})
77
+ assert_equal %w{a}, a.ancestry_names
78
+ # instance method:
79
+ assert_equal %w{a b c}, a.find_or_create_by_path(%w{b c}).ancestry_names
80
+ end
81
+
59
82
  def test_descendants
60
83
  assert_equal [tags(:child)], tags(:parent).descendants
61
84
  assert_equal [tags(:parent), tags(:child)], tags(:parent).self_and_descendants
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: closure_tree
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease: 6
5
- version: 1.0.0.beta1
5
+ version: 1.0.0.beta2
6
6
  platform: ruby
7
7
  authors: []
8
8
 
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-05-24 00:00:00 -07:00
13
+ date: 2011-05-25 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -73,7 +73,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
73
73
  requirements:
74
74
  - - ">="
75
75
  - !ruby/object:Gem::Version
76
- hash: 170453244524217629
76
+ hash: -1571970483886197148
77
77
  segments:
78
78
  - 0
79
79
  version: "0"