activerecord-recursive_tree_scopes 0.1.2 → 0.2.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
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