activerecord-recursive_tree_scopes 0.1.2 → 0.2.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: efa3ff153255f4cac37a076ac482b8d13ef8825d
4
- data.tar.gz: b5918f4bda320f432e0b51711423a815c9fa8a9b
3
+ metadata.gz: 8aa1c8117447b477a47776f01fe8588ffcbe695e
4
+ data.tar.gz: 037dcf0df1b722b581642b2a3fe6ea960ffd4505
5
5
  SHA512:
6
- metadata.gz: 82b635e671e577ddca6a369351f1d98fc013b027330472241498e546c9c33844e7cbec8c34b97ebadc742126190d2e4b5a69cf80dfcb02e3d3decf985687925d
7
- data.tar.gz: adb8ffcf9ff36ba16d98fc5b20edf9456c27f5da2b9b739a809db7d789c5d1daa7cd7083ac5cc3b9ee7c163c342b6403ccc0adce3a67208a4ae93f731714097b
6
+ metadata.gz: 8d929d9155abc09b234d1e994887ce1c4f5e61211229b65cd4e5ec94cd7e1c78ff6ba312426872134cde98aadd1e6ace5754ca301fce4d68051ce1132ff980f0
7
+ data.tar.gz: 374c820c032e89a7c5fecd1807397676935b3eb0c332139db7b7005609ed9d6a3abdd775024990973be1d5bc39567bf6c8e84cc88c05e54ba5054a50bca5d840
data/README.md CHANGED
@@ -61,6 +61,21 @@ ORDER BY employees.id
61
61
  ```
62
62
 
63
63
 
64
+ # Advanced Usage
65
+ Multiple key trees are supported. For example, a `Person` model may have
66
+ `mother_id` and `father_id` keys. That's no problem, just tell the scope about
67
+ both keys. `person.progenitors` will return all ancestors, mothers and fathers.
68
+ ```ruby
69
+ class Person < ActiveRecord::Base
70
+ belongs_to :mother, class_name: 'Person'
71
+ belongs_to :father, class_name: 'Person'
72
+
73
+ has_ancestors :progenitors, key: [ :mother_id, :father_id ]
74
+ has_descendants :progeny, key: [ :mother_id, :father_id ]
75
+ end
76
+ ```
77
+
78
+
64
79
  ## Friendly
65
80
 
66
81
  Go ahead, chain away:
@@ -96,7 +111,7 @@ ORDER BY employees.id
96
111
 
97
112
 
98
113
  ## Requirements
99
- * ActiveRecord >= 3.0.0
114
+ * ActiveRecord >= 3.1.0
100
115
  * PostgreSQL >= 8.4
101
116
 
102
117
 
@@ -105,9 +120,15 @@ ORDER BY employees.id
105
120
  Add `gem 'activerecord-recursive_tree_scopes'` to your Gemfile.
106
121
 
107
122
 
123
+ ## Alternatives
124
+
125
+ The [Edge](https://github.com/JackC/edge) gem is similar but makes better use
126
+ of Arel and relations.
127
+
128
+
108
129
  ## Thanks
109
130
 
110
- Thanks to [Joshua Davey](https://github.com/jgdavey) who's
131
+ Thanks to [Joshua Davey](https://github.com/jgdavey), his
111
132
  [blog post](http://hashrocket.com/blog/posts/recursive-sql-in-activerecord)
112
133
  inspired this gem.
113
134
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "activerecord-recursive_tree_scopes"
8
- s.version = "0.1.2"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["John Wulff"]
12
- s.date = "2013-10-01"
12
+ s.date = "2013-10-02"
13
13
  s.description = "Using an ActiveRecord scope, recursively query trees."
14
14
  s.email = "johnw@orcasnet.com"
15
15
  s.extra_rdoc_files = [
@@ -37,7 +37,8 @@ Gem::Specification.new do |s|
37
37
  "spec/database.yml.travis",
38
38
  "spec/spec_helper.rb",
39
39
  "spec/support/create_schema.rb",
40
- "spec/support/employee.rb"
40
+ "spec/support/employee.rb",
41
+ "spec/support/person.rb"
41
42
  ]
42
43
  s.homepage = "http://github.com/jwulff/activerecord-recursive_tree_scopes"
43
44
  s.licenses = ["MIT"]
@@ -2,7 +2,7 @@ module RecursiveTreeScopes
2
2
  module ModelMixin
3
3
  def has_ancestors(ancestors_name = :ancestors, options = {})
4
4
  options[:key] ||= :parent_id
5
-
5
+
6
6
  scope ancestors_name, lambda{ |record|
7
7
  RecursiveTreeScopes::Scopes.ancestors_for(record, options[:key])
8
8
  }
@@ -36,20 +36,21 @@ module RecursiveTreeScopes
36
36
  end
37
37
 
38
38
  def ancestors_sql_for(instance, key)
39
+ keys = [ key ].flatten
39
40
  tree_sql = <<-SQL
40
- WITH RECURSIVE ancestor_search(id, #{key}, path) AS (
41
- SELECT id, #{key}, ARRAY[id]
41
+ WITH RECURSIVE ancestor_search(id, #{keys.join ', '}, path) AS (
42
+ SELECT id, #{keys.join ', '}, ARRAY[id]
42
43
  FROM #{instance.class.table_name}
43
44
  WHERE id = #{instance.id}
44
45
  UNION ALL
45
- SELECT #{instance.class.table_name}.id, #{instance.class.table_name}.#{key}, path || #{instance.class.table_name}.id
46
+ SELECT #{instance.class.table_name}.id, #{keys.collect{|key| "#{instance.class.table_name}.#{key}" }.join ', '}, path || #{instance.class.table_name}.id
46
47
  FROM #{instance.class.table_name}, ancestor_search
47
- WHERE ancestor_search.#{key} = #{instance.class.table_name}.id
48
+ WHERE #{keys.collect{ |key| "ancestor_search.#{key} = #{instance.class.table_name}.id" }.join ' OR '}
48
49
  )
49
50
  SELECT id
50
51
  FROM ancestor_search
51
52
  WHERE id != #{instance.id}
52
- ORDER BY path
53
+ ORDER BY array_length(path, 1), path
53
54
  SQL
54
55
  tree_sql.gsub(/\s{2,}/, ' ')
55
56
  end
@@ -59,6 +60,7 @@ module RecursiveTreeScopes
59
60
  end
60
61
 
61
62
  def descendants_sql_for(instance, key)
63
+ keys = [ key ].flatten
62
64
  tree_sql = <<-SQL
63
65
  WITH RECURSIVE descendants_search(id, path) AS (
64
66
  SELECT id, ARRAY[id]
@@ -67,13 +69,14 @@ module RecursiveTreeScopes
67
69
  UNION ALL
68
70
  SELECT #{instance.class.table_name}.id, path || #{instance.class.table_name}.id
69
71
  FROM descendants_search
70
- JOIN #{instance.class.table_name} ON #{instance.class.table_name}.#{key} = descendants_search.id
72
+ JOIN #{instance.class.table_name}
73
+ ON #{keys.collect{ |key| "descendants_search.id = #{instance.class.table_name}.#{key}" }.join ' OR '}
71
74
  WHERE NOT #{instance.class.table_name}.id = ANY(path)
72
75
  )
73
76
  SELECT id
74
77
  FROM descendants_search
75
78
  WHERE id != #{instance.id}
76
- ORDER BY path
79
+ ORDER BY array_length(path, 1), path
77
80
  SQL
78
81
  tree_sql.gsub(/\s{2,}/, ' ')
79
82
  end
@@ -1,112 +1,212 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe "ActiverecordRecursiveTreeScopes" do
4
- let!(:alonso) { Employee.create! name: 'Alonso' }
5
- let!(:alfred) { Employee.create! name: 'Alfred' }
6
- let!(:barry) { Employee.create! name: 'Barry', manager: alfred }
7
- let!(:bob) { Employee.create! name: 'Bob', manager: alfred }
8
- let!(:charles) { Employee.create! name: 'Charles', manager: barry }
9
- let!(:cameron) { Employee.create! name: 'Cameron', manager: barry }
10
- let!(:carl) { Employee.create! name: 'Carl', manager: barry }
11
- let!(:dave) { Employee.create! name: 'Dave', manager: carl }
12
- let!(:daryl) { Employee.create! name: 'Daryl', manager: charles }
13
- let!(:dick) { Employee.create! name: 'Dick', manager: charles }
14
- let!(:edward) { Employee.create! name: 'Edward', manager: dick }
15
- let!(:frank) { Employee.create! name: 'Frank', manager: edward }
16
-
17
- describe 'Alonso' do
18
- subject { alonso }
19
- its(:manager) { should be_nil }
20
- its(:directly_managed) { should == [] }
21
- its(:managers) { should == [] }
22
- its(:managed) { should == [] }
23
- end
24
-
25
- describe 'Alfred' do
26
- subject { alfred }
27
- its(:manager) { should be_nil }
28
- its(:directly_managed) { should == [ barry, bob ] }
29
- its(:managers) { should == [] }
30
- its(:managed) { should == [ barry, bob, charles, cameron, carl, dave, daryl, dick, edward, frank ] }
4
+ describe 'simple tree' do
5
+ let!(:alonso) { Employee.create! name: 'Alonso' }
6
+ let!(:alfred) { Employee.create! name: 'Alfred' }
7
+ let!(:barry) { Employee.create! name: 'Barry', manager: alfred }
8
+ let!(:bob) { Employee.create! name: 'Bob', manager: alfred }
9
+ let!(:charles) { Employee.create! name: 'Charles', manager: barry }
10
+ let!(:cameron) { Employee.create! name: 'Cameron', manager: barry }
11
+ let!(:carl) { Employee.create! name: 'Carl', manager: barry }
12
+ let!(:dave) { Employee.create! name: 'Dave', manager: carl }
13
+ let!(:daryl) { Employee.create! name: 'Daryl', manager: charles }
14
+ let!(:dick) { Employee.create! name: 'Dick', manager: charles }
15
+ let!(:edward) { Employee.create! name: 'Edward', manager: dick }
16
+ let!(:frank) { Employee.create! name: 'Frank', manager: edward }
17
+
18
+ describe 'chaining' do
19
+ describe 'ancestors' do
20
+ it do
21
+ frank.managers.where(name: 'Barry').should == [ barry ]
22
+ end
23
+ end
24
+
25
+ describe 'descendants' do
26
+ it do
27
+ barry.managed.where("name LIKE ?", 'D%').should == [ dave, daryl, dick ]
28
+ end
29
+ end
30
+ end
31
+
32
+ describe 'Alonso' do
33
+ subject { alonso }
34
+ its(:manager) { should be_nil }
35
+ its(:directly_managed) { should == [] }
36
+ its(:managers) { should == [] }
37
+ its(:managed) { should == [] }
38
+ end
39
+
40
+ describe 'Alfred' do
41
+ subject { alfred }
42
+ its(:manager) { should be_nil }
43
+ its(:directly_managed) { should == [ barry, bob ] }
44
+ its(:managers) { should == [] }
45
+ its(:managed) { should == [ barry, bob, charles, cameron, carl, dave, daryl, dick, edward, frank ] }
46
+ end
47
+
48
+ describe 'Barry' do
49
+ subject { barry }
50
+ its(:manager) { should == alfred }
51
+ its(:directly_managed) { should == [ charles, cameron, carl ] }
52
+ its(:managers) { should == [ alfred ] }
53
+ its(:managed) { should == [ charles, cameron, carl, dave, daryl, dick, edward, frank ] }
54
+ end
55
+
56
+ describe 'Bob' do
57
+ subject { bob }
58
+ its(:manager) { should == alfred }
59
+ its(:directly_managed) { should == [] }
60
+ its(:managers) { should == [ alfred ] }
61
+ its(:managed) { should == [] }
62
+ end
63
+
64
+ describe 'Charles' do
65
+ subject { charles }
66
+ its(:manager) { should == barry }
67
+ its(:directly_managed) { should == [ daryl, dick ] }
68
+ its(:managers) { should == [ alfred, barry ] }
69
+ its(:managed) { should == [ daryl, dick, edward, frank ] }
70
+ end
71
+
72
+ describe 'Cameron' do
73
+ subject { cameron }
74
+ its(:manager) { should == barry }
75
+ its(:directly_managed) { should == [] }
76
+ its(:managers) { should == [ alfred, barry ] }
77
+ its(:managed) { should == [] }
78
+ end
79
+
80
+ describe 'Carl' do
81
+ subject { carl }
82
+ its(:manager) { should == barry }
83
+ its(:directly_managed) { should == [ dave ] }
84
+ its(:managers) { should == [ alfred, barry ] }
85
+ its(:managed) { should == [ dave ] }
86
+ end
87
+
88
+ describe 'Dave' do
89
+ subject { dave }
90
+ its(:manager) { should == carl }
91
+ its(:directly_managed) { should == [] }
92
+ its(:managers) { should == [ alfred, barry, carl ] }
93
+ its(:managed) { should == [] }
94
+ end
95
+
96
+ describe 'Daryl' do
97
+ subject { daryl }
98
+ its(:manager) { should == charles }
99
+ its(:directly_managed) { should == [] }
100
+ its(:managers) { should == [ alfred, barry, charles ] }
101
+ its(:managed) { should == [] }
102
+ end
103
+
104
+ describe 'Dick' do
105
+ subject { dick }
106
+ its(:manager) { should == charles }
107
+ its(:directly_managed) { should == [ edward ] }
108
+ its(:managers) { should == [ alfred, barry, charles ] }
109
+ its(:managed) { should == [ edward, frank ] }
110
+ end
111
+
112
+ describe 'Edward' do
113
+ subject { edward }
114
+ its(:manager) { should == dick }
115
+ its(:directly_managed) { should == [ frank ] }
116
+ its(:managers) { should == [ alfred, barry, charles, dick ] }
117
+ its(:managed) { should == [ frank ] }
118
+ end
119
+
120
+ describe 'Frank' do
121
+ subject { frank }
122
+ its(:manager) { should == edward }
123
+ its(:directly_managed) { should == [] }
124
+ its(:managers) { should == [ alfred, barry, charles, dick, edward ] }
125
+ its(:managed) { should == [] }
126
+ end
31
127
  end
32
128
 
33
- describe 'Barry' do
34
- subject { barry }
35
- its(:manager) { should == alfred }
36
- its(:directly_managed) { should == [ charles, cameron, carl ] }
37
- its(:managers) { should == [ alfred ] }
38
- its(:managed) { should == [ charles, cameron, carl, dave, daryl, dick, edward, frank ] }
39
- end
129
+ describe 'multiple key tree' do
130
+ let!(:george) { Person.create! name: 'George' }
131
+ let!(:hazel) { Person.create! name: 'Hazel' }
132
+ let!(:bill) { Person.create! name: 'Bill', father: george, mother: hazel }
133
+ let!(:william) { Person.create! name: 'William' }
134
+ let!(:sharlyn) { Person.create! name: 'Sharlyn' }
135
+ let!(:tamara) { Person.create! name: 'Tamara', father: william, mother: sharlyn }
136
+ let!(:john) { Person.create! name: 'John', father: bill, mother: tamara }
137
+ let!(:mark) { Person.create! name: 'Mark', father: bill, mother: tamara }
138
+ let!(:buster) { Person.create! name: 'Buster', father: john }
40
139
 
41
- describe 'Bob' do
42
- subject { bob }
43
- its(:manager) { should == alfred }
44
- its(:directly_managed) { should == [] }
45
- its(:managers) { should == [ alfred ] }
46
- its(:managed) { should == [] }
47
- end
140
+ describe 'George' do
141
+ subject { george }
142
+ its(:mother) { should be_nil }
143
+ its(:father) { should be_nil }
144
+ its(:progenitors) { should == [] }
145
+ its(:progeny) { should == [ bill, john, mark, buster ] }
146
+ end
48
147
 
49
- describe 'Charles' do
50
- subject { charles }
51
- its(:manager) { should == barry }
52
- its(:directly_managed) { should == [ daryl, dick ] }
53
- its(:managers) { should == [ alfred, barry ] }
54
- its(:managed) { should == [ daryl, dick, edward, frank ] }
55
- end
148
+ describe 'Hazel' do
149
+ subject { hazel }
150
+ its(:mother) { should be_nil }
151
+ its(:father) { should be_nil }
152
+ its(:progenitors) { should == [] }
153
+ its(:progeny) { should == [ bill, john, mark, buster ] }
154
+ end
56
155
 
57
- describe 'Cameron' do
58
- subject { cameron }
59
- its(:manager) { should == barry }
60
- its(:directly_managed) { should == [] }
61
- its(:managers) { should == [ alfred, barry ] }
62
- its(:managed) { should == [] }
63
- end
156
+ describe 'William' do
157
+ subject { william }
158
+ its(:mother) { should be_nil }
159
+ its(:father) { should be_nil }
160
+ its(:progenitors) { should == [] }
161
+ its(:progeny) { should == [ tamara, john, mark, buster ] }
162
+ end
64
163
 
65
- describe 'Carl' do
66
- subject { carl }
67
- its(:manager) { should == barry }
68
- its(:directly_managed) { should == [ dave ] }
69
- its(:managers) { should == [ alfred, barry ] }
70
- its(:managed) { should == [ dave ] }
71
- end
164
+ describe 'Sharlyn' do
165
+ subject { sharlyn }
166
+ its(:mother) { should be_nil }
167
+ its(:father) { should be_nil }
168
+ its(:progenitors) { should == [] }
169
+ its(:progeny) { should == [ tamara, john, mark, buster ] }
170
+ end
72
171
 
73
- describe 'Dave' do
74
- subject { dave }
75
- its(:manager) { should == carl }
76
- its(:directly_managed) { should == [] }
77
- its(:managers) { should == [ alfred, barry, carl ] }
78
- its(:managed) { should == [] }
79
- end
172
+ describe 'Bill' do
173
+ subject { bill }
174
+ its(:mother) { should == hazel }
175
+ its(:father) { should == george }
176
+ its(:progenitors) { should == [ george, hazel ] }
177
+ its(:progeny) { should == [ john, mark, buster ] }
178
+ end
80
179
 
81
- describe 'Daryl' do
82
- subject { daryl }
83
- its(:manager) { should == charles }
84
- its(:directly_managed) { should == [] }
85
- its(:managers) { should == [ alfred, barry, charles ] }
86
- its(:managed) { should == [] }
87
- end
180
+ describe 'Tamara' do
181
+ subject { tamara }
182
+ its(:mother) { should == sharlyn }
183
+ its(:father) { should == william }
184
+ its(:progenitors) { should == [ william, sharlyn ] }
185
+ its(:progeny) { should == [ john, mark, buster ] }
186
+ end
88
187
 
89
- describe 'Dick' do
90
- subject { dick }
91
- its(:manager) { should == charles }
92
- its(:directly_managed) { should == [ edward ] }
93
- its(:managers) { should == [ alfred, barry, charles ] }
94
- its(:managed) { should == [ edward, frank ] }
95
- end
188
+ describe 'John' do
189
+ subject { john }
190
+ its(:mother) { should == tamara }
191
+ its(:father) { should == bill }
192
+ its(:progenitors) { should == [ george, hazel, bill, william, sharlyn, tamara ] }
193
+ its(:progeny) { should == [ buster ] }
194
+ end
96
195
 
97
- describe 'Edward' do
98
- subject { edward }
99
- its(:manager) { should == dick }
100
- its(:directly_managed) { should == [ frank ] }
101
- its(:managers) { should == [ alfred, barry, charles, dick ] }
102
- its(:managed) { should == [ frank ] }
103
- end
196
+ describe 'Mark' do
197
+ subject { mark }
198
+ its(:mother) { should == tamara }
199
+ its(:father) { should == bill }
200
+ its(:progenitors) { should == [ george, hazel, bill, william, sharlyn, tamara ] }
201
+ its(:progeny) { should == [] }
202
+ end
104
203
 
105
- describe 'Frank' do
106
- subject { frank }
107
- its(:manager) { should == edward }
108
- its(:directly_managed) { should == [] }
109
- its(:managers) { should == [ alfred, barry, charles, dick, edward ] }
110
- its(:managed) { should == [] }
204
+ describe 'Buster' do
205
+ subject { buster }
206
+ its(:mother) { should be_nil }
207
+ its(:father) { should == john }
208
+ its(:progenitors) { should == [ george, hazel, bill, william, sharlyn, tamara, john ] }
209
+ its(:progeny) { should == [] }
210
+ end
111
211
  end
112
212
  end
@@ -6,9 +6,19 @@ class CreateSchema < ActiveRecord::Migration
6
6
  end
7
7
 
8
8
  execute "ALTER TABLE employees ADD CONSTRAINT employees_fk_manager_id FOREIGN KEY (manager_id) REFERENCES employees (id)"
9
+
10
+ create_table :people do |t|
11
+ t.string :name
12
+ t.integer :mother_id
13
+ t.integer :father_id
14
+ end
15
+
16
+ execute "ALTER TABLE people ADD CONSTRAINT people_fk_mother_id FOREIGN KEY (mother_id) REFERENCES people (id)"
17
+ execute "ALTER TABLE people ADD CONSTRAINT people_fk_father_id FOREIGN KEY (father_id) REFERENCES people (id)"
9
18
  end
10
19
 
11
20
  def down
12
21
  drop_table :employees
22
+ drop_table :people
13
23
  end
14
24
  end
@@ -0,0 +1,7 @@
1
+ class Person < ActiveRecord::Base
2
+ belongs_to :mother, class_name: 'Person'
3
+ belongs_to :father, class_name: 'Person'
4
+
5
+ has_ancestors :progenitors, key: [ :mother_id, :father_id ]
6
+ has_descendants :progeny, key: [ :mother_id, :father_id ]
7
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-recursive_tree_scopes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Wulff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-01 00:00:00.000000000 Z
11
+ date: 2013-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -109,6 +109,7 @@ files:
109
109
  - spec/spec_helper.rb
110
110
  - spec/support/create_schema.rb
111
111
  - spec/support/employee.rb
112
+ - spec/support/person.rb
112
113
  homepage: http://github.com/jwulff/activerecord-recursive_tree_scopes
113
114
  licenses:
114
115
  - MIT