activerecord-hierarchical_query 1.0.1 → 1.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
- 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