closure_tree 6.6.0 → 7.0.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.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +7 -6
  3. data/Appraisals +59 -1
  4. data/CHANGELOG.md +21 -0
  5. data/Gemfile +0 -12
  6. data/README.md +40 -3
  7. data/closure_tree.gemspec +10 -7
  8. data/lib/closure_tree/finders.rb +15 -5
  9. data/lib/closure_tree/has_closure_tree.rb +2 -0
  10. data/lib/closure_tree/has_closure_tree_root.rb +2 -6
  11. data/lib/closure_tree/hash_tree_support.rb +1 -1
  12. data/lib/closure_tree/hierarchy_maintenance.rb +3 -3
  13. data/lib/closure_tree/model.rb +31 -1
  14. data/lib/closure_tree/numeric_deterministic_ordering.rb +11 -2
  15. data/lib/closure_tree/numeric_order_support.rb +3 -0
  16. data/lib/closure_tree/support.rb +7 -3
  17. data/lib/closure_tree/support_attributes.rb +9 -0
  18. data/lib/closure_tree/support_flags.rb +1 -4
  19. data/lib/closure_tree/version.rb +1 -1
  20. data/lib/generators/closure_tree/migration_generator.rb +8 -0
  21. data/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb +1 -1
  22. metadata +28 -76
  23. data/gemfiles/activerecord_4.2.gemfile +0 -19
  24. data/gemfiles/activerecord_5.0.gemfile +0 -19
  25. data/gemfiles/activerecord_5.1.gemfile +0 -19
  26. data/gemfiles/activerecord_edge.gemfile +0 -20
  27. data/img/example.png +0 -0
  28. data/img/preorder.png +0 -0
  29. data/spec/cache_invalidation_spec.rb +0 -39
  30. data/spec/cuisine_type_spec.rb +0 -38
  31. data/spec/db/database.yml +0 -21
  32. data/spec/db/models.rb +0 -128
  33. data/spec/db/schema.rb +0 -166
  34. data/spec/fixtures/tags.yml +0 -98
  35. data/spec/generators/migration_generator_spec.rb +0 -48
  36. data/spec/has_closure_tree_root_spec.rb +0 -154
  37. data/spec/hierarchy_maintenance_spec.rb +0 -16
  38. data/spec/label_spec.rb +0 -554
  39. data/spec/matcher_spec.rb +0 -34
  40. data/spec/metal_spec.rb +0 -55
  41. data/spec/model_spec.rb +0 -9
  42. data/spec/namespace_type_spec.rb +0 -13
  43. data/spec/parallel_spec.rb +0 -159
  44. data/spec/pool_spec.rb +0 -27
  45. data/spec/spec_helper.rb +0 -24
  46. data/spec/support/database.rb +0 -52
  47. data/spec/support/database_cleaner.rb +0 -14
  48. data/spec/support/exceed_query_limit.rb +0 -18
  49. data/spec/support/hash_monkey_patch.rb +0 -13
  50. data/spec/support/query_counter.rb +0 -18
  51. data/spec/support/sqlite3_with_advisory_lock.rb +0 -10
  52. data/spec/support_spec.rb +0 -14
  53. data/spec/tag_examples.rb +0 -665
  54. data/spec/tag_spec.rb +0 -6
  55. data/spec/user_spec.rb +0 -174
  56. data/spec/uuid_tag_spec.rb +0 -6
@@ -65,10 +65,15 @@ module ClosureTree
65
65
  node_score = "(1 + anc.#{_ct.quoted_order_column(false)}) * " +
66
66
  "power(#{h['total_descendants']}, #{h['max_depth'].to_i + 1} - #{depth_column})"
67
67
 
68
- "sum(#{node_score})"
68
+ # We want the NULLs to be first in case we are not ordering roots and they have NULL order.
69
+ Arel.sql("SUM(#{node_score}) IS NULL DESC, SUM(#{node_score})")
69
70
  end
70
71
 
71
72
  def roots_and_descendants_preordered
73
+ if _ct.dont_order_roots
74
+ raise ClosureTree::RootOrderingDisabledError.new("Root ordering is disabled on this model")
75
+ end
76
+
72
77
  join_sql = <<-SQL.strip_heredoc
