closure_tree 1.0.0.beta1 → 1.0.0.beta2

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.
@@ -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"