closure_tree 5.0.0 → 5.1.0

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