73
78
  JOIN #{_ct.quoted_hierarchy_table_name} anc_hier
74
79
  ON anc_hier.descendant_id = #{_ct.quoted_table_name}.#{_ct.quoted_id_column_name}
@@ -78,7 +83,7 @@ module ClosureTree
78
83
  SELECT descendant_id, max(generations) AS max_depth
79
84
  FROM #{_ct.quoted_hierarchy_table_name}
80
85
  GROUP BY descendant_id
81
- ) AS depths ON depths.descendant_id = anc.#{_ct.quoted_id_column_name}
86
+ ) #{ _ct.t_alias_keyword } depths ON depths.descendant_id = anc.#{_ct.quoted_id_column_name}
82
87
  SQL
83
88
  joins(join_sql)
84
89
  .group("#{_ct.quoted_table_name}.#{_ct.quoted_id_column_name}")
@@ -113,6 +118,10 @@ module ClosureTree
113
118
  def add_sibling(sibling, add_after = true)
114
119
  fail "can't add self as sibling" if self == sibling
115
120
 
121
+ if _ct.dont_order_roots && parent.nil?
122
+ raise ClosureTree::RootOrderingDisabledError.new("Root ordering is disabled on this model")
123
+ end
124
+
116
125
  # Make sure self isn't dirty, because we're going to call reload:
117
126
  save
118
127
 
@@ -14,6 +14,7 @@ module ClosureTree
14
14
 
15
15
  module MysqlAdapter
16
16
  def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil)
17
+ return if parent_id.nil? && dont_order_roots
17
18
  min_where = if minimum_sort_order_value
18
19
  "AND #{quoted_order_column} >= #{minimum_sort_order_value}"
19
20
  else
@@ -31,6 +32,7 @@ module ClosureTree
31
32
 
32
33
  module PostgreSQLAdapter
33
34
  def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil)
35
+ return if parent_id.nil? && dont_order_roots
34
36
  min_where = if minimum_sort_order_value
35
37
  "AND #{quoted_order_column} >= #{minimum_sort_order_value}"
36
38
  else
@@ -56,6 +58,7 @@ module ClosureTree
56
58
 
57
59
  module GenericAdapter
58
60
  def reorder_with_parent_id(parent_id, minimum_sort_order_value = nil)
61
+ return if parent_id.nil? && dont_order_roots
59
62
  scope = model_class.
60
63
  where(parent_column_sym => parent_id).
61
64
  order(nulls_last_order_by)
@@ -22,7 +22,8 @@ module ClosureTree
22
22
  :parent_column_name => 'parent_id',
23
23
  :dependent => :nullify, # or :destroy or :delete_all -- see the README
24
24
  :name_column => 'name',
25
- :with_advisory_lock => true
25
+ :with_advisory_lock => true,
26
+ :numeric_order => false
26
27
  }.merge(options)
27
28
  raise ArgumentError, "name_column can't be 'path'" if options[:name_column] == 'path'
28
29
  if order_is_numeric?
@@ -82,12 +83,15 @@ module ClosureTree
82
83
 
83
84
  # lambda-ize the order, but don't apply the default order_option
84
85
  def has_many_without_order_option(opts)
85
- [lambda { order(opts[:order]) }, opts.except(:order)]
86
+ [lambda { order(opts[:order].call) }, opts.except(:order)]
86
87
  end
87
88
 
88
89
  def has_many_with_order_option(opts)
89
90
  order_options = [opts[:order], order_by].compact
90
- [lambda { order(order_options) }, opts.except(:order)]
91
+ [lambda {
92
+ order_options = order_options.map { |o| o.is_a?(Proc) ? o.call : o }
93
+ order(order_options)
94
+ }, opts.except(:order)]
91
95
  end
92
96
 
93
97
  def ids_from(scope)
@@ -75,6 +75,10 @@ module ClosureTree
75
75
  options[:order]
76
76
  end
77
77
 
78
+ def dont_order_roots
79
+ options[:dont_order_roots] || false
80
+ end
81
+
78
82
  def nulls_last_order_by
79
83
  "-#{quoted_order_column} #{order_by_order(reverse = true)}"
80
84
  end
@@ -110,5 +114,10 @@ module ClosureTree
110
114
  prefix = include_table_name ? "#{quoted_table_name}." : ""
