closure_tree 6.0.0 → 6.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: 61222be14d812607e9d4cefdcfc76c0579fb08dd
4
- data.tar.gz: 81e89568a49834a426341cdd5f1595ccd7587c7c
3
+ metadata.gz: be5442160917053dfca50f710fa8276b2d2d937c
4
+ data.tar.gz: e6012dd4b630ace5fa36835de936bd395e41df20
5
5
  SHA512:
6
- metadata.gz: 97e39637522b4a2d9ea04d0900c5da4fbd5e21ec2dacc3d6082ca7c77106bc612100ce1c61e8e82e238ef705e9bce4d7d22537a012e3abfff9a042d96256c3ca
7
- data.tar.gz: fb6aa0360ae7642efc6c5d70ff44bc9d583c25a027ae07c3eaa1573fea8d99879a5f424a8641209b34a10ffc17c98edeff122bfebd282b71521010f8bb9c1153
6
+ metadata.gz: 43316c4fd1030f11ca90d59cf477f9a7d5300a16771152e3b81a779d5a25f665b9f9a288c42bc0c54183717d537cc95d704fc180166064079fbe6a9ee20d2bc5
7
+ data.tar.gz: 7f65c0281b3d878b2c333087f3e63a8e09c6aecbcabc98d798e2e1651853f50f7aefe50ece31b611bf12b17718fa3b023adc67cb39c747034a79aafae152dd93
data/.gitignore CHANGED
@@ -11,4 +11,4 @@ tmp/
11
11
  .rvmrc
12
12
  *.lock
13
13
  tmp/
14
- .ruby-version
14
+ .ruby-*
@@ -2,12 +2,18 @@ cache: bundler
2
2
  sudo: false
3
3
  language: ruby
4
4
  rvm:
5
- - 2.2.3
6
- - jruby-head
5
+ - 2.3.1
6
+ - 2.2.5
7
+ - 2.0
8
+ # these haven't been passing for a while:
9
+ # - jruby-head
10
+ # - rbx
7
11
 
8
12
  gemfile:
9
13
  - gemfiles/activerecord_4.1.gemfile
10
14
  - gemfiles/activerecord_4.2.gemfile
15
+ - gemfiles/activerecord_5.0_foreigner.gemfile
16
+ - gemfiles/activerecord_5.0.gemfile
11
17
  - gemfiles/activerecord_edge.gemfile
12
18
 
13
19
  env:
@@ -15,16 +21,19 @@ env:
15
21
  - DB=mysql
16
22
  - DB=postgresql
17
23
 
18
- #addons:
19
- # postgresql: "9.3"
20
-
21
24
  script: WITH_ADVISORY_LOCK_PREFIX=$TRAVIS_JOB_ID bundle exec rake --trace spec:all
22
25
 
23
26
  matrix:
24
27
  allow_failures:
25
28
  - gemfile: gemfiles/activerecord_edge.gemfile
26
29
  - rvm: jruby-head
30
+ - rvm: rbx
27
31
 
32
+ # rails 5+ requires ruby >= 2.2:
28
33
  exclude:
29
- - rvm: ruby-head
30
- gemfile: gemfiles/activerecord_4.1.gemfile
34
+ - rvm: 2.0
35
+ gemfile: gemfiles/activerecord_5.0_foreigner.gemfile
36
+ - rvm: 2.0
37
+ gemfile: gemfiles/activerecord_5.0.gemfile
38
+ - rvm: 2.0
39
+ gemfile: gemfiles/activerecord_edge.gemfile
data/Appraisals CHANGED
@@ -1,6 +1,6 @@
1
1
  appraise 'activerecord-4.1' do
2
2
  gem 'activerecord', '~> 4.1.0'
3
- gem 'foreigner', :git => 'https://github.com/matthuhiggins/foreigner.git'
3
+ gem 'foreigner'
4
4
  platforms :ruby, :rbx do
5
5
  gem 'mysql2', '~> 0.3.20'
6
6
  end
@@ -8,11 +8,21 @@ end
8
8
 
9
9
  appraise 'activerecord-4.2' do
10
10
  gem 'activerecord', '~> 4.2.0'
11
+
11
12
  platforms :ruby, :rbx do
12
13
  gem 'mysql2', '~> 0.3.20'
13
14
  end
14
15
  end
15
16
 
17
+ appraise 'activerecord-5.0-foreigner' do
18
+ gem 'activerecord', '~> 5.0.0'
19
+ gem 'foreigner'
20
+ end
21
+
22
+ appraise 'activerecord-5.0' do
23
+ gem 'activerecord', '~> 5.0.0'
24
+ end
25
+
16
26
  appraise 'activerecord-edge' do
17
27
  gem 'activerecord', github: 'rails/rails'
18
28
  gem 'arel', github: 'rails/arel'
@@ -1,11 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ### 6.1.0
4
+
5
+ * Added official support for ActiveRecord 5.0! Thanks to [Abdelkader Boudih](https://github.com/seuros),
6
+ [Jay Fredlund](https://github.com/jayfredlund), Veselin Stoyanov, and
7
+ [Aaron Russell](https://github.com/aaronrussell) for all the PRs.
8
+ * Add `database_less` configuration to not raise an error during build step when
9
+ database is unavailable which is a common case in some PaaS like
10
+ (Heroku, Catalyze, ..., etc).
11
+
3
12
  ### 6.0.0
4
13
 
5
14
  * [Andrew Kumanyaev](https://github.com/zzet) *dramatically* improved mutation performance on large trees.
6
15
  Thanks for the PR!
7
16
  * [Martin Schmidt](https://github.com/martin-schmidt) discovered and fixed build problems due to new versions
8
17
  of mysql2 and ammeter which broke Travis builds. Thanks for the PR!
18
+ * [Fabien MICHEL](https://github.com/fabien-michel) updated the README with another example. Thanks for the PR!
9
19
 
10
20
  ### 6.0.0.alpha,beta,gamma
11
21
 
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-2015 Matthew McEachen
1
+ Copyright (c) 2012-2016 Matthew McEachen
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ### Closure_tree lets your ActiveRecord models act as nodes in a [tree data structure](http://en.wikipedia.org/wiki/Tree_%28data_structure%29)
4
4
 
5
- Common applications include modeling hierarchical data, like tags, page graphs in CMSes,
5
+ Common applications include modeling hierarchical data, like tags, threaded comments, page graphs in CMSes,
6
6
  and tracking user referrals.
7
7
 
8
8
  [![Build Status](https://secure.travis-ci.org/mceachen/closure_tree.png?branch=master)](http://travis-ci.org/mceachen/closure_tree)
@@ -26,8 +26,8 @@ closure_tree has some great features:
26
26
  * 2 SQL INSERTs on node creation
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
- * __Support for ActiveRecord 4.1, 4.2 and 5.0.alpha__
30
- * __Support for Ruby 2.2 and JRuby 9000__
29
+ * __Support for ActiveRecord 4.1, 4.2 and 5.0__
30
+ * __Support for Ruby 2.0, 2.1, 2.2, 2.3.1 and JRuby 9000__
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)
@@ -109,6 +109,9 @@ Note that closure_tree only supports ActiveRecord 4.1 and later, and has test co
109
109
 
110
110
  If you're starting from scratch you don't need to call `rebuild!`.
111
111
 
112
+ NOTE: Run `rails g closure_tree:config` to create an initializer with extra
113
+ configurations. (Optional)
114
+
112
115
  ## Warning
113
116
 
114
117
  As stated above, using multiple hierarchy gems (like `ancestry` or `nested set`) on the same model
@@ -145,11 +148,17 @@ child3 = Tag.new(name: 'Third Child')
145
148
  parent.add_child child3
146
149
  ```
147
150
 
151
+ Or by setting the parent on the child :
152
+
153
+ ```ruby
154
+ Tag.create(name: 'Fourth Child', parent: parent)
155
+ ```
156
+
148
157
  Then:
149
158
 
150
159
  ```ruby
151
160
  grandparent.self_and_descendants.collect(&:name)
152
- => ["Grandparent", "Parent", "First Child", "Second Child", "Third Child"]
161
+ => ["Grandparent", "Parent", "First Child", "Second Child", "Third Child", "Fourth Child"]
153
162
 
154
163
  child1.ancestry_path
155
164
  => ["Grandparent", "Parent", "First Child"]
@@ -275,6 +284,7 @@ When you include ```has_closure_tree``` in your model, you can provide a hash to
275
284
  * ```:nullify``` will simply set the parent column to null. Each child node will be considered a "root" node. This is the default.
276
285
  * ```:delete_all``` will delete all descendant nodes (which circumvents the destroy hooks)
277
286
  * ```:destroy``` will destroy all descendant nodes (which runs the destroy hooks on each child node)
287
+ * ```nil``` does nothing with descendant nodes
278
288
  * ```:name_column``` used by #```find_or_create_by_path```, #```find_by_path```, and ```ancestry_path``` instance methods. This is primarily useful if the model only has one required field (like a "tag").
279
289
  * ```:order``` used to set up [deterministic ordering](#deterministic-ordering)
280
290
  * ```:touch``` delegates to the `belongs_to` annotation for the parent, so `touch`ing cascades to all children (the performance of this for deep trees isn't currently optimal).
@@ -394,12 +404,12 @@ If your ```order``` column is an integer attribute, you'll also have these:
394
404
  * ```node1.prepend_sibling(node2)``` which will
395
405
  1. set ```node2``` to the same parent as ```node1```,
396
406
  2. set ```node2```'s order column to 1 less than ```node1```'s value, and
397
- 3. decrement the order_column of all children of node1's parents whose order_column is <>>= node2's new value by 1.
407
+ 3. increment the order_column of all children of node1's parents whose order_column is > node2's new value by 1.
398
408
 
399
409
  * ```node1.append_sibling(node2)``` which will
400
410
  1. set ```node2``` to the same parent as ```node1```,
401
411
  2. set ```node2```'s order column to 1 more than ```node1```'s value, and
402
- 3. increment the order_column of all children of node1's parents whose order_column is >= node2's new value by 1.
412
+ 3. increment the order_column of all children of node1's parents whose order_column is > node2's new value by 1.
403
413
 
404
414
  ```ruby
405
415
 
@@ -503,6 +513,22 @@ after do
503
513
  end
504
514
  ```
505
515
 
516
+ ### `bundle install` says `Gem::Ext::BuildError: ERROR: Failed to build gem native extension`
517
+
518
+ When building from source, the `mysql2`, `pg`, and `sqlite` gems need their native client libraries
519
+ installed on your system. Note that this error isn't specific to ClosureTree.
520
+
521
+ On Ubuntu/Debian systems, run:
522
+
523
+ ```
524
+ sudo apt-get install libpq-dev libsqlite3-dev libmysqlclient-dev
525
+ bundle install
526
+ ```
527
+
528
+ ### Object destroy fails with MySQL 5.7+
529
+
530
+ A bug was introduced in MySQL's query optimizer. [See the workaround here](https://github.com/mceachen/closure_tree/issues/206).
531
+
506
532
  ## Testing with Closure Tree
507
533
 
508
534
  Closure tree comes with some RSpec2/3 matchers which you may use for your tests:
@@ -538,10 +564,9 @@ end
538
564
 
539
565
  Closure tree is [tested under every valid combination](http://travis-ci.org/#!/mceachen/closure_tree) of
540
566
 
541
- * Ruby 2.2 (and sometimes head)
542
- * jRuby 9000 (and sometimes head)
543
- * The latest ActiveRecord 4.1, 4.2, and master branch
544
- * Concurrency tests for MySQL and PostgreSQL. SQLite is tested in a single-threaded environment.
567
+ * Ruby 2.0, 2.2, 2.3.1
568
+ * ActiveRecord 4.1, 4.2, and 5.0
569
+ * PostgreSQL, MySQL, and SQLite. Concurrency tests are only run with MySQL and PostgreSQL.
545
570
 
546
571
  Assuming you're using [rbenv](https://github.com/sstephenson/rbenv), you can use ```tests.sh``` to
547
572
  run the test matrix locally.
@@ -552,7 +577,7 @@ See the [change log](https://github.com/mceachen/closure_tree/blob/master/CHANGE
552
577
 
553
578
  ## Thanks to
554
579
 
555
- * The more than 30 engineers around the world that have contributed their time and code to this gem
580
+ * The 45+ engineers around the world that have contributed their time and code to this gem
556
581
  (see the [changelog](https://github.com/mceachen/closure_tree/blob/master/CHANGELOG.md)!)
557
582
  * https://github.com/collectiveidea/awesome_nested_set
558
583
  * https://github.com/patshaughnessy/class_factory
@@ -14,18 +14,18 @@ Gem::Specification.new do |gem|
14
14
 
15
15
  gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
16
16
  gem.test_files = gem.files.grep(%r{^spec/})
17
- gem.required_ruby_version = '>= 2.1.0'
17
+ gem.required_ruby_version = '>= 2.0.0'
18
18
 
19
19
  gem.add_runtime_dependency 'activerecord', '>= 4.1.0'
20
20
  gem.add_runtime_dependency 'with_advisory_lock', '>= 3.0.0'
21
21
 
22
22
  gem.add_development_dependency 'rspec-instafail'
23
- gem.add_development_dependency 'rspec-rails', '~> 3.2.3'
23
+ gem.add_development_dependency 'rspec-rails'
24
24
  gem.add_development_dependency 'database_cleaner'
25
25
  gem.add_development_dependency 'appraisal'
26
26
  gem.add_development_dependency 'timecop'
27
27
  gem.add_development_dependency 'parallel'
28
- gem.add_development_dependency 'ammeter', '1.1.2' # See https://github.com/mceachen/closure_tree/issues/181
28
+ # gem.add_development_dependency 'ammeter', '1.1.2' # See https://github.com/mceachen/closure_tree/issues/181
29
29
  # gem.add_development_dependency 'byebug'
30
30
  # gem.add_development_dependency 'ruby-prof' # <- don't need this normally.
31
31
  end
@@ -3,7 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activerecord", "~> 4.1.0"
6
- gem "foreigner", :git => "https://github.com/matthuhiggins/foreigner.git"
6
+ gem "foreigner"
7
7
 
8
8
  platforms :ruby, :rbx do
9
9
  gem "mysql2", "~> 0.3.20"
@@ -0,0 +1,19 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5.0.0"
6
+
7
+ platforms :ruby, :rbx do
8
+ gem "mysql2"
9
+ gem "pg"
10
+ gem "sqlite3"
11
+ end
12
+
13
+ platforms :jruby do
14
+ gem "activerecord-jdbcmysql-adapter"
15
+ gem "activerecord-jdbcpostgresql-adapter"
16
+ gem "activerecord-jdbcsqlite3-adapter"
17
+ end
18
+
19
+ gemspec :path => "../"
@@ -0,0 +1,20 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5.0.0"
6
+ gem "foreigner"
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 => "../"
@@ -1,4 +1,4 @@
1
- require 'active_support'
1
+ require 'active_record'
2
2
 
3
3
  module ClosureTree
4
4
  extend ActiveSupport::Autoload
@@ -12,6 +12,15 @@ module ClosureTree
12
12
  autoload :Digraphs
13
13
  autoload :DeterministicOrdering
14
14
  autoload :NumericDeterministicOrdering
15
+ autoload :Configuration
16
+
17
+ def self.configure
18
+ yield configuration
19
+ end
20
+
21
+ def self.configuration
22
+ @configuration ||= Configuration.new
23
+ end
15
24
  end
16
25
 
17
26
  ActiveSupport.on_load :active_record do
@@ -0,0 +1,9 @@
1
+ module ClosureTree
2
+ class Configuration # :nodoc:
3
+ attr_accessor :database_less
4
+
5
+ def initialize
6
+ @database_less = ENV['DATABASE_URL'].to_s.include?('//user:pass@127.0.0.1/')
7
+ end
8
+ end
9
+ end
@@ -47,12 +47,12 @@ module ClosureTree
47
47
  end
48
48
 
49
49
  def without_self(scope)
50
- scope.without(self)
50
+ scope.without_instance(self)
51
51
  end
52
52
 
53
53
  module ClassMethods
54
54
 
55
- def without(instance)
55
+ def without_instance(instance)
56
56
  if instance.new_record?
57
57
  all
58
58
  else
@@ -30,8 +30,7 @@ module ClosureTree
30
30
  include ClosureTree::DeterministicOrdering if _ct.order_option?
31
31
  include ClosureTree::NumericDeterministicOrdering if _ct.order_is_numeric?
32
32
  rescue StandardError => e
33
- # Support Heroku's database-less assets:precompile pre-deploy step:
34
- raise e unless ENV['DATABASE_URL'].to_s.include?('//user:pass@127.0.0.1/')
33
+ raise e unless ClosureTree.configuration.database_less
35
34
  end
36
35
 
37
36
  alias_method :acts_as_tree, :has_closure_tree
@@ -11,7 +11,7 @@ module ClosureTree
11
11
  # There is no default depth limit. This might be crazy-big, depending
12
12
  # on your tree shape. Hash huge trees at your own peril!
13
13
  def hash_tree(options = {})
14
- _ct.hash_tree(nil, options[:limit_depth])
14
+ _ct.hash_tree(_ct.default_tree_scope(all, options[:limit_depth]))
15
15
  end
16
16
  end
17
17
  end
@@ -1,6 +1,6 @@
1
1
  module ClosureTree
2
2
  module HashTreeSupport
3
- def default_tree_scope(limit_depth = nil)
3
+ def default_tree_scope(scope, limit_depth = nil)
4
4
  # Deepest generation, within limit, for each descendant
5
5
  # NOTE: Postgres requires HAVING clauses to always contains aggregate functions (!!)
6
6
  having_clause = limit_depth ? "HAVING MAX(generations) <= #{limit_depth - 1}" : ''
@@ -13,15 +13,11 @@ module ClosureTree
13
13
  ) AS generation_depth
14
14
  ON #{quoted_table_name}.#{model_class.primary_key} = generation_depth.descendant_id
15
15
  SQL
16
- scope_with_order(model_class.joins(generation_depth), 'generation_depth.depth')
16
+ scope_with_order(scope.joins(generation_depth), 'generation_depth.depth')
17
17
  end
18
18
 
19
19
  def hash_tree(tree_scope, limit_depth = nil)
20
- limited_scope = if tree_scope
21
- limit_depth ? tree_scope.where("#{quoted_hierarchy_table_name}.generations <= #{limit_depth - 1}") : tree_scope
22
- else
23
- default_tree_scope(limit_depth)
24
- end
20
+ limited_scope = limit_depth ? tree_scope.where("#{quoted_hierarchy_table_name}.generations <= #{limit_depth - 1}") : tree_scope
25
21
  build_hash_tree(limited_scope)
26
22
  end
27
23
 
@@ -36,7 +36,7 @@ module ClosureTree
36
36
 
37
37
  def _ct_after_save
38
38
  if changes[_ct.parent_column_name] || @was_new_record
39
- rebuild! unless @_ct_skip_hierarchy_maintenance
39
+ rebuild!
40
40
  end
41
41
  if changes[_ct.parent_column_name] && !@was_new_record
42
42
  # Resetting the ancestral collections addresses
@@ -5,11 +5,13 @@ module ClosureTree
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- belongs_to :parent,
9
- class_name: _ct.model_class.to_s,
10
- foreign_key: _ct.parent_column_name,
11
- inverse_of: :children,
12
- touch: _ct.options[:touch]
8
+
9
+ belongs_to :parent, nil, *_ct.belongs_to_with_optional_option(
10
+ class_name: _ct.model_class.to_s,
11
+ foreign_key: _ct.parent_column_name,
12
+ inverse_of: :children,
13
+ touch: _ct.options[:touch],
14
+ optional: true)
13
15
 
14
16
  order_by_generations = "#{_ct.quoted_hierarchy_table_name}.generations asc"
15
17
 
@@ -76,6 +76,10 @@ module ClosureTree
76
76
  end
77
77
  end
78
78
 
79
+ def belongs_to_with_optional_option(opts)
80
+ [ActiveRecord::VERSION::MAJOR < 5 ? opts.except(:optional) : opts]
81
+ end
82
+
79
83
  # lambda-ize the order, but don't apply the default order_option
80
84
  def has_many_without_order_option(opts)
81
85
  [lambda { order(opts[:order]) }, opts.except(:order)]
@@ -1,3 +1,5 @@
1
+ require 'closure_tree'
2
+
1
3
  module ClosureTree
2
4
  module Test
3
5
  module Matcher
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = Gem::Version.new('6.0.0')
2
+ VERSION = Gem::Version.new('6.1.0')
3
3
  end
@@ -0,0 +1,12 @@
1
+ module ClosureTree
2
+ module Generators # :nodoc:
3
+ class ConfigGenerator < Rails::Generators::Base # :nodoc:
4
+ source_root File.expand_path('../templates', __FILE__)
5
+ desc 'Install closure tree config.'
6
+
7
+ def config
8
+ template 'config.rb', 'config/initializers/closure_tree_config.rb'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ ClosureTree.configure do |config|
2
+ # Some PaaS like Heroku don't have available the db in some build steps like
3
+ # assets:precompile, this is skipped when this value is true, default = false
4
+ # config.database_less = true
5
+ end
@@ -1,48 +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
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
@@ -252,7 +252,7 @@ describe Label do
252
252
  end
253
253
 
254
254
  def children_name_and_order
255
- name_and_order(@parent.children(reload = true))
255
+ name_and_order(@parent.children.reload)
256
256
  end
257
257
 
258
258
  def roots_name_and_order
@@ -454,7 +454,7 @@ describe Label do
454
454
  it 'should retain sort orders of descendants when moving to a new parent' do
455
455
  expected_order = ('a'..'z').to_a.shuffle
456
456
  expected_order.map { |ea| first_root.add_child(Label.new(name: ea)) }
457
- actual_order = first_root.children(reload = true).pluck(:name)
457
+ actual_order = first_root.children.reload.pluck(:name)
458
458
  expect(actual_order).to eq(expected_order)
459
459
  last_root.append_child(first_root)
460
460
  expect(last_root.self_and_descendants.pluck(:name)).to eq(%w(10 0) + expected_order)
@@ -465,13 +465,13 @@ describe Label do
465
465
  z = first_root.find_or_create_by_path(path)
466
466
  z_children_names = (100..150).to_a.shuffle.map { |ea| ea.to_s }
467
467
  z_children_names.reverse.each { |ea| z.prepend_child(Label.new(name: ea)) }
468
- expect(z.children(reload = true).pluck(:name)).to eq(z_children_names)
468
+ expect(z.children.reload.pluck(:name)).to eq(z_children_names)
469
469
  a = first_root.find_by_path(['a'])
470
470
  # move b up to a's level:
471
471
  b = a.children.first
472
472
  a.add_sibling(b)
473
473
  expect(b.parent).to eq(first_root)
474
- expect(z.children(reload = true).pluck(:name)).to eq(z_children_names)
474
+ expect(z.children.reload.pluck(:name)).to eq(z_children_names)
475
475
  end
476
476
  end
477
477
 
@@ -524,4 +524,31 @@ describe Label do
524
524
  expect(Label.roots_and_descendants_preordered.collect { |ea| ea.name }).to eq(expected)
525
525
  end
526
526
  end unless sqlite? # sqlite doesn't have a power function.
527
+
528
+ context 'hash_tree' do
529
+ before do
530
+ @a = EventLabel.create(name: 'a')
531
+ @b = DateLabel.create(name: 'b')
532
+ @c = DirectoryLabel.create(name: 'c')
533
+ (1..3).each { |i| DirectoryLabel.create!(name: "c#{ i }", mother_id: @c.id) }
534
+ end
535
+ it 'should return tree with correct scope when called on class' do
536
+ tree = DirectoryLabel.hash_tree
537
+ expect(tree.keys.size).to eq(1)
538
+ expect(tree.keys.first).to eq(@c)
539
+ expect(tree[@c].keys.size).to eq(3)
540
+ end
541
+ it 'should return tree with correct scope when called on all' do
542
+ tree = DirectoryLabel.all.hash_tree
543
+ expect(tree.keys.size).to eq(1)
544
+ expect(tree.keys.first).to eq(@c)
545
+ expect(tree[@c].keys.size).to eq(3)
546
+ end
547
+ it 'should return tree with correct scope when called on scope chain' do
548
+ tree = Label.where(name: 'b').hash_tree
549
+ expect(tree.keys.size).to eq(1)
550
+ expect(tree.keys.first).to eq(@b)
551
+ expect(tree[@b]).to eq({})
552
+ end
553
+ end
527
554
  end
@@ -1,9 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  # We don't need to run the expensive parallel tests for every combination of prefix/suffix.
4
- # Those affect SQL generation, not parallelism
4
+ # Those affect SQL generation, not parallelism.
5
+ # SQLite doesn't support concurrency reliably, either.
5
6
  def run_parallel_tests?
6
- ActiveRecord::Base.table_name_prefix.empty? &&
7
+ !sqlite? &&
8
+ ActiveRecord::Base.table_name_prefix.empty? &&
7
9
  ActiveRecord::Base.table_name_suffix.empty?
8
10
  end
9
11
 
@@ -11,7 +13,6 @@ def max_threads
11
13
  5
12
14
  end
13
15
 
14
-
15
16
  class WorkerBase
16
17
  extend Forwardable
17
18
  attr_reader :name
@@ -106,7 +107,7 @@ describe 'Concurrent creation' do
106
107
  run_workers
107
108
  # duplication from at least one iteration:
108
109
  expect(Tag.where(name: @names).size).to be > @iterations
109
- end unless sqlite? # sqlite throws errors from concurrent access
110
+ end
110
111
 
111
112
  class SiblingPrependerWorker < WorkerBase
112
113
  def before_work
@@ -119,50 +120,6 @@ describe 'Concurrent creation' do
119
120
  end
120
121
  end
121
122
 
122
- # TODO: this test should be rewritten to be proper producer-consumer code
123
- xit 'fails to deadlock from parallel sibling churn' do
124
- # target should be non-trivially long to maximize time spent in hierarchy maintenance
125
- target = Tag.find_or_create_by_path(('a'..'z').to_a + ('A'..'Z').to_a)
126
- expected_children = (1..100).to_a.map { |ea| "root ##{ea}" }
127
- children_to_add = expected_children.dup
128
- added_children = []
129
- children_to_delete = []
130
- deleted_children = []
131
- creator_threads = @workers.times.map do
132
- Thread.new do
133
- while children_to_add.present?
134
- name = children_to_add.shift
135
- unless name.nil?
136
- Tag.transaction { target.find_or_create_by_path(name) }
137
- children_to_delete << name
138
- added_children << name
139
- end
140
- end
141
- end
142
- end
143
- run_destruction = true
144
- destroyer_threads = @workers.times.map do
145
- Thread.new do
146
- begin
147
- victim_name = children_to_delete.shift
148
- if victim_name
149
- Tag.transaction do
150
- victim = target.children.where(name: victim_name).first
151
- victim.destroy
152
- deleted_children << victim_name
153
- end
154
- else
155
- sleep rand # wait for more victims
156
- end
157
- end while run_destruction || !children_to_delete.empty?
158
- end
159
- end
160
- creator_threads.each(&:join)
161
- destroyer_threads.each(&:join)
162
- expect(added_children).to match(expected_children)
163
- expect(deleted_children).to match(expected_children)
164
- end
165
-
166
123
  it 'fails to deadlock while simultaneously deleting items from the same hierarchy' do
167
124
  target = User.find_or_create_by_path((1..200).to_a.map { |ea| ea.to_s })
168
125
  emails = target.self_and_ancestors.to_a.map(&:email).shuffle
@@ -176,7 +133,7 @@ describe 'Concurrent creation' do
176
133
  end
177
134
  User.connection.reconnect!
178
135
  expect(User.all).to be_empty
179
- end unless sqlite? # sqlite throws errors from concurrent access
136
+ end
180
137
 
181
138
  class SiblingPrependerWorker < WorkerBase
182
139
  def before_work
@@ -198,6 +155,5 @@ describe 'Concurrent creation' do
198
155
 
199
156
  # The only non-root node should be "root":
200
157
  expect(Label.all.select { |ea| ea.root? }).to eq([@target.parent])
201
- end unless sqlite? # sqlite throws errors from concurrent access
202
-
158
+ end
203
159
  end if run_parallel_tests?
@@ -2,10 +2,8 @@ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
2
2
 
3
3
  ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
4
4
  require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5
- require 'rspec'
6
- require 'active_record'
5
+
7
6
  require 'database_cleaner'
8
- require 'closure_tree'
9
7
  require 'closure_tree/test/matcher'
10
8
  require 'tmpdir'
11
9
  require 'timecop'
@@ -1,5 +1,5 @@
1
1
  database_folder = "#{File.dirname(__FILE__)}/../db"
2
- database_adapter = ENV['DB'] ||= 'mysql'
2
+ database_adapter = ENV['DB'] ||= 'postgresql'
3
3
 
4
4
  def sqlite?
5
5
  ENV['DB'] == 'sqlite'
data/tests.sh CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/bin/sh -ex
2
2
 
3
- for RMI in 2.2.3 #jruby-1.6.13 :P
3
+ for RMI in 2.3.1 #jruby-1.6.13 :P
4
4
  do
5
5
  rbenv local $RMI
6
- for DB in postgresql mysql sqlite
6
+ appraisal bundle install
7
+ for DB in mysql sqlite postgresql
7
8
  do
8
9
  appraisal rake spec:all WITH_ADVISORY_LOCK_PREFIX=$(date +%s) DB=$DB
9
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: 6.0.0
4
+ version: 6.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: 2015-11-02 00:00:00.000000000 Z
11
+ date: 2016-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -56,16 +56,16 @@ dependencies:
56
56
  name: rspec-rails
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 3.2.3
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 3.2.3
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: database_cleaner
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -122,20 +122,6 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
- - !ruby/object:Gem::Dependency
126
- name: ammeter
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - '='
130
- - !ruby/object:Gem::Version
131
- version: 1.1.2
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - '='
137
- - !ruby/object:Gem::Version
138
- version: 1.1.2
139
125
  description: Easily and efficiently make your ActiveRecord model support hierarchies
140
126
  email:
141
127
  - matthew-github@mceachen.org
@@ -156,11 +142,14 @@ files:
156
142
  - closure_tree.gemspec
157
143
  - gemfiles/activerecord_4.1.gemfile
158
144
  - gemfiles/activerecord_4.2.gemfile
145
+ - gemfiles/activerecord_5.0.gemfile
146
+ - gemfiles/activerecord_5.0_foreigner.gemfile
159
147
  - gemfiles/activerecord_edge.gemfile
160
148
  - img/example.png
161
149
  - img/preorder.png
162
150
  - lib/closure_tree.rb
163
151
  - lib/closure_tree/active_record_support.rb
152
+ - lib/closure_tree/configuration.rb
164
153
  - lib/closure_tree/deterministic_ordering.rb
165
154
  - lib/closure_tree/digraphs.rb
166
155
  - lib/closure_tree/finders.rb
@@ -176,7 +165,9 @@ files:
176
165
  - lib/closure_tree/support_flags.rb
177
166
  - lib/closure_tree/test/matcher.rb
178
167
  - lib/closure_tree/version.rb
168
+ - lib/generators/closure_tree/config_generator.rb
179
169
  - lib/generators/closure_tree/migration_generator.rb
170
+ - lib/generators/closure_tree/templates/config.rb
180
171
  - lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb
181
172
  - mktree.rb
182
173
  - spec/cache_invalidation_spec.rb
@@ -216,7 +207,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
216
207
  requirements:
217
208
  - - ">="
218
209
  - !ruby/object:Gem::Version
219
- version: 2.1.0
210
+ version: 2.0.0
220
211
  required_rubygems_version: !ruby/object:Gem::Requirement
221
212
  requirements:
222
213
  - - ">="
@@ -224,7 +215,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
224
215
  version: '0'
225
216
  requirements: []
226
217
  rubyforge_project:
227
- rubygems_version: 2.4.5.1
218
+ rubygems_version: 2.5.1
228
219
  signing_key:
229
220
  specification_version: 4
230
221
  summary: Easily and efficiently make your ActiveRecord model support hierarchies