activerecord-hierarchical_query 1.0.1 → 1.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
- SHA1:
3
- metadata.gz: 81984079ce907a96ab19520c8ecd77bcf587b0b4
4
- data.tar.gz: a75cf7a24893407ab3be1c30289fdb3d1e6d926a
2
+ SHA256:
3
+ metadata.gz: 31640e2b4355e1204bc3ff3c233124de13269b864b4b55a893c528f23bfac6dc
4
+ data.tar.gz: c6a07c2b90fc9121d62cdf4be640f87abbc841e8055cdb0fecdef90999885e26
5
5
  SHA512:
6
- metadata.gz: a5de9642594d03716e3ee6cda1ee023e2362c2608eb06d50321611eab31e09b7b7d01f03ddecd062d38dd0579bf87002625a21d2e0230e6a582ea121cb6bdb3e
7
- data.tar.gz: 968afb8c7471b36a6e2029f5f8022a325e521a1808fbace12365be16c83aa360785c9009ee566403c9b7eb0a14ed2c58945f1ca51464c6b85ef612b28186248c
6
+ metadata.gz: 8b6151f2d1ca6cbaa9c0a85ab5fd9e1242a4bdf8bbeac17634b887d7084ea0fc39fd980f5052d10a8c76dd4df6d05b3160007fee707a162b5d1fb3af8d65ce58
7
+ data.tar.gz: 2b686e08d2ec2189952f6a76c8e78578c75a10cd8410a2ec4872ab5372c547a57dd9b6b63616b9fd0567713ad99254d5e1a8759e5e1ac9d3dd2fee9c277626b3
data/README.md CHANGED
@@ -3,12 +3,42 @@
3
3
  [![Build Status](https://travis-ci.org/take-five/activerecord-hierarchical_query.png?branch=master)](https://travis-ci.org/take-five/activerecord-hierarchical_query)
4
4
  [![Code Climate](https://codeclimate.com/github/take-five/activerecord-hierarchical_query.png)](https://codeclimate.com/github/take-five/activerecord-hierarchical_query)
5
5
  [![Coverage Status](https://coveralls.io/repos/take-five/activerecord-hierarchical_query/badge.png)](https://coveralls.io/r/take-five/activerecord-hierarchical_query)
6
- [![Dependency Status](https://gemnasium.com/take-five/activerecord-hierarchical_query.png)](https://gemnasium.com/take-five/activerecord-hierarchical_query)
7
6
  [![Gem Version](https://badge.fury.io/rb/activerecord-hierarchical_query.png)](http://badge.fury.io/rb/activerecord-hierarchical_query)
8
7
 
9
- Create hierarchical queries using simple DSL, recursively traverse trees using single SQL query.
8
+ Create hierarchical queries using simple DSL, recursively
9
+ traverse trees using single SQL query.
10
10
 
11
- If a table contains hierarchical data, then you can select rows in hierarchical order using hierarchical query builder.
11
+ If a table contains hierarchical data, then you can select rows
12
+ in hierarchical order using hierarchical query builder.
13
+
14
+ ## Requirements
15
+
16
+ * ActiveRecord ~> 5.2
17
+ * PostgreSQL >= 8.4
18
+
19
+ Note that though PostgresSQL 8.4 and up should work, this library
20
+ is tested on PostgresSQL 10.5.
21
+
22
+ ### Rails Version
23
+
24
+ **Rails 3 support has ended.**
25
+
26
+ **Support for Rails 4 is limited to review of security related
27
+ pull requests that include tests.** EOL for Rails 4 is when
28
+ Rails 6 comes out.
29
+
30
+ Rails 5 is supported on the `rails-5` branch and through gem
31
+ versions `>= 1.0.0` on rubygems.org. This branch will soon
32
+ replace master. If you have trouble with Rails 5.1 or 5.0,
33
+ upgrade to Rails 5.2 and install the latest version.
34
+
35
+ Now that @zachaysan is the new primary maintainer of this
36
+ project, support for laggard versions of Rails is going to be
37
+ minimal. Security updates will be accepted, but due to structural
38
+ changes in Rails core it's no longer possible to easily support
39
+ former versions of Rails.
40
+
41
+ ## In a nutshell
12
42
 
13
43
  ### Traverse trees
14
44
 
@@ -59,25 +89,12 @@ records = Category.join_recursive do |query|
59
89
  .connect_by(parent_id: :id)
60
90
  end.order('depth ASC')
61
91
 
62
- # returned value is just regular ActiveRecord::Relation instance, so you can use its methods
92
+ # returns a regular ActiveRecord::Relation instance
93
+ # so methods like `pluck` all work as expected.
94
+
63
95
  crumbs = records.pluck(:name).join(' / ')
64
96
  ```
65
97
 
66
- ## Requirements
67
-
68
- * ActiveRecord >= 3.1.0
69
- * PostgreSQL >= 8.4
70
-
71
- ## Rails 5
72
-
73
- Rails 5 is supported on the `rails-5` branch and through gem versions
74
- `>= 1.0.0` on rubygems.org. The Rails branch is intended to
75
- follow minor Rails releases (currently 5.1), but it should be
76
- compatible with 5.0 as well. If you have trouble try upgrading Rails
77
- first. Tag @zachaysan with in a GitHub issue if the latest version
78
- of Rails is not supported or if there are reproducable problems on
79
- the latest minor version of Rails 5.
80
-
81
98
  ## Installation
82
99
 
83
100
  Add this line to your application's Gemfile:
@@ -106,12 +123,12 @@ Alternatively, the require can be placed in the `Gemfile`:
106
123
  gem 'activerecord-hierarchical_query', require: 'active_record/hierarchical_query'
107
124
  ```
108
125
 
109
-
110
126
  ## Usage
111
127
 
112
- Let's say you've got an ActiveRecord model `Category` with attributes `id`, `parent_id`
113
- and `name`. You can traverse nodes recursively starting from root rows connected by
114
- `parent_id` column ordered by `name`:
128
+ Let's say you've got an ActiveRecord model `Category` with
129
+ attributes `id`, `parent_id` and `name`. You can traverse nodes
130
+ recursively starting from root rows connected by `parent_id`
131
+ column ordered by `name`:
115
132
 
116
133
  ```ruby
117
134
  Category.join_recursive do
@@ -140,10 +157,12 @@ Hierarchical queries are processed as follows:
140
157
  * First, root rows are selected -- those rows that satisfy `START WITH` condition in
141
158
  order specified by `ORDER SIBLINGS` clause. In example above it's specified by
142
159
  statements `query.start_with(parent_id: nil)` and `query.order_siblings(:name)`.
160
+
143
161
  * Second, child rows for each root rows are selected. Each child row must satisfy
144
162
  condition specified by `CONNECT BY` clause with respect to one of the root rows
145
163
  (`query.connect_by(id: :parent_id)` in example above). Order of child rows is
146
164
  also specified by `ORDER SIBLINGS` clause.
165
+
147
166
  * Successive generations of child rows are selected with respect to `CONNECT BY` clause.
148
167
  First the children of each row selected in step 2 selected, then the children of those
149
168
  children and so on.
@@ -201,7 +220,8 @@ Category.join_recursive do
201
220
  end
202
221
  ```
203
222
 
204
- You can even refer to parent table, just don't forget to include columns in `SELECT` clause!
223
+ You can even refer to parent table, just don't forget to include
224
+ columns in `SELECT` clause!
205
225
 
206
226
  ```ruby
207
227
  Category.join_recursive do |query|
@@ -222,8 +242,9 @@ end
222
242
 
223
243
  ### NOCYCLE
224
244
 
225
- Recursive query will loop if hierarchy contains cycles (your graph is not acyclic).
226
- `NOCYCLE` clause, which is turned off by default, could prevent it.
245
+ Recursive query will loop if hierarchy contains cycles (your
246
+ graph is not acyclic). `NOCYCLE` clause, which is turned off by
247
+ default, could prevent it.
227
248
 
228
249
  Loop example:
229
250
 
@@ -235,7 +256,8 @@ node_1.parent = node_2
235
256
  node_1.save
236
257
  ```
237
258
 
238
- `node_1` and `node_2` now link to each other, so following query will never end:
259
+ `node_1` and `node_2` now link to each other, so the following
260
+ query will not terminate:
239
261
 
240
262
  ```ruby
241
263
  Category.join_recursive do |query|
@@ -255,8 +277,11 @@ end
255
277
  ```
256
278
 
257
279
  ## DISTINCT
258
- By default, the union term in the Common Table Expression uses a `UNION ALL`. If you want
259
- to `SELECT DISTINCT` CTE values, add a query option for `distinct`:
280
+
281
+ By default, the union term in the Common Table Expression uses a
282
+ `UNION ALL`. If you want to `SELECT DISTINCT` CTE values, add a
283
+ query option for `distinct`:
284
+
260
285
  ```ruby
261
286
  Category.join_recursive do |query|
262
287
  query.connect_by(id: :parent_id)
@@ -265,7 +290,9 @@ Category.join_recursive do |query|
265
290
  end
266
291
  ```
267
292
 
268
- If you want to join CTE terms by `UNION DISTINCT`, pass an option to `join_recursive`:
293
+ If you want to join CTE terms by `UNION DISTINCT`, pass an option
294
+ to `join_recursive`:
295
+
269
296
  ```ruby
270
297
  Category.join_recursive(union_type: :distinct) do |query|
271
298
  query.connect_by(id: :parent_id)
@@ -291,7 +318,7 @@ Category.join_recursive do |query|
291
318
  end
292
319
  ```
293
320
 
294
- would generate following SQL (if PostgreSQL used):
321
+ Would generate following SQL:
295
322
 
296
323
  ```sql
297
324
  SELECT "categories".*
@@ -324,14 +351,20 @@ FROM "categories" INNER JOIN (
324
351
  ORDER BY "categories__recursive"."__order_column" ASC
325
352
  ```
326
353
 
327
- If you want to use a `LEFT OUTER JOIN` instead of an `INNER JOIN`, add a query option for `outer_join_hierarchical`. This option allows the query to return non-hierarchical entries:
354
+ If you want to use a `LEFT OUTER JOIN` instead of an `INNER JOIN`,
355
+ add a query option for `outer_join_hierarchical`. This
356
+ option allows the query to return non-hierarchical entries:
357
+
328
358
  ```ruby
329
359
  .join_recursive(outer_join_hierarchical: true)
330
360
  ```
331
361
 
332
- If, when joining the recursive view to the main table, you want to change the foreign_key on the recursive view from the primary key of the main table to another column:
362
+ If, when joining the recursive view to the main table, you want
363
+ to change the foreign_key on the recursive view from the primary
364
+ key of the main table to another column:
365
+
333
366
  ```ruby
334
- .join_recursive(foreign_key: another_column)
367
+ .join_recursive(foreign_key: another_column)
335
368
  ```
336
369
 
337
370
  ## Related resources
@@ -343,8 +376,5 @@ If, when joining the recursive view to the main table, you want to change the fo
343
376
 
344
377
  ## Contributing
345
378
 
346
- 1. Fork it ( http://github.com/take-five/activerecord-hierarchical_query/fork )
347
- 2. Create your feature branch (`git checkout -b my-new-feature`)
348
- 3. Commit your changes (`git commit -am 'Add some feature'`)
349
- 4. Push to the branch (`git push origin my-new-feature`)
350
- 5. Create new Pull Request
379
+ Read through the short
380
+ [contributing guide](https://github.com/take-five/activerecord-hierarchical_query/blob/master/CONTRIBUTING.md).
@@ -14,8 +14,14 @@ module ActiveRecord
14
14
  @builder = builder
15
15
  end
16
16
 
17
- def bind_values
18
- scope.bound_attributes
17
+ if ActiveRecord.version < Gem::Version.new("5.2")
18
+ def bind_values
19
+ scope.bound_attributes
20
+ end
21
+ else
22
+ def bind_values
23
+ scope.values || {}
24
+ end
19
25
  end
20
26
 
21
27
  def arel
@@ -12,8 +12,14 @@ module ActiveRecord
12
12
  @builder = builder
13
13
  end
14
14
 
15
- def bind_values
16
- scope.bound_attributes
15
+ if ActiveRecord.version < Gem::Version.new("5.2")
16
+ def bind_values
17
+ scope.bound_attributes
18
+ end
19
+ else
20
+ def bind_values
21
+ scope.values || {}
22
+ end
17
23
  end
18
24
 
19
25
  def arel
@@ -11,8 +11,16 @@ module ActiveRecord
11
11
  @union_type = options.fetch(:union_type, :all)
12
12
  end
13
13
 
14
- def bind_values
15
- non_recursive_term.bind_values + recursive_term.bind_values
14
+ if ActiveRecord.version < Gem::Version.new("5.2")
15
+ def bind_values
16
+ non_recursive_term.bind_values + recursive_term.bind_values
17
+ end
18
+ else
19
+ def bind_values
20
+ non_recursive_term
21
+ .bind_values
22
+ .merge recursive_term.bind_values
23
+ end
16
24
  end
17
25
 
18
26
  def arel
@@ -23,13 +23,21 @@ module ActiveRecord
23
23
 
24
24
  relation = relation.joins(joined_arel_node)
25
25
 
26
- # copy bound variables from inner subquery
26
+ return relation unless ActiveRecord.version < Gem::Version.new("5.2")
27
+
27
28
  relation.bind_values += bind_values
28
29
 
29
30
  relation
30
31
  end
31
32
 
32
33
  private
34
+
35
+ if ActiveRecord.version < Gem::Version.new("5.2")
36
+ def bind_values
37
+ @builder.bind_values
38
+ end
39
+ end
40
+
33
41
  def joined_arel_node
34
42
  @options[:outer_join_hierarchical] == true ? outer_join : inner_join
35
43
  end
@@ -70,10 +78,6 @@ module ActiveRecord
70
78
  custom_foreign_key ? @alias[custom_foreign_key] : @alias[@query.klass.primary_key]
71
79
  end
72
80
 
73
- def bind_values
74
- @builder.bind_values
75
- end
76
-
77
81
  def ordered?
78
82
  @query.orderings.any?
79
83
  end
@@ -84,12 +88,29 @@ module ActiveRecord
84
88
 
85
89
  # This node is required to support joins to aliased Arel nodes
86
90
  class SubqueryAlias < Arel::Nodes::As
91
+
87
92
  attr_reader :table_name
88
93
 
94
+ unless method_defined? :name
95
+ alias_method :name, :table_name
96
+ end
97
+
89
98
  def initialize(subquery, alias_node)
90
99
  super
91
- @table_name = alias_node.name
100
+
101
+ @table_name = alias_node.try :name
102
+
103
+ return unless alias_node.respond_to? :left
104
+
105
+ aliased_name = alias_node.left.relation.name
106
+ return if @table_name == aliased_name
107
+
108
+ # Defensive coding; this shouldn't happen unless the
109
+ # Rails team does a change to how Arel works.
110
+ message = "Unexpected alias name mismatch"
111
+ raise RuntimeError, message
92
112
  end
113
+
93
114
  end
94
115
  end
95
116
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module HierarchicalQuery
3
- VERSION = '1.0.1'
3
+ VERSION = '1.1.0'
4
4
  end
5
5
  end
@@ -59,6 +59,18 @@ describe ActiveRecord::HierarchicalQuery do
59
59
  end
60
60
  ).to include root, child_1, child_2, child_3, child_4, child_5
61
61
  end
62
+
63
+ it 'handles Arel::Nodes::As name delegation' do
64
+ expected_array = \
65
+ klass.join_recursive do
66
+ connect_by { |parent, child| parent[:id].eq child[:parent_id] }
67
+ end
68
+
69
+ expect(
70
+ child_5.alias_node_query
71
+ ).to match_array expected_array
72
+ end
73
+
62
74
  end
63
75
 
64
76
  describe 'START WITH clause' do
@@ -2,9 +2,14 @@
2
2
  require 'pathname'
3
3
  require 'logger'
4
4
 
5
+ begin
6
+ require 'pry'
7
+ rescue LoadError
8
+ end
9
+
5
10
  ENV['TZ'] = 'UTC'
6
11
 
7
- SPEC_ROOT = Pathname.new(File.dirname(__FILE__))
12
+ SPEC_ROOT = Pathname.new(File.dirname(__FILE__)) unless defined? SPEC_ROOT
8
13
 
9
14
  require 'bundler'
10
15
  Bundler.setup(:default, ENV['TRAVIS'] ? :travis : :local)
@@ -15,25 +20,35 @@ require 'active_record'
15
20
 
16
21
  ActiveRecord::Base.configurations = YAML.load(SPEC_ROOT.join('database.yml').read)
17
22
  ActiveRecord::Base.establish_connection(:pg)
23
+
18
24
  ActiveRecord::Base.logger = Logger.new(ENV['DEBUG'] ? $stderr : '/dev/null')
19
25
  ActiveRecord::Base.logger.formatter = proc do |severity, datetime, progname, msg|
20
26
  "#{datetime.strftime('%H:%M:%S.%L')}: #{msg}\n"
21
27
  end
22
28
 
23
- load SPEC_ROOT.join('schema.rb')
29
+ begin
30
+ load SPEC_ROOT.join('schema.rb')
31
+ rescue ActiveRecord::NoDatabaseError
32
+ bold = "\033[1m"
33
+ red = "\033[31m"
34
+ reset = "\033[0m"
35
+
36
+ puts ""
37
+ puts bold + red + "Database missing." + reset
38
+ puts "If you have #{bold}sudo#{reset}, run the below " +
39
+ "(ignore any role creation errors)."
40
+ puts ""
41
+ puts bold + "rake db:create" + reset
42
+ puts ""
43
+ exit
44
+ end
45
+
24
46
  require SPEC_ROOT.join('support', 'models').to_s
25
47
 
26
48
  DatabaseCleaner.strategy = :transaction
27
49
 
28
- # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
29
50
  RSpec.configure do |config|
30
- config.run_all_when_everything_filtered = true
31
- config.filter_run :focus
32
51
 
33
- # Run specs in random order to surface order dependencies. If you find an
34
- # order dependency and want to debug it, you can fix the order by providing
35
- # the seed, which is printed after each run.
36
- # --seed 1234
37
52
  config.order = 'random'
38
53
 
39
54
  config.around(:each) do |example|
@@ -37,6 +37,16 @@ class Category < ActiveRecord::Base
37
37
  def ancestors
38
38
  parent ? parent.ancestors + [parent] : []
39
39
  end
40
+
41
+ # Arel::Nodes::As delegates name to the left relation
42
+ def alias_node_query
43
+ Category.left_outer_joins(:articles)
44
+ .join_recursive do |query|
45
+ query
46
+ .connect_by(id: :parent_id)
47
+ end
48
+ end
49
+
40
50
  end
41
51
 
42
52
  class Article < ActiveRecord::Base
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-hierarchical_query
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexei Mikhailov
8
+ - Zach Aysan
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2017-06-21 00:00:00.000000000 Z
12
+ date: 2018-09-22 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: activerecord
@@ -16,93 +17,114 @@ dependencies:
16
17
  requirements:
17
18
  - - ">="
18
19
  - !ruby/object:Gem::Version
19
- version: 3.1.0
20
+ version: '5.0'
20
21
  - - "<"
21
22
  - !ruby/object:Gem::Version
22
- version: '5.2'
23
+ version: '5.3'
23
24
  type: :runtime
24
25
  prerelease: false
25
26
  version_requirements: !ruby/object:Gem::Requirement
26
27
  requirements:
27
28
  - - ">="
28
29
  - !ruby/object:Gem::Version
29
- version: 3.1.0
30
+ version: '5.0'
30
31
  - - "<"
31
32
  - !ruby/object:Gem::Version
32
- version: '5.2'
33
+ version: '5.3'
34
+ - !ruby/object:Gem::Dependency
35
+ name: pg
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0.21'
41
+ - - "<"
42
+ - !ruby/object:Gem::Version
43
+ version: '1.2'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0.21'
51
+ - - "<"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.2'
33
54
  - !ruby/object:Gem::Dependency
34
55
  name: bundler
35
56
  requirement: !ruby/object:Gem::Requirement
36
57
  requirements:
37
58
  - - "~>"
38
59
  - !ruby/object:Gem::Version
39
- version: '1.5'
60
+ version: '1.16'
40
61
  type: :development
41
62
  prerelease: false
42
63
  version_requirements: !ruby/object:Gem::Requirement
43
64
  requirements:
44
65
  - - "~>"
45
66
  - !ruby/object:Gem::Version
46
- version: '1.5'
67
+ version: '1.16'
47
68
  - !ruby/object:Gem::Dependency
48
69
  name: rake
49
70
  requirement: !ruby/object:Gem::Requirement
50
71
  requirements:
51
72
  - - "~>"
52
73
  - !ruby/object:Gem::Version
53
- version: 10.4.2
74
+ version: '12.3'
54
75
  type: :development
55
76
  prerelease: false
56
77
  version_requirements: !ruby/object:Gem::Requirement
57
78
  requirements:
58
79
  - - "~>"
59
80
  - !ruby/object:Gem::Version
60
- version: 10.4.2
81
+ version: '12.3'
61
82
  - !ruby/object:Gem::Dependency
62
83
  name: rspec
63
84
  requirement: !ruby/object:Gem::Requirement
64
85
  requirements:
65
86
  - - "~>"
66
87
  - !ruby/object:Gem::Version
67
- version: 3.1.0
88
+ version: '3.8'
68
89
  type: :development
69
90
  prerelease: false
70
91
  version_requirements: !ruby/object:Gem::Requirement
71
92
  requirements:
72
93
  - - "~>"
73
94
  - !ruby/object:Gem::Version
74
- version: 3.1.0
95
+ version: '3.8'
75
96
  - !ruby/object:Gem::Dependency
76
97
  name: database_cleaner
77
98
  requirement: !ruby/object:Gem::Requirement
78
99
  requirements:
79
100
  - - "~>"
80
101
  - !ruby/object:Gem::Version
81
- version: 1.3.0
102
+ version: '1.7'
82
103
  type: :development
83
104
  prerelease: false
84
105
  version_requirements: !ruby/object:Gem::Requirement
85
106
  requirements:
86
107
  - - "~>"
87
108
  - !ruby/object:Gem::Version
88
- version: 1.3.0
109
+ version: '1.7'
89
110
  - !ruby/object:Gem::Dependency
90
111
  name: simplecov
91
112
  requirement: !ruby/object:Gem::Requirement
92
113
  requirements:
93
114
  - - "~>"
94
115
  - !ruby/object:Gem::Version
95
- version: 0.14.1
116
+ version: '0.16'
96
117
  type: :development
97
118
  prerelease: false
98
119
  version_requirements: !ruby/object:Gem::Requirement
99
120
  requirements:
100
121
  - - "~>"
101
122
  - !ruby/object:Gem::Version
102
- version: 0.14.1
123
+ version: '0.16'
103
124
  description:
104
125
  email:
105
126
  - amikhailov83@gmail.com
127
+ - zachaysan@gmail.com
106
128
  executables: []
107
129
  extensions: []
108
130
  extra_rdoc_files: []
@@ -148,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
170
  version: '0'
149
171
  requirements: []
150
172
  rubyforge_project:
151
- rubygems_version: 2.4.8
173
+ rubygems_version: 2.7.7
152
174
  signing_key:
153
175
  specification_version: 4
154
176
  summary: Recursively traverse trees using a single SQL query