closure_tree 6.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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