111
115
  "#{prefix}#{connection.quote_column_name(order_column)}"
112
116
  end
117
+
118
+ # table_name alias keyword , like "AS". When used on table name alias, Oracle Database don't support used 'AS'
119
+ def t_alias_keyword
120
+ (ActiveRecord::Base.connection.adapter_name.to_sym == :OracleEnhanced) ? "" : "AS"
121
+ end
113
122
  end
114
123
  end
@@ -17,10 +17,7 @@ module ClosureTree
17
17
  end
18
18
 
19
19
  def order_is_numeric?
20
- # The table might not exist yet (in the case of ActiveRecord::Observer use, see issue 32)
21
- return false if !order_option? || !model_class.table_exists?
22
- c = model_class.columns_hash[order_column]
23
- c && c.type == :integer
20
+ options[:numeric_order]
24
21
  end
25
22
 
26
23
  def subclass?
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = Gem::Version.new('6.6.0')
2
+ VERSION = Gem::Version.new('7.0.0')
3
3
  end
@@ -1,5 +1,6 @@
1
1
  require 'closure_tree/active_record_support'
2
2
  require 'forwardable'
3
+ require 'rails/generators'
3
4
  require 'rails/generators/active_record'
4
5
  require 'rails/generators/named_base'
5
6
 
@@ -41,6 +42,13 @@ module ClosureTree
41
42
  end
42
43
  end
43
44
 
45
+ def migration_version
46
+ major = ActiveRecord::VERSION::MAJOR
47
+ if major >= 5
48
+ "[#{major}.#{ActiveRecord::VERSION::MINOR}]"
49
+ end
50
+ end
51
+
44
52
  def self.next_migration_number(dirname)
45
53
  ActiveRecord::Generators::Base.next_migration_number(dirname)
46
54
  end
@@ -1,4 +1,4 @@
1
- class <%= migration_class_name %> < ActiveRecord::Migration
1
+ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
3
  create_table :<%= migration_name %>, id: false do |t|
4
4
  t.<%= primary_key_type %> :ancestor_id, null: false
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.6.0
4
+ version: 7.0.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: 2017-07-12 00:00:00.000000000 Z
11
+ date: 2018-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,30 +16,30 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.1.0
19
+ version: 4.2.10
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 4.1.0
26
+ version: 4.2.10
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: with_advisory_lock
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 3.0.0
33
+ version: 4.0.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 3.0.0
40
+ version: 4.0.0
41
41
  - !ruby/object:Gem::Dependency
42
- name: rspec-instafail
42
+ name: appraisal
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rspec-rails
56
+ name: database_cleaner
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: database_cleaner
70
+ name: generator_spec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -81,7 +81,7 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: appraisal
84
+ name: parallel
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
@@ -95,7 +95,7 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: timecop
98
+ name: rspec-instafail
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
@@ -109,7 +109,21 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: parallel
112
+ name: rspec-rails
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: timecop
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
129
  - - ">="
@@ -140,12 +154,6 @@ files:
140
154
  - README.md
141
155
  - Rakefile
142
156
  - closure_tree.gemspec
143
- - gemfiles/activerecord_4.2.gemfile
144
- - gemfiles/activerecord_5.0.gemfile
145
- - gemfiles/activerecord_5.1.gemfile
146
- - gemfiles/activerecord_edge.gemfile
147
- - img/example.png
148
- - img/preorder.png
149
157
  - lib/closure_tree.rb
150
158
  - lib/closure_tree/active_record_support.rb
151
159
  - lib/closure_tree/configuration.rb
@@ -170,34 +178,6 @@ files:
170
178
  - lib/generators/closure_tree/templates/config.rb
171
179
  - lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb
172
180
  - mktree.rb
173
- - spec/cache_invalidation_spec.rb
174
- - spec/cuisine_type_spec.rb
175
- - spec/db/database.yml
176
- - spec/db/models.rb
177
- - spec/db/schema.rb
178
- - spec/fixtures/tags.yml
179
- - spec/generators/migration_generator_spec.rb
180
- - spec/has_closure_tree_root_spec.rb
181
- - spec/hierarchy_maintenance_spec.rb
182
- - spec/label_spec.rb
183
- - spec/matcher_spec.rb
184
- - spec/metal_spec.rb
185
- - spec/model_spec.rb
186
- - spec/namespace_type_spec.rb
187
- - spec/parallel_spec.rb
188
- - spec/pool_spec.rb
189
- - spec/spec_helper.rb
190
- - spec/support/database.rb
191
- - spec/support/database_cleaner.rb
192
- - spec/support/exceed_query_limit.rb
193
- - spec/support/hash_monkey_patch.rb
194
- - spec/support/query_counter.rb
195
- - spec/support/sqlite3_with_advisory_lock.rb
196
- - spec/support_spec.rb
197
- - spec/tag_examples.rb
198
- - spec/tag_spec.rb
199
- - spec/user_spec.rb
200
- - spec/uuid_tag_spec.rb
201
181
  - tests.sh
202
182
  homepage: http://mceachen.github.io/closure_tree/
203
183
  licenses:
@@ -219,36 +199,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
219
199
  version: '0'
220
200
  requirements: []
221
201
  rubyforge_project:
222
- rubygems_version: 2.6.8
202
+ rubygems_version: 2.7.6
223
203
  signing_key:
224
204
  specification_version: 4
225
205
  summary: Easily and efficiently make your ActiveRecord model support hierarchies
226
- test_files:
227
- - spec/cache_invalidation_spec.rb
228
- - spec/cuisine_type_spec.rb
229
- - spec/db/database.yml
230
- - spec/db/models.rb
231
- - spec/db/schema.rb
232
- - spec/fixtures/tags.yml
233
- - spec/generators/migration_generator_spec.rb
234
- - spec/has_closure_tree_root_spec.rb
235
- - spec/hierarchy_maintenance_spec.rb
236
- - spec/label_spec.rb
237
- - spec/matcher_spec.rb
238
- - spec/metal_spec.rb
239
- - spec/model_spec.rb
240
- - spec/namespace_type_spec.rb
241
- - spec/parallel_spec.rb
242
- - spec/pool_spec.rb
243
- - spec/spec_helper.rb
244
- - spec/support/database.rb
245
- - spec/support/database_cleaner.rb
246
- - spec/support/exceed_query_limit.rb
247
- - spec/support/hash_monkey_patch.rb
248
- - spec/support/query_counter.rb
249
- - spec/support/sqlite3_with_advisory_lock.rb
250
- - spec/support_spec.rb
251
- - spec/tag_examples.rb
252
- - spec/tag_spec.rb
253
- - spec/user_spec.rb
254
- - spec/uuid_tag_spec.rb
206
+ test_files: []
@@ -1,19 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 4.2.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 => "../"
@@ -1,19 +0,0 @@
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 => "../"
@@ -1,19 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 5.1.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 => "../"
@@ -1,20 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", :github => "rails/rails"
6
- gem "arel", :github => "rails/arel"
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 => "../"
Binary file
Binary file
@@ -1,39 +0,0 @@
1
- require 'spec_helper'
2
-
3
-
4
- describe 'cache invalidation', cache: true do
5
- before do
6
- Timecop.travel(10.seconds.ago) do
7
- #create a long tree with 2 branch
8
- @root = MenuItem.create(
9
- name: SecureRandom.hex(10)
10
- )
11
- 2.times do
12
- parent = @root
13
- 10.times do
14
- parent = parent.children.create(
15
- name: SecureRandom.hex(10)
16
- )
17
- end
18
- end
19
- @first_leaf = MenuItem.leaves.first
20
- @second_leaf = MenuItem.leaves.last
21
- end
22
- end
23
-
24
- describe 'touch option' do
25
- it 'should invalidate cache for all it ancestors' do
26
- old_time_stamp = @first_leaf.ancestors.pluck(:updated_at)
27
- @first_leaf.touch
28
- new_time_stamp = @first_leaf.ancestors.pluck(:updated_at)
29
- expect(old_time_stamp).to_not eq(new_time_stamp)
30
- end
31
-
32
- it 'should not invalidate cache for another branch' do
33
- old_time_stamp = @second_leaf.updated_at
34
- @first_leaf.touch
35
- new_time_stamp = @second_leaf.updated_at
36
- expect(old_time_stamp).to eq(new_time_stamp)
37
- end
38
- end
39
- end