closure_tree 5.1.1 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|