closure_tree 5.1.1 → 5.2.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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +5 -2
- data/Appraisals +4 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +1 -1
- data/README.md +42 -17
- data/Rakefile +20 -10
- data/closure_tree.gemspec +3 -3
- data/gemfiles/activerecord_3.2.gemfile +1 -1
- data/gemfiles/activerecord_4.0.gemfile +1 -1
- data/gemfiles/activerecord_4.1.gemfile +1 -1
- data/gemfiles/activerecord_4.2.gemfile +20 -0
- data/gemfiles/activerecord_edge.gemfile +1 -1
- data/lib/closure_tree.rb +2 -2
- data/lib/closure_tree/active_record_support.rb +20 -0
- data/lib/closure_tree/{acts_as_tree.rb → has_closure_tree.rb} +4 -4
- data/lib/closure_tree/support.rb +3 -9
- data/lib/closure_tree/version.rb +1 -1
- data/lib/generators/closure_tree/migration_generator.rb +27 -8
- data/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb +5 -5
- data/spec/db/models.rb +8 -5
- data/spec/generators/migration_generator_spec.rb +48 -0
- data/spec/parallel_spec.rb +18 -9
- data/tests.sh +3 -4
- metadata +24 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4e2702f6eb10fd2f2b7c9c9347e1bfbff2b97ea
|
4
|
+
data.tar.gz: 472bf0e636274ee25de4c88b64a1684ea627fb0b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec12064f1459d2f72e4d0b04af9392426dcf463b09abcf5053eaf598c54b917e0ed6e29509c04a9dbc04a574ace5ede33199527cbdbcde028f44a611d5d7a7c8
|
7
|
+
data.tar.gz: 1553a1b5bbf0bf4ffce3d3acae823d3e319b5ac9eab2edfff3e0555cec89de8d80927bbb51ab497f83856e81504437a76cd9c8e8d693a7cb6f8b80c660542d75
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
cache: bundler
|
2
|
+
sudo: false
|
1
3
|
language: ruby
|
2
4
|
rvm:
|
3
5
|
- 1.9.3
|
4
|
-
- 2.1.
|
6
|
+
- 2.1.5
|
5
7
|
- rbx-2
|
6
8
|
- jruby-19mode
|
7
9
|
# - ruby-head
|
@@ -12,6 +14,7 @@ gemfile:
|
|
12
14
|
- gemfiles/activerecord_3.2.gemfile
|
13
15
|
- gemfiles/activerecord_4.0.gemfile
|
14
16
|
- gemfiles/activerecord_4.1.gemfile
|
17
|
+
- gemfiles/activerecord_4.2.gemfile
|
15
18
|
- gemfiles/activerecord_edge.gemfile
|
16
19
|
|
17
20
|
env:
|
@@ -22,7 +25,7 @@ env:
|
|
22
25
|
addons:
|
23
26
|
postgresql: "9.3"
|
24
27
|
|
25
|
-
script: WITH_ADVISORY_LOCK_PREFIX=$TRAVIS_JOB_ID bundle exec rake --trace
|
28
|
+
script: WITH_ADVISORY_LOCK_PREFIX=$TRAVIS_JOB_ID bundle exec rake --trace spec:all
|
26
29
|
|
27
30
|
matrix:
|
28
31
|
allow_failures:
|
data/Appraisals
CHANGED
@@ -11,6 +11,10 @@ appraise 'activerecord-4.1' do
|
|
11
11
|
gem 'activerecord', '~> 4.1.0'
|
12
12
|
end
|
13
13
|
|
14
|
+
appraise 'activerecord-4.2' do
|
15
|
+
gem 'activerecord', '~> 4.2.0'
|
16
|
+
end
|
17
|
+
|
14
18
|
appraise 'activerecord-edge' do
|
15
19
|
gem 'activerecord', github: 'rails/rails'
|
16
20
|
gem 'arel', github: 'rails/arel'
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### 5.2.0
|
4
|
+
|
5
|
+
* [Eduardo Turiño](https://github.com/eturino) renamed `acts_as_tree` to `has_closure_tree`. We'll
|
6
|
+
keep both annotations around for the forseeable future, but I think not name-colliding by default is
|
7
|
+
strictly better. (Thanks for both the suggestion and PR!)
|
8
|
+
* [Ryan Selk](https://github.com/rselk) made several enhancements to the migration generation (thanks!).
|
9
|
+
* [ruok5](https://github.com/ruok5) updated the README to clarify a heirarchy maintenance usecase. Thanks!
|
10
|
+
* Made migrations error with a helpful message if the target didn't have the `has_closure_tree` or
|
11
|
+
`acts_as_tree` annotation. This addresses [issue 131](https://github.com/mceachen/closure_tree/issues/131).
|
12
|
+
|
3
13
|
### 5.1.1
|
4
14
|
|
5
15
|
* Fixed bug in `rails g closure_tree:migration` (introduced by me, not by seuros!)
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -43,6 +43,7 @@ for a description of different tree storage algorithms.
|
|
43
43
|
## Table of Contents
|
44
44
|
|
45
45
|
- [Installation](#installation)
|
46
|
+
- [Warning](#warning)
|
46
47
|
- [Usage](#usage)
|
47
48
|
- [Accessing Data](#accessing-data)
|
48
49
|
- [Polymorphic hierarchies with STI](#polymorphic-hierarchies-with-sti)
|
@@ -60,19 +61,26 @@ Note that closure_tree only supports Rails 3.2 and later, and has test coverage
|
|
60
61
|
|
61
62
|
2. Run `bundle install`
|
62
63
|
|
63
|
-
3. Add `
|
64
|
+
3. Add `has_closure_tree` (or `acts_as_tree`, which is an alias of the same method) to your hierarchical model:
|
64
65
|
|
65
66
|
```ruby
|
66
67
|
class Tag < ActiveRecord::Base
|
68
|
+
has_closure_tree
|
69
|
+
end
|
70
|
+
|
71
|
+
class AnotherTag < ActiveRecord::Base
|
67
72
|
acts_as_tree
|
68
73
|
end
|
69
74
|
```
|
70
75
|
|
71
|
-
Make sure you check out the [large number options](#available-options) that `
|
76
|
+
Make sure you check out the [large number options](#available-options) that `has_closure_tree` accepts.
|
72
77
|
|
73
|
-
Make sure you add `
|
78
|
+
Make sure you add `has_closure_tree` **after** `attr_accessible` and
|
74
79
|
`self.table_name =` lines in your model.
|
75
80
|
|
81
|
+
If you're already using other hierarchical gems, like `ancestry` or `acts_as_tree`, please refer
|
82
|
+
to the [warning section](#warning)!
|
83
|
+
|
76
84
|
4. Add a migration to add a `parent_id` column to the hierarchical model.
|
77
85
|
You may want to also [add a column for deterministic ordering of children](#sort_order), but that's optional.
|
78
86
|
|
@@ -90,7 +98,7 @@ Note that closure_tree only supports Rails 3.2 and later, and has test coverage
|
|
90
98
|
to create the closure tree table for your model.
|
91
99
|
|
92
100
|
By default the table name will be the model's table name, followed by
|
93
|
-
"_hierarchies". Note that by calling ```
|
101
|
+
"_hierarchies". Note that by calling ```has_closure_tree```, a "virtual model" (in this case, ```TagHierarchy```)
|
94
102
|
will be created dynamically. You don't need to create it.
|
95
103
|
|
96
104
|
6. Run `rake db:migrate`
|
@@ -101,6 +109,12 @@ Note that closure_tree only supports Rails 3.2 and later, and has test coverage
|
|
101
109
|
|
102
110
|
If you're starting from scratch you don't need to call `rebuild!`.
|
103
111
|
|
112
|
+
## Warning
|
113
|
+
|
114
|
+
As stated above, using multiple hierarchy gems (like `ancestry` or `nested set`) on the same model
|
115
|
+
will most likely result in pain, suffering, hair loss, tooth decay, heel-related ailments, and gingivitis.
|
116
|
+
Assume things will break.
|
117
|
+
|
104
118
|
## Usage
|
105
119
|
|
106
120
|
### Creation
|
@@ -146,7 +160,7 @@ child1.ancestry_path
|
|
146
160
|
You can `find` as well as `find_or_create` by "ancestry paths".
|
147
161
|
|
148
162
|
If you provide an array of strings to these methods, they reference the `name` column in your
|
149
|
-
model, which can be overridden with the `:name_column` option provided to `
|
163
|
+
model, which can be overridden with the `:name_column` option provided to `has_closure_tree`.
|
150
164
|
|
151
165
|
```ruby
|
152
166
|
child = Tag.find_or_create_by_path(%w[grandparent parent child])
|
@@ -187,6 +201,17 @@ h.ancestry_path
|
|
187
201
|
=> ["a", "b", "c", "d", "e", "f", "g", "h"]
|
188
202
|
```
|
189
203
|
|
204
|
+
When it is more convenient to simply change the `parent_id` of a node directly (for example, when dealing with a form `<select>`), closure_tree will handle the necessary changes automatically when the record is saved:
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
j = Tag.find 102
|
208
|
+
j.self_and_ancestor_ids
|
209
|
+
=> [102, 87, 77]
|
210
|
+
j.update parent_id: 96
|
211
|
+
j.self_and_ancestor_ids
|
212
|
+
=> [102, 96, 95, 78]
|
213
|
+
```
|
214
|
+
|
190
215
|
### Nested hashes
|
191
216
|
|
192
217
|
```hash_tree``` provides a method for rendering a subtree as an
|
@@ -241,7 +266,7 @@ Just for kicks, this is the test tree I used for proving that preordered tree tr
|
|
241
266
|
|
242
267
|
### Available options
|
243
268
|
|
244
|
-
When you include ```
|
269
|
+
When you include ```has_closure_tree``` in your model, you can provide a hash to override the following defaults:
|
245
270
|
|
246
271
|
* ```:parent_column_name``` to override the column name of the parent foreign key in the model's table. This defaults to "parent_id".
|
247
272
|
* ```:hierarchy_class_name``` to override the hierarchy class name. This defaults to the singular name of the model + "Hierarchy", like ```TagHierarchy```.
|
@@ -296,18 +321,18 @@ When you include ```acts_as_tree``` in your model, you can provide a hash to ove
|
|
296
321
|
* ```tag.find_all_by_generation(0).to_a``` == ```[tag]```
|
297
322
|
* ```tag.find_all_by_generation(1)``` == ```tag.children```
|
298
323
|
* ```tag.find_all_by_generation(2)``` will return the tag's grandchildren, and so on.
|
299
|
-
* ```tag.destroy``` will destroy a node and do <em>something</em> to its children, which is determined by the ```:dependent``` option passed to ```
|
324
|
+
* ```tag.destroy``` will destroy a node and do <em>something</em> to its children, which is determined by the ```:dependent``` option passed to ```has_closure_tree```.
|
300
325
|
|
301
326
|
## Polymorphic hierarchies with STI
|
302
327
|
|
303
328
|
Polymorphic models using single table inheritance (STI) are supported:
|
304
329
|
|
305
330
|
1. Create a db migration that adds a String ```type``` column to your model
|
306
|
-
2. Subclass the model class. You only need to add ```
|
331
|
+
2. Subclass the model class. You only need to add ```has_closure_tree``` to your base class:
|
307
332
|
|
308
333
|
```ruby
|
309
334
|
class Tag < ActiveRecord::Base
|
310
|
-
|
335
|
+
has_closure_tree
|
311
336
|
end
|
312
337
|
class WhenTag < Tag ; end
|
313
338
|
class WhereTag < Tag ; end
|
@@ -319,17 +344,17 @@ you use the ```:type``` attribute, so **this doesn't work**:
|
|
319
344
|
|
320
345
|
```ruby
|
321
346
|
# BAD: ActiveRecord ignores the :type attribute:
|
322
|
-
root.children.create(:
|
347
|
+
root.children.create(name: "child", type: "WhenTag")
|
323
348
|
```
|
324
349
|
|
325
350
|
Instead, use either ```.add_child``` or ```children <<```:
|
326
351
|
|
327
352
|
```ruby
|
328
353
|
# GOOD!
|
329
|
-
a = Tag.create!(:
|
330
|
-
b = WhenTag.new(:
|
354
|
+
a = Tag.create!(name: "a")
|
355
|
+
b = WhenTag.new(name: "b")
|
331
356
|
a.children << b
|
332
|
-
c = WhatTag.new(:
|
357
|
+
c = WhatTag.new(name: "c")
|
333
358
|
b.add_child(c)
|
334
359
|
```
|
335
360
|
|
@@ -343,7 +368,7 @@ If you want to order children alphabetically, and your model has a ```name``` co
|
|
343
368
|
|
344
369
|
```ruby
|
345
370
|
class Tag < ActiveRecord::Base
|
346
|
-
|
371
|
+
has_closure_tree order: 'name'
|
347
372
|
end
|
348
373
|
```
|
349
374
|
|
@@ -357,7 +382,7 @@ and in your model:
|
|
357
382
|
|
358
383
|
```ruby
|
359
384
|
class OrderedTag < ActiveRecord::Base
|
360
|
-
|
385
|
+
has_closure_tree order: 'sort_order'
|
361
386
|
end
|
362
387
|
```
|
363
388
|
|
@@ -434,11 +459,11 @@ for both MySQL and PostgreSQL, [with_advisory_lock](https://github.com/mceachen/
|
|
434
459
|
is used automatically to ensure correctness.
|
435
460
|
|
436
461
|
If you are already managing concurrency elsewhere in your application, and want to disable the use
|
437
|
-
of with_advisory_lock, pass
|
462
|
+
of with_advisory_lock, pass ```with_advisory_lock: false``` in the options hash:
|
438
463
|
|
439
464
|
```ruby
|
440
465
|
class Tag
|
441
|
-
|
466
|
+
has_closure_tree with_advisory_lock: false
|
442
467
|
end
|
443
468
|
```
|
444
469
|
|
data/Rakefile
CHANGED
@@ -12,19 +12,29 @@ YARD::Rake::YardocTask.new do |t|
|
|
12
12
|
end
|
13
13
|
|
14
14
|
require "rspec/core/rake_task"
|
15
|
-
RSpec::Core::RakeTask.new(:spec)
|
15
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
16
|
+
task.pattern = 'spec/*_spec.rb'
|
17
|
+
end
|
16
18
|
|
17
19
|
task :default => :spec
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
namespace :spec do
|
22
|
+
desc 'Run all spec variants'
|
23
|
+
task :all do
|
24
|
+
rake = 'bundle exec rake'
|
25
|
+
fail unless system("#{rake} spec:generators")
|
26
|
+
[['', ''], ['db_prefix_', ''], ['', '_db_suffix'], ['abc_', '_123']].each do |prefix, suffix|
|
27
|
+
env = "DB_PREFIX=#{prefix} DB_SUFFIX=#{suffix}"
|
28
|
+
fail unless system("#{rake} spec #{env}")
|
29
|
+
end
|
30
|
+
require 'active_record/version'
|
31
|
+
if ActiveRecord::VERSION::MAJOR == 3
|
32
|
+
fail unless system("#{rake} spec ATTR_ACCESSIBLE=1")
|
33
|
+
end
|
22
34
|
end
|
23
|
-
|
24
|
-
|
25
|
-
|
35
|
+
|
36
|
+
desc 'Run generator specs'
|
37
|
+
RSpec::Core::RakeTask.new(:generators) do |task|
|
38
|
+
task.pattern = 'spec/generators/*_spec.rb'
|
26
39
|
end
|
27
40
|
end
|
28
|
-
|
29
|
-
# Run the specs using all the different database engines:
|
30
|
-
# for DB in sqlite3 mysql postgresql ; do rake ; done
|
data/closure_tree.gemspec
CHANGED
@@ -20,14 +20,14 @@ Gem::Specification.new do |gem|
|
|
20
20
|
gem.add_runtime_dependency 'with_advisory_lock', '>= 3.0.0'
|
21
21
|
|
22
22
|
gem.add_development_dependency 'yard'
|
23
|
-
gem.add_development_dependency 'rspec', '>= 3.0'
|
24
23
|
gem.add_development_dependency 'rspec-instafail'
|
25
|
-
|
26
|
-
gem.add_development_dependency 'rspec-rails' # FIXME: for rspec-rails and rspec fixture support
|
24
|
+
gem.add_development_dependency 'rspec-rails', '>= 3.1'
|
27
25
|
gem.add_development_dependency 'uuidtools'
|
28
26
|
gem.add_development_dependency 'database_cleaner'
|
29
27
|
gem.add_development_dependency 'appraisal'
|
30
28
|
gem.add_development_dependency 'timecop'
|
31
29
|
gem.add_development_dependency 'parallel'
|
30
|
+
gem.add_development_dependency 'ammeter', '~> 1.1.2'
|
31
|
+
# gem.add_development_dependency 'byebug'
|
32
32
|
# gem.add_development_dependency 'ruby-prof' # <- don't need this normally.
|
33
33
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "foreigner", :git => "https://github.com/matthuhiggins/foreigner.git"
|
6
|
+
gem "activerecord", "~> 4.2.0"
|
7
|
+
|
8
|
+
platforms :ruby, :rbx do
|
9
|
+
gem "mysql2"
|
10
|
+
gem "pg"
|
11
|
+
gem "sqlite3"
|
12
|
+
end
|
13
|
+
|
14
|
+
platforms :jruby do
|
15
|
+
gem "activerecord-jdbcmysql-adapter"
|
16
|
+
gem "activerecord-jdbcpostgresql-adapter"
|
17
|
+
gem "activerecord-jdbcsqlite3-adapter"
|
18
|
+
end
|
19
|
+
|
20
|
+
gemspec :path => "../"
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "foreigner", :git => "https://github.com/
|
5
|
+
gem "foreigner", :git => "https://github.com/matthuhiggins/foreigner.git"
|
6
6
|
gem "activerecord", :github => "rails/rails"
|
7
7
|
gem "arel", :github => "rails/arel"
|
8
8
|
|
data/lib/closure_tree.rb
CHANGED
@@ -3,7 +3,7 @@ require 'active_support'
|
|
3
3
|
module ClosureTree
|
4
4
|
extend ActiveSupport::Autoload
|
5
5
|
|
6
|
-
autoload :
|
6
|
+
autoload :HasClosureTree
|
7
7
|
autoload :Support
|
8
8
|
autoload :HierarchyMaintenance
|
9
9
|
autoload :Model
|
@@ -15,5 +15,5 @@ module ClosureTree
|
|
15
15
|
end
|
16
16
|
|
17
17
|
ActiveSupport.on_load :active_record do
|
18
|
-
ActiveRecord::Base.send :extend, ClosureTree::
|
18
|
+
ActiveRecord::Base.send :extend, ClosureTree::HasClosureTree
|
19
19
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ClosureTree
|
2
|
+
module ActiveRecordSupport
|
3
|
+
def ensure_fixed_table_name(table_name)
|
4
|
+
[
|
5
|
+
ActiveRecord::Base.table_name_prefix,
|
6
|
+
remove_prefix_and_suffix(table_name),
|
7
|
+
ActiveRecord::Base.table_name_suffix
|
8
|
+
].compact.join
|
9
|
+
end
|
10
|
+
|
11
|
+
def remove_prefix_and_suffix(table_name)
|
12
|
+
pre, suff = ActiveRecord::Base.table_name_prefix, ActiveRecord::Base.table_name_suffix
|
13
|
+
if table_name.start_with?(pre) && table_name.end_with?(suff)
|
14
|
+
table_name[pre.size..-(suff.size + 1)]
|
15
|
+
else
|
16
|
+
table_name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,8 +1,6 @@
|
|
1
|
-
require 'with_advisory_lock'
|
2
|
-
|
3
1
|
module ClosureTree
|
4
|
-
module
|
5
|
-
def
|
2
|
+
module HasClosureTree
|
3
|
+
def has_closure_tree(options = {})
|
6
4
|
options.assert_valid_keys(
|
7
5
|
:parent_column_name,
|
8
6
|
:dependent,
|
@@ -35,5 +33,7 @@ module ClosureTree
|
|
35
33
|
# Support Heroku's database-less assets:precompile pre-deploy step:
|
36
34
|
raise e unless ENV['DATABASE_URL'].to_s.include?('//user:pass@127.0.0.1/') && ENV['RAILS_GROUPS'] == 'assets'
|
37
35
|
end
|
36
|
+
|
37
|
+
alias_method :acts_as_tree, :has_closure_tree
|
38
38
|
end
|
39
39
|
end
|
data/lib/closure_tree/support.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
require 'closure_tree/support_flags'
|
2
2
|
require 'closure_tree/support_attributes'
|
3
3
|
require 'closure_tree/numeric_order_support'
|
4
|
+
require 'closure_tree/active_record_support'
|
5
|
+
require 'with_advisory_lock'
|
4
6
|
|
5
7
|
# This class and mixins are an effort to reduce the namespace pollution to models that act_as_tree.
|
6
8
|
module ClosureTree
|
7
9
|
class Support
|
8
10
|
include ClosureTree::SupportFlags
|
9
11
|
include ClosureTree::SupportAttributes
|
12
|
+
include ClosureTree::ActiveRecordSupport
|
10
13
|
|
11
14
|
attr_reader :model_class
|
12
15
|
attr_reader :options
|
@@ -93,15 +96,6 @@ module ClosureTree
|
|
93
96
|
end
|
94
97
|
end
|
95
98
|
|
96
|
-
def remove_prefix_and_suffix(table_name)
|
97
|
-
pre, suff = ActiveRecord::Base.table_name_prefix, ActiveRecord::Base.table_name_suffix
|
98
|
-
if table_name.start_with?(pre) && table_name.end_with?(suff)
|
99
|
-
table_name[pre.size..-(suff.size + 1)]
|
100
|
-
else
|
101
|
-
table_name
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
99
|
def ids_from(scope)
|
106
100
|
scope.pluck(model_class.primary_key)
|
107
101
|
end
|
data/lib/closure_tree/version.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
|
-
require '
|
2
|
-
require 'rails/generators/active_record/migration'
|
1
|
+
require 'closure_tree/active_record_support'
|
3
2
|
require 'forwardable'
|
3
|
+
require 'rails/generators/active_record'
|
4
|
+
require 'rails/generators/named_base'
|
4
5
|
|
5
6
|
module ClosureTree
|
6
7
|
module Generators # :nodoc:
|
7
|
-
class MigrationGenerator <
|
8
|
-
include
|
9
|
-
|
8
|
+
class MigrationGenerator < Rails::Generators::NamedBase # :nodoc:
|
9
|
+
include Rails::Generators::Migration
|
10
|
+
include ClosureTree::ActiveRecordSupport
|
10
11
|
extend Forwardable
|
11
12
|
def_delegators :ct, :hierarchy_table_name, :primary_key_type
|
12
13
|
|
@@ -15,15 +16,33 @@ module ClosureTree
|
|
15
16
|
end
|
16
17
|
|
17
18
|
def create_migration_file
|
18
|
-
migration_template 'create_hierarchies_table.rb.erb', "db/migrate/create_#{
|
19
|
+
migration_template 'create_hierarchies_table.rb.erb', "db/migrate/create_#{migration_name}.rb"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def migration_name
|
25
|
+
remove_prefix_and_suffix(ct.hierarchy_table_name)
|
19
26
|
end
|
20
27
|
|
21
28
|
def migration_class_name
|
22
|
-
"Create#{
|
29
|
+
"Create#{migration_name.camelize}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def target_class
|
33
|
+
@target_class ||= class_name.constantize
|
23
34
|
end
|
24
35
|
|
25
36
|
def ct
|
26
|
-
@ct ||=
|
37
|
+
@ct ||= if target_class.respond_to?(:_ct)
|
38
|
+
target_class._ct
|
39
|
+
else
|
40
|
+
fail "Please RTFM and add the `has_closure_tree` (or `acts_as_tree`) annotation to #{class_name} before creating the migration."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.next_migration_number(dirname)
|
45
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
27
46
|
end
|
28
47
|
end
|
29
48
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
2
|
def change
|
3
|
-
create_table :<%=
|
3
|
+
create_table :<%= migration_name %>, id: false do |t|
|
4
4
|
t.<%= primary_key_type %> :ancestor_id, null: false
|
5
5
|
t.<%= primary_key_type %> :descendant_id, null: false
|
6
6
|
t.integer :generations, null: false
|
7
7
|
end
|
8
8
|
|
9
|
-
add_index :<%=
|
9
|
+
add_index :<%= migration_name %>, [:ancestor_id, :descendant_id, :generations],
|
10
10
|
unique: true,
|
11
|
-
name: "
|
11
|
+
name: "<%= file_name %>_anc_desc_idx"
|
12
12
|
|
13
|
-
add_index :<%=
|
14
|
-
name: "
|
13
|
+
add_index :<%= migration_name -%>, [:descendant_id],
|
14
|
+
name: "<%= file_name %>_desc_idx"
|
15
15
|
end
|
16
16
|
end
|
data/spec/db/models.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'uuidtools'
|
2
2
|
|
3
3
|
class Tag < ActiveRecord::Base
|
4
|
-
|
4
|
+
has_closure_tree :dependent => :destroy, :order => :name
|
5
5
|
before_destroy :add_destroyed_tag
|
6
6
|
attr_accessible :name, :title if _ct.use_attr_accessible?
|
7
7
|
|
@@ -18,7 +18,7 @@ end
|
|
18
18
|
class UUIDTag < ActiveRecord::Base
|
19
19
|
self.primary_key = :uuid
|
20
20
|
before_create :set_uuid
|
21
|
-
|
21
|
+
has_closure_tree dependent: :destroy, order: 'name', parent_column_name: 'parent_uuid'
|
22
22
|
before_destroy :add_destroyed_tag
|
23
23
|
attr_accessible :name, :title if _ct.use_attr_accessible?
|
24
24
|
|
@@ -90,15 +90,18 @@ class CuisineType < ActiveRecord::Base
|
|
90
90
|
end
|
91
91
|
|
92
92
|
module Namespace
|
93
|
+
def self.table_name_prefix
|
94
|
+
'namespace_'
|
95
|
+
end
|
93
96
|
class Type < ActiveRecord::Base
|
94
|
-
|
97
|
+
has_closure_tree dependent: :destroy
|
95
98
|
attr_accessible :name if _ct.use_attr_accessible?
|
96
99
|
end
|
97
100
|
end
|
98
101
|
|
99
102
|
class Metal < ActiveRecord::Base
|
100
103
|
self.table_name = "#{table_name_prefix}metal#{table_name_suffix}"
|
101
|
-
|
104
|
+
has_closure_tree order: 'sort_order', name_column: 'value'
|
102
105
|
self.inheritance_column = 'metal_type'
|
103
106
|
end
|
104
107
|
|
@@ -109,5 +112,5 @@ class Unobtanium < Metal
|
|
109
112
|
end
|
110
113
|
|
111
114
|
class MenuItem < ActiveRecord::Base
|
112
|
-
|
115
|
+
has_closure_tree touch: true, with_advisory_lock: false
|
113
116
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ammeter/init'
|
3
|
+
|
4
|
+
# Generators are not automatically loaded by Rails
|
5
|
+
require 'generators/closure_tree/migration_generator'
|
6
|
+
|
7
|
+
RSpec.describe ClosureTree::Generators::MigrationGenerator, type: :generator do
|
8
|
+
TMPDIR = Dir.mktmpdir
|
9
|
+
# Tell generator where to put its output
|
10
|
+
destination TMPDIR
|
11
|
+
before { prepare_destination }
|
12
|
+
|
13
|
+
describe 'generator output' do
|
14
|
+
before { run_generator %w(tag) }
|
15
|
+
subject { migration_file('db/migrate/create_tag_hierarchies.rb') }
|
16
|
+
it { is_expected.to be_a_migration }
|
17
|
+
it { is_expected.to contain(/t.integer :ancestor_id, null: false/) }
|
18
|
+
it { is_expected.to contain(/t.integer :descendant_id, null: false/) }
|
19
|
+
it { is_expected.to contain(/t.integer :generations, null: false/) }
|
20
|
+
it { is_expected.to contain(/add_index :tag_hierarchies/) }
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'generator output with namespaced model' do
|
24
|
+
before { run_generator %w(Namespace::Type) }
|
25
|
+
subject { migration_file('db/migrate/create_namespace_type_hierarchies.rb') }
|
26
|
+
it { is_expected.to be_a_migration }
|
27
|
+
it { is_expected.to contain(/t.integer :ancestor_id, null: false/) }
|
28
|
+
it { is_expected.to contain(/t.integer :descendant_id, null: false/) }
|
29
|
+
it { is_expected.to contain(/t.integer :generations, null: false/) }
|
30
|
+
it { is_expected.to contain(/add_index :namespace_type_hierarchies/) }
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'generator output with namespaced model with /' do
|
34
|
+
before { run_generator %w(namespace/type) }
|
35
|
+
subject { migration_file('db/migrate/create_namespace_type_hierarchies.rb') }
|
36
|
+
it { is_expected.to be_a_migration }
|
37
|
+
it { is_expected.to contain(/t.integer :ancestor_id, null: false/) }
|
38
|
+
it { is_expected.to contain(/t.integer :descendant_id, null: false/) }
|
39
|
+
it { is_expected.to contain(/t.integer :generations, null: false/) }
|
40
|
+
it { is_expected.to contain(/add_index :namespace_type_hierarchies/) }
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should run all tasks in generator without errors' do
|
44
|
+
gen = generator %w(tag)
|
45
|
+
expect(gen).to receive :create_migration_file
|
46
|
+
capture(:stdout) { gen.invoke_all }
|
47
|
+
end
|
48
|
+
end
|
data/spec/parallel_spec.rb
CHANGED
@@ -11,21 +11,26 @@ def max_threads
|
|
11
11
|
5
|
12
12
|
end
|
13
13
|
|
14
|
+
|
14
15
|
class WorkerBase
|
15
16
|
extend Forwardable
|
16
17
|
attr_reader :name
|
17
18
|
def_delegators :@thread, :join, :wakeup, :status, :to_s
|
18
19
|
|
20
|
+
def log(msg)
|
21
|
+
puts("#{Thread.current}: #{msg}") if ENV['VERBOSE']
|
22
|
+
end
|
23
|
+
|
19
24
|
def initialize(target, name)
|
20
25
|
@target = target
|
21
26
|
@name = name
|
22
27
|
@thread = Thread.new do
|
23
28
|
ActiveRecord::Base.connection_pool.with_connection { before_work } if respond_to? :before_work
|
24
|
-
|
29
|
+
log 'going to sleep...'
|
25
30
|
sleep
|
26
|
-
|
31
|
+
log 'woke up...'
|
27
32
|
ActiveRecord::Base.connection_pool.with_connection { work }
|
28
|
-
|
33
|
+
log 'done.'
|
29
34
|
end
|
30
35
|
end
|
31
36
|
end
|
@@ -33,9 +38,9 @@ end
|
|
33
38
|
class FindOrCreateWorker < WorkerBase
|
34
39
|
def work
|
35
40
|
path = [name, :a, :b, :c]
|
36
|
-
|
41
|
+
log "making #{path}..."
|
37
42
|
t = (@target || Tag).find_or_create_by_path(path)
|
38
|
-
|
43
|
+
log "made #{t.id}, #{t.ancestry_path}"
|
39
44
|
end
|
40
45
|
end
|
41
46
|
|
@@ -45,6 +50,10 @@ describe 'Concurrent creation' do
|
|
45
50
|
@iterations = 5
|
46
51
|
end
|
47
52
|
|
53
|
+
def log(msg)
|
54
|
+
puts(msg) if ENV['VERBOSE']
|
55
|
+
end
|
56
|
+
|
48
57
|
def run_workers(worker_class = FindOrCreateWorker)
|
49
58
|
@names = @iterations.times.map { |iter| "iteration ##{iter}" }
|
50
59
|
@names.each do |name|
|
@@ -55,17 +64,17 @@ describe 'Concurrent creation' do
|
|
55
64
|
if unready_workers.empty?
|
56
65
|
break
|
57
66
|
else
|
58
|
-
|
67
|
+
log "Not ready to wakeup: #{unready_workers.map { |ea| [ea.to_s, ea.status] }}"
|
59
68
|
sleep(0.1)
|
60
69
|
end
|
61
70
|
end
|
62
71
|
sleep(0.25)
|
63
72
|
# OK, GO!
|
64
|
-
|
73
|
+
log 'Calling .wakeup on all workers...'
|
65
74
|
workers.each(&:wakeup)
|
66
75
|
sleep(0.25)
|
67
76
|
# Then wait for them to finish:
|
68
|
-
|
77
|
+
log 'Calling .join on all workers...'
|
69
78
|
workers.each(&:join)
|
70
79
|
end
|
71
80
|
# Ensure we're still connected:
|
@@ -160,7 +169,7 @@ describe 'Concurrent creation' do
|
|
160
169
|
Parallel.map(emails, :in_threads => max_threads) do |email|
|
161
170
|
ActiveRecord::Base.connection_pool.with_connection do
|
162
171
|
User.transaction do
|
163
|
-
|
172
|
+
log "Destroying #{email}..."
|
164
173
|
User.where(email: email).destroy_all
|
165
174
|
end
|
166
175
|
end
|
data/tests.sh
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
#!/bin/sh -ex
|
2
2
|
|
3
|
-
for RMI in 2.1.
|
3
|
+
for RMI in 2.1.5 #jruby-1.6.13 :P
|
4
4
|
do
|
5
5
|
rbenv local $RMI
|
6
|
-
for
|
6
|
+
for DB in postgresql mysql sqlite
|
7
7
|
do
|
8
|
-
appraisal
|
9
|
-
DB=$db WITH_ADVISORY_LOCK_PREFIX=$(date +%s) appraisal rake all_spec_flavors
|
8
|
+
appraisal rake spec:all WITH_ADVISORY_LOCK_PREFIX=$(date +%s) DB=$DB
|
10
9
|
done
|
11
10
|
done
|
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.
|
4
|
+
version: 5.2.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-
|
11
|
+
date: 2014-12-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rspec
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '3.0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '3.0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: rspec-instafail
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,14 +72,14 @@ dependencies:
|
|
86
72
|
requirements:
|
87
73
|
- - ">="
|
88
74
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
75
|
+
version: '3.1'
|
90
76
|
type: :development
|
91
77
|
prerelease: false
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
93
79
|
requirements:
|
94
80
|
- - ">="
|
95
81
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
82
|
+
version: '3.1'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
84
|
name: uuidtools
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,6 +150,20 @@ dependencies:
|
|
164
150
|
- - ">="
|
165
151
|
- !ruby/object:Gem::Version
|
166
152
|
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: ammeter
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 1.1.2
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 1.1.2
|
167
167
|
description: Easily and efficiently make your ActiveRecord model support hierarchies
|
168
168
|
email:
|
169
169
|
- matthew-github@mceachen.org
|
@@ -185,14 +185,16 @@ files:
|
|
185
185
|
- gemfiles/activerecord_3.2.gemfile
|
186
186
|
- gemfiles/activerecord_4.0.gemfile
|
187
187
|
- gemfiles/activerecord_4.1.gemfile
|
188
|
+
- gemfiles/activerecord_4.2.gemfile
|
188
189
|
- gemfiles/activerecord_edge.gemfile
|
189
190
|
- img/example.png
|
190
191
|
- img/preorder.png
|
191
192
|
- lib/closure_tree.rb
|
192
|
-
- lib/closure_tree/
|
193
|
+
- lib/closure_tree/active_record_support.rb
|
193
194
|
- lib/closure_tree/deterministic_ordering.rb
|
194
195
|
- lib/closure_tree/digraphs.rb
|
195
196
|
- lib/closure_tree/finders.rb
|
197
|
+
- lib/closure_tree/has_closure_tree.rb
|
196
198
|
- lib/closure_tree/hash_tree.rb
|
197
199
|
- lib/closure_tree/hierarchy_maintenance.rb
|
198
200
|
- lib/closure_tree/model.rb
|
@@ -212,6 +214,7 @@ files:
|
|
212
214
|
- spec/db/models.rb
|
213
215
|
- spec/db/schema.rb
|
214
216
|
- spec/fixtures/tags.yml
|
217
|
+
- spec/generators/migration_generator_spec.rb
|
215
218
|
- spec/hierarchy_maintenance_spec.rb
|
216
219
|
- spec/label_spec.rb
|
217
220
|
- spec/matcher_spec.rb
|
@@ -251,7 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
254
|
version: '0'
|
252
255
|
requirements: []
|
253
256
|
rubyforge_project:
|
254
|
-
rubygems_version: 2.
|
257
|
+
rubygems_version: 2.2.2
|
255
258
|
signing_key:
|
256
259
|
specification_version: 4
|
257
260
|
summary: Easily and efficiently make your ActiveRecord model support hierarchies
|
@@ -262,6 +265,7 @@ test_files:
|
|
262
265
|
- spec/db/models.rb
|
263
266
|
- spec/db/schema.rb
|
264
267
|
- spec/fixtures/tags.yml
|
268
|
+
- spec/generators/migration_generator_spec.rb
|
265
269
|
- spec/hierarchy_maintenance_spec.rb
|
266
270
|
- spec/label_spec.rb
|
267
271
|
- spec/matcher_spec.rb
|