closure_tree 5.0.0 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 92930bde62d7c1d8460fcefb8964da22b6edca2b
4
- data.tar.gz: fe1eb67d373bda0ce3e4f7a404e521d7dce52bf7
3
+ metadata.gz: df0555778d166d7f54acabde46e6118f2990c6c5
4
+ data.tar.gz: 837a50c2039d612eac26a9d4bf4ec106729991b0
5
5
  SHA512:
6
- metadata.gz: e07b36bf6badfeb5d9035eddd650e821159a8963b71df737f8e42947401aecf7456a218fe34f787cad6f8ddbd341e11072fb4a23f1c96c8d45e0a8ba42290fae
7
- data.tar.gz: f767928d73fefcc76338fe0edb444f6b282b7249dfa808e7f406f4b04593f63696ecabd23e3b421b39f2ea09f9883b9f4000a91c23ab3e24bcadfd8955a0f813
6
+ metadata.gz: 223f1243db513bc4754d6894f971799a0b998642841f5fb536b7023e9b4b8437ce3cca6b9e750477534b04674c46e49e00544022a2b1186f4d1cd4e691c10c9d
7
+ data.tar.gz: 5ca1595f2c52d857902d51020cc354b035272a210c5e3d6748071a86840beecc65ae996d6001ca5d4883c51175a6d30aa9b60aa34f4e429c55942676cba7ee73
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ### 5.1.0
4
+
5
+ * [Abdelkader Boudih](https://github.com/seuros) added a database generator
6
+ for the hierarchies table. Thanks!
7
+ * [Jason Weathered](https://github.com/jasoncodes) fixed [issue #117](https://github.com/mceachen/closure_tree/pull/117)
8
+ with the preordered traversal code that assumed the primary key column was called `id`. Thanks!
9
+
3
10
  ### 5.0.0
4
11
 
5
12
  #### Breaking API changes
data/README.md CHANGED
@@ -27,7 +27,7 @@ closure_tree has some great features:
27
27
  * 3 SQL INSERT/UPDATEs on node reparenting
28
28
  * __Support for [concurrency](#concurrency)__ (using [with_advisory_lock](https://github.com/mceachen/with_advisory_lock))
29
29
  * __Support for Rails 3.2, 4.0, and 4.1__
30
- * __Support for Ruby 1.9, 2.1, and jRuby 1.6.13
30
+ * __Support for Ruby 1.9, 2.1, and jRuby 1.6.13__
31
31
  * Support for reparenting children (and all their descendants)
32
32
  * Support for [single-table inheritance (STI)](#sti) within the hierarchy
33
33
  * ```find_or_create_by_path``` for [building out heterogeneous hierarchies quickly and conveniently](#find_or_create_by_path)
@@ -56,16 +56,24 @@ for a description of different tree storage algorithms.
56
56
 
57
57
  Note that closure_tree only supports Rails 3.2 and later, and has test coverage for MySQL, PostgreSQL, and SQLite.
58
58
 
59
- 1. Add this to your Gemfile: ```gem 'closure_tree'```
59
+ 1. Add `gem 'closure_tree'` to your Gemfile
60
60
 
61
- 2. Run ```bundle install```
61
+ 2. Run `bundle install`
62
62
 
63
- 3. Add ```acts_as_tree``` to your hierarchical model(s).
64
- Make sure you add ```acts_as_tree``` *after any ```attr_accessible``` and ```self.table_name =```
65
- lines in your model.
66
- Please review the [available options](#available-options) you can provide.
63
+ 3. Add `acts_as_tree` to your hierarchical model:
67
64
 
68
- 4. Add a migration to add a ```parent_id``` column to the model you want to act_as_tree.
65
+ ```ruby
66
+ class Tag < ActiveRecord::Base
67
+ acts_as_tree
68
+ end
69
+ ```
70
+
71
+ Make sure you check out the [large number options](#available-options) that `acts_as_tree` accepts.
72
+
73
+ Make sure you add `acts_as_tree` **after** `attr_accessible` and
74
+ `self.table_name =` lines in your model.
75
+
76
+ 4. Add a migration to add a `parent_id` column to the hierarchical model.
69
77
  You may want to also [add a column for deterministic ordering of children](#sort_order), but that's optional.
70
78
 
71
79
  ```ruby
@@ -76,40 +84,22 @@ Note that closure_tree only supports Rails 3.2 and later, and has test coverage
76
84
  end
77
85
  ```
78
86
 
79
- Note that if the column is null, the tag will be considered a root node.
87
+ The column must be nullable. Root nodes have a `NULL` `parent_id`.
80
88
 
81
- 5. Add a database migration to store the hierarchy for your model. By
82
- default the table name will be the model's table name, followed by
83
- "_hierarchies". Note that by calling ```acts_as_tree```, a "virtual model" (in this case, ```TagHierarchy```)
84
- will be added automatically, so you don't need to create it.
89
+ 5. Run `rails g closure_tree:migration tag` (and replace `tag` with your model name)
90
+ to create the closure tree table for your model.
85
91
 
86
- ```ruby
87
- class CreateTagHierarchies < ActiveRecord::Migration
88
- def change
89
- create_table :tag_hierarchies, :id => false do |t|
90
- t.integer :ancestor_id, :null => false # ID of the parent/grandparent/great-grandparent/... tag
91
- t.integer :descendant_id, :null => false # ID of the target tag
92
- t.integer :generations, :null => false # Number of generations between the ancestor and the descendant. Parent/child = 1, for example.
93
- end
94
-
95
- # For "all progeny of…" and leaf selects:
96
- add_index :tag_hierarchies, [:ancestor_id, :descendant_id, :generations],
97
- :unique => true, :name => "tag_anc_desc_udx"
98
-
99
- # For "all ancestors of…" selects,
100
- add_index :tag_hierarchies, [:descendant_id],
101
- :name => "tag_desc_idx"
102
- end
103
- end
104
- ```
92
+ By default the table name will be the model's table name, followed by
93
+ "_hierarchies". Note that by calling ```acts_as_tree```, a "virtual model" (in this case, ```TagHierarchy```)
94
+ will be created dynamically. You don't need to create it.
105
95
 
106
- 6. Run ```rake db:migrate```
96
+ 6. Run `rake db:migrate`
107
97
 
108
98
  7. If you're migrating from another system where your model already has a
109
- ```parent_id``` column, run ```Tag.rebuild!``` and your
110
- ```tag_hierarchies``` table will be truncated and rebuilt.
99
+ `parent_id` column, run `Tag.rebuild!` and your
100
+ `tag_hierarchies` table will be truncated and rebuilt.
111
101
 
112
- If you're starting from scratch you don't need to call ```rebuild!```.
102
+ If you're starting from scratch you don't need to call `rebuild!`.
113
103
 
114
104
  ## Usage
115
105
 
@@ -118,26 +108,26 @@ Note that closure_tree only supports Rails 3.2 and later, and has test coverage
118
108
  Create a root node:
119
109
 
120
110
  ```ruby
121
- grandparent = Tag.create(:name => 'Grandparent')
111
+ grandparent = Tag.create(name: 'Grandparent')
122
112
  ```
123
113
 
124
114
  Child nodes are created by appending to the children collection:
125
115
 
126
116
  ```ruby
127
- parent = grandparent.children.create(:name => 'Parent')
117
+ parent = grandparent.children.create(name: 'Parent')
128
118
  ```
129
119
 
130
120
  Or by appending to the children collection:
131
121
 
132
122
  ```ruby
133
- child2 = Tag.new(:name => 'Second Child')
123
+ child2 = Tag.new(name: 'Second Child')
134
124
  parent.children << child2
135
125
  ```
136
126
 
137
127
  Or by calling the "add_child" method:
138
128
 
139
129
  ```ruby
140
- child3 = Tag.new(:name => 'Third Child')
130
+ child3 = Tag.new(name: 'Third Child')
141
131
  parent.add_child child3
142
132
  ```
143
133
 
@@ -153,22 +143,22 @@ child1.ancestry_path
153
143
 
154
144
  ### find_or_create_by_path
155
145
 
156
- You can ```find``` as well as ```find_or_create``` by "ancestry paths".
146
+ You can `find` as well as `find_or_create` by "ancestry paths".
157
147
 
158
148
  If you provide an array of strings to these methods, they reference the `name` column in your
159
- model, which can be overridden with the `:name_column` option provided to ```acts_as_tree```.
149
+ model, which can be overridden with the `:name_column` option provided to `acts_as_tree`.
160
150
 
161
151
  ```ruby
162
- child = Tag.find_or_create_by_path(["grandparent", "parent", "child"])
152
+ child = Tag.find_or_create_by_path(%w[grandparent parent child])
163
153
  ```
164
154
 
165
155
  As of v5.0.0, `find_or_create_by_path` can also take an array of attribute hashes:
166
156
 
167
157
  ```ruby
168
158
  child = Tag.find_or_create_by_path([
169
- {name: "Grandparent", title: "Sr."},
170
- {name: "Parent", title: "Mrs."},
171
- {name: "Child", title: "Jr."}
159
+ {name: 'Grandparent', title: 'Sr.'},
160
+ {name: 'Parent', title: 'Mrs.'},
161
+ {name: 'Child', title: 'Jr.'}
172
162
  ])
173
163
  ```
174
164
 
@@ -189,8 +179,8 @@ Nodes can be moved around to other parents, and closure_tree moves the node's de
189
179
  new parent for you:
190
180
 
191
181
  ```ruby
192
- d = Tag.find_or_create_by_path %w(a b c d)
193
- h = Tag.find_or_create_by_path %w(e f g h)
182
+ d = Tag.find_or_create_by_path %w[a b c d]
183
+ h = Tag.find_or_create_by_path %w[e f g h]
194
184
  e = h.root
195
185
  d.add_child(e) # "d.children << e" would work too, of course
196
186
  h.ancestry_path
@@ -486,7 +476,7 @@ the spec ```tag_spec.rb```:
486
476
  Tag.rebuild! # <- required if you use fixtures
487
477
  end
488
478
  ```
489
-
479
+ `
490
480
  **However, if you're just starting with Rails, may I humbly suggest you adopt a factory library**,
491
481
  rather than using fixtures? [Lots of people have written about this already](https://www.google.com/search?q=fixtures+versus+factories).
492
482
 
@@ -38,14 +38,14 @@ module ClosureTree
38
38
  JOIN #{_ct.quoted_hierarchy_table_name} anc_hier
39
39
  ON anc_hier.descendant_id = #{_ct.quoted_hierarchy_table_name}.descendant_id
40
40
  JOIN #{_ct.quoted_table_name} anc
41
- ON anc.id = anc_hier.ancestor_id
41
+ ON anc.#{_ct.quoted_id_column_name} = anc_hier.ancestor_id
42
42
  JOIN #{_ct.quoted_hierarchy_table_name} depths
43
- ON depths.ancestor_id = #{_ct.quote(self.id)} AND depths.descendant_id = anc.id
43
+ ON depths.ancestor_id = #{_ct.quote(self.id)} AND depths.descendant_id = anc.#{_ct.quoted_id_column_name}
44
44
  SQL
45
45
  node_score = "(1 + anc.#{_ct.quoted_order_column(false)}) * " +
46
46
  "power(#{h['total_descendants']}, #{h['max_depth'].to_i + 1} - depths.generations)"
47
47
  order_by = "sum(#{node_score})"
48
- self_and_descendants.joins(join_sql).group("#{_ct.quoted_table_name}.id").reorder(order_by)
48
+ self_and_descendants.joins(join_sql).group("#{_ct.quoted_table_name}.#{_ct.quoted_id_column_name}").reorder(order_by)
49
49
  end
50
50
 
51
51
  module ClassMethods
@@ -58,19 +58,19 @@ module ClosureTree
58
58
  SQL
59
59
  join_sql = <<-SQL.strip_heredoc
60
60
  JOIN #{_ct.quoted_hierarchy_table_name} anc_hier
61
- ON anc_hier.descendant_id = #{_ct.quoted_table_name}.id
61
+ ON anc_hier.descendant_id = #{_ct.quoted_table_name}.#{_ct.quoted_id_column_name}
62
62
  JOIN #{_ct.quoted_table_name} anc
63
- ON anc.id = anc_hier.ancestor_id
63
+ ON anc.#{_ct.quoted_id_column_name} = anc_hier.ancestor_id
64
64
  JOIN (
65
65
  SELECT descendant_id, max(generations) AS max_depth
66
66
  FROM #{_ct.quoted_hierarchy_table_name}
67
67
  GROUP BY 1
68
- ) AS depths ON depths.descendant_id = anc.id
68
+ ) AS depths ON depths.descendant_id = anc.#{_ct.quoted_id_column_name}
69
69
  SQL
70
70
  node_score = "(1 + anc.#{_ct.quoted_order_column(false)}) * " +
71
71
  "power(#{h['total_descendants']}, #{h['max_depth'].to_i + 1} - depths.max_depth)"
72
72
  order_by = "sum(#{node_score})"
73
- joins(join_sql).group("#{_ct.quoted_table_name}.id").reorder(order_by)
73
+ joins(join_sql).group("#{_ct.quoted_table_name}.#{_ct.quoted_id_column_name}").reorder(order_by)
74
74
  end
75
75
  end
76
76
 
@@ -21,6 +21,14 @@ module ClosureTree
21
21
  options[:hierarchy_class_name] || model_class.to_s + "Hierarchy"
22
22
  end
23
23
 
24
+ def primary_key_column
25
+ model_class.columns.detect { |ea| ea.name == model_class.primary_key }
26
+ end
27
+
28
+ def primary_key_type
29
+ primary_key_column.type
30
+ end
31
+
24
32
  def parent_column_name
25
33
  options[:parent_column_name]
26
34
  end
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = Gem::Version.new('5.0.0') unless defined?(::ClosureTree::VERSION)
2
+ VERSION = Gem::Version.new('5.1.0') unless defined?(::ClosureTree::VERSION)
3
3
  end
@@ -0,0 +1,30 @@
1
+ require 'rails/generators/named_base'
2
+ require 'rails/generators/active_record/migration'
3
+ require 'forwardable'
4
+
5
+ module ClosureTree
6
+ module Generators # :nodoc:
7
+ class MigrationGenerator < ::Rails::Generators::NamedBase # :nodoc:
8
+ include ActiveRecord::Generators::Migration
9
+
10
+ extend Forwardable
11
+ def_delegators :ct, :hierarchy_table_name, :primary_key_type
12
+
13
+ def self.default_generator_root
14
+ File.dirname(__FILE__)
15
+ end
16
+
17
+ def create_migration_file
18
+ migration_template 'create_hierarchies_table.rb.erb', "db/migrate/create_#{singular_table_name}_hierarchies.rb"
19
+ end
20
+
21
+ def migration_class_name
22
+ "Create#{ct.hierarchy_class_name}".gsub(/\W/, '')
23
+ end
24
+
25
+ def ct
26
+ @ct ||= class_name.constantize._ct
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def change
3
+ create_table :<%= hierarchy_table_name %>, id: false do |t|
4
+ t.<%= primary_key_type %> :ancestor_id, null: false
5
+ t.<%= primary_key_type %> :descendant_id, null: false
6
+ t.integer :generations, null: false
7
+ end
8
+
9
+ add_index :<%= hierarchy_table_name %>, [:ancestor_id, :descendant_id, :generations],
10
+ unique: true,
11
+ name: "anc_desc_idx"
12
+
13
+ add_index :<%= hierarchy_table_name -%>, [:descendant_id],
14
+ name: "desc_idx"
15
+ end
16
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: closure_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew McEachen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-08 00:00:00.000000000 Z
11
+ date: 2014-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -203,6 +203,8 @@ files:
203
203
  - lib/closure_tree/support_flags.rb
204
204
  - lib/closure_tree/test/matcher.rb
205
205
  - lib/closure_tree/version.rb
206
+ - lib/generators/closure_tree/migration_generator.rb
207
+ - lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb
206
208
  - mktree.rb
207
209
  - spec/cache_invalidation_spec.rb
208
210
  - spec/cuisine_type_spec.rb