acts_as_many_trees 0.0.3 → 0.0.5

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: fd3f337c1418d33c73887add48d5bec679c4dc93
4
- data.tar.gz: a6e945638bc2e2351b76aa196a55c3e42a29f00c
3
+ metadata.gz: 2968bbb9d336c54e6d67fbcfa2693a7340dd73bf
4
+ data.tar.gz: b97258ba8b2bd548d842345d6f5640ee3aa8c412
5
5
  SHA512:
6
- metadata.gz: 9ea378e205fa97476047c31a321d1af3a5ba268ce2152e1cb7e5de7e98db79461ede251b36503aac1d5cef484081eeb7f02b6108ec0d94ebde1e7b3aca5d588f
7
- data.tar.gz: 0df35bdfc480744e0f89d2f48f666c86415e171946ff6e00ee07d18eb06518c4a0bf2565579bbac7e88028402953ad61f5c49d5fba64ee4f327eedbf1c933f3c
6
+ metadata.gz: 793df7daa449ca0ac8bfd911710314749ae1b7c6ee3b2b3a1f5331ed4ad76b6db2f92edd672175abc8099329afe1db1eba36dab4c0c87e3081bbf3d06c9b5fac
7
+ data.tar.gz: b0bff842b040b01c21d9d8019f9524b9e148caf0e6a58177f06ccb8e8932a5a4f2dca19c7a29f7ef66e58261f005ec0e8b8d4f7f295dbbaf732e68e9d747f1f7
@@ -26,7 +26,7 @@ module ActsAsManyTrees
26
26
  included do
27
27
  has_many :unscoped_descendant_links,
28
28
  ->{order(:position)},
29
- class_name:hierarchy_class.to_s,
29
+ class_name: hierarchy_class.to_s,
30
30
  foreign_key: 'ancestor_id',
31
31
  dependent: :delete_all,
32
32
  inverse_of: :unscoped_ancestor
@@ -39,24 +39,43 @@ module ActsAsManyTrees
39
39
  inverse_of: :unscoped_descendant
40
40
 
41
41
  has_many :unscoped_ancestors,through: :unscoped_ancestor_links
42
- has_many :unscoped_descendants, through: :unscoped_descendant_links
43
- scope :roots , ->(hierarchy=''){
44
- on = Arel::Nodes::On.new(Arel::Nodes::Equality.new(arel_table[:id],hierarchy_class.arel_table[:descendant_id])
45
- .and(hierarchy_class.arel_table[:hierarchy_scope].eq(hierarchy))
46
- )
47
- outer_join = Arel::Nodes::OuterJoin.new(hierarchy_class.arel_table,on)
48
- joins(outer_join).merge(hierarchy_class.where(generation: 0))
49
- }
42
+ has_many :unscoped_descendants, {:through=>:unscoped_descendant_links, :source=>:unscoped_descendant}
43
+ has_many :self_and_siblings,
44
+ {:through=>:unscoped_ancestor_links,
45
+ :source=>:item_siblings
46
+ }
47
+ has_many :siblings_before,
48
+ ->{where('unscoped_ancestor_links_siblings_before_join.position > item_hierarchies.position').where('unscoped_ancestor_links_siblings_before_join.generation=1')},
49
+ {:through=>:unscoped_ancestor_links,
50
+ :source=>:item_siblings
51
+ }
52
+ has_many :siblings_after,
53
+ ->{where('unscoped_ancestor_links_siblings_after_join.position < item_hierarchies.position').where('unscoped_ancestor_links_siblings_after_join.generation=1')},
54
+ {:through=>:unscoped_ancestor_links,
55
+ :source=>:item_siblings
56
+ }
57
+
58
+ scope :roots , ->(hierarchy=''){
59
+ on = Arel::Nodes::On.new(Arel::Nodes::Equality.new(arel_table[:id],hierarchy_class.arel_table[:descendant_id])
60
+ .and(hierarchy_class.arel_table[:hierarchy_scope].eq(hierarchy))
61
+ .and(hierarchy_class.arel_table[:generation].not_eq(0))
62
+ )
63
+ outer_join = Arel::Nodes::OuterJoin.new(hierarchy_class.arel_table,on)
64
+ joins(outer_join).merge(hierarchy_class.where(ancestor_id: nil))
65
+ }
66
+ scope :not_this,->(this_id) { where.not(id: this_id)}
50
67
  end
51
68
  delegate :hierarchy_class, to: :class
52
69
  def parent=(inpt_parent)
53
70
  if inpt_parent.is_a?(Hash)
54
- new_parent=inpt_parent[:new_parent]
55
- after_node=inpt_parent[:after_node]
56
- before_node=inpt_parent[:before_node]
57
- hierarchy_scope=inpt_parent[:hierarchy_scope] || ''
71
+ new_parent=inpt_parent[:new_parent]
72
+ after_node=inpt_parent[:after_node]
73
+ before_node=inpt_parent[:before_node]
74
+ hierarchy_scope=inpt_parent[:hierarchy_scope] || ''
58
75
  else
59
76
  new_parent=inpt_parent
77
+ after_node=inpt_parent.children.last unless inpt_parent.nil?
78
+ before_node=inpt_parent.next_sibling unless inpt_parent.nil?
60
79
  hierarchy_scope = ''
61
80
  end
62
81
  hierarchy_class.set_parent_of(self,new_parent,hierarchy_scope,after_node,before_node)
@@ -78,23 +97,37 @@ module ActsAsManyTrees
78
97
  descendants(hierarchy_scope).where('generation=1')
79
98
  end
80
99
 
81
- def siblings(hierarchy_scope='')
82
- parent(hierarchy_scope).children(hierarchy_scope).where.not(id: id)
100
+ def self_and_ancestors(hierarchy='')
101
+ unscoped_ancestors.merge(hierarchy_class.scope_hierarchy(hierarchy))
83
102
  end
84
103
 
85
- def self_and_siblings(hierarchy_scope='')
86
- parent(hierarchy_scope).children(hierarchy_scope)
104
+ def ancestors(hierarchy='')
105
+ self_and_ancestors(hierarchy).not_this(self.id)
87
106
  end
88
107
 
108
+ def self_and_descendants(hierarchy='')
109
+ unscoped_descendants.merge(hierarchy_class.scope_hierarchy(hierarchy))
110
+ end
89
111
 
90
- def ancestors(hierarchy='')
91
- unscoped_ancestors.merge(hierarchy_class.scope_hierarchy(hierarchy))
112
+ def siblings
113
+ self_and_siblings.where.not(id: id)
114
+ end
115
+
116
+ def previous_sibling
117
+ siblings_before.last
118
+ end
119
+
120
+ def next_sibling
121
+ siblings_after.first
92
122
  end
93
123
 
94
124
  def descendants(hierarchy='')
95
- unscoped_descendants.merge(hierarchy_class.scope_hierarchy(hierarchy))
125
+ self_and_descendants(hierarchy).not_this(self.id)
96
126
  end
97
- end
98
127
 
128
+ def position(hierarchy='')
129
+ unscoped_ancestor_links.where(ancestor_id: id,hierarchy_scope: hierarchy).first.position
130
+ end
131
+ end
99
132
  end
100
133
  ActiveRecord::Base.send :include, ActsAsManyTrees::Base
@@ -4,6 +4,7 @@ module ActsAsManyTrees
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
+ UPPER_BOUND=10**20
7
8
  class_attribute :item_class_name
8
9
  self.item_class_name = self.to_s.gsub('Hierarchy','')
9
10
  class_attribute :item_class
@@ -19,6 +20,44 @@ module ActsAsManyTrees
19
20
  foreign_key: 'descendant_id',
20
21
  inverse_of: :unscoped_ancestor_links
21
22
 
23
+ has_many :self_and_ancestors,
24
+ ->(rec){where(hierarchy_scope: rec.hierarchy_scope)},
25
+ class_name: self.name,
26
+ foreign_key: 'descendant_id',
27
+ primary_key: 'ancestor_id'
28
+
29
+ has_many :ancestors,
30
+ ->(rec){where(hierarchy_scope: rec.hierarchy_scope).where.not(generation:0)},
31
+ class_name: self.name,
32
+ foreign_key: 'descendant_id',
33
+ primary_key: 'ancestor_id'
34
+
35
+ has_many :self_and_descendants,
36
+ ->(rec){where(hierarchy_scope: rec.hierarchy_scope).where.order(:position)},
37
+ class_name: self.name,
38
+ foreign_key: 'ancestor_id',
39
+ primary_key: 'descendant_id'
40
+
41
+ has_many :descendants,
42
+ ->(rec){where(hierarchy_scope: rec.hierarchy_scope).where.not(generation:0).order(:position)},
43
+ class_name: self.name,
44
+ foreign_key: 'ancestor_id',
45
+ primary_key: 'descendant_id'
46
+
47
+ has_many :siblings,
48
+ ->{where(generation: 1)},
49
+ class_name: self.name,
50
+ foreign_key: 'ancestor_id',
51
+ primary_key: 'ancestor_id'
52
+
53
+ has_many :children,
54
+ ->(rec){where(hierarchy_scope: rec.hierarchy_scope,generation: 1).order(:position)},
55
+ class_name: self.name,
56
+ foreign_key: 'ancestor_id',
57
+ primary_key: 'descendant_id'
58
+
59
+ has_many :item_siblings,{through: :siblings, source: :unscoped_descendant}
60
+
22
61
  scope :scope_hierarchy,->(scope_hierarchy=''){ where hierarchy_scope: scope_hierarchy}
23
62
  # select t1.* from item_trees t1 left outer join item_trees t2 on t1.ancestor_id = t2.descendant_id and t1.tree_scope = t2.tree_scope where t2.ancestor_id is null
24
63
  scope :roots,->do
@@ -32,30 +71,122 @@ module ActsAsManyTrees
32
71
  )
33
72
  .where(t2[:ancestor_id].eq(nil)
34
73
  )
35
-
74
+ end
75
+ scope :self_and_siblings, ->(item,hierarchy_scope='')do
76
+ joins(:siblings)
77
+ end
78
+ scope :siblings_before_this,->(rec) do
79
+ joins(:siblings).where(:position,lt(rec.position))
36
80
  end
37
81
 
38
82
  def self.set_parent_of(item,new_parent,hierarchy_scope='',after_node=nil,before_node=nil)
39
- self.delete_ancestors(item,hierarchy_scope) if item
40
- self.fill_in_parent_for(new_parent,item,hierarchy_scope,after_node,before_node) if item || new_parent
41
- self.fill_in_ancestors_for(new_parent,item,hierarchy_scope) if item && new_parent
42
- self.delete_ancestors_of_item_children(item,hierarchy_scope) if item
43
- self.set_new_ancestors_of_item_children(item,hierarchy_scope) if item
83
+ if new_parent
84
+ wrk_parent = self.find_by(descendant_id:new_parent.id,ancestor_id:new_parent.id,generation: 0,hierarchy_scope: hierarchy_scope)
85
+ unless wrk_parent
86
+ position = (after_this(nil,nil,hierarchy_scope)+before_this(nil,hierarchy_scope))/2.0
87
+ wrk_parent=self.create(descendant_id:new_parent.id,ancestor_id:new_parent.id,generation: 0,hierarchy_scope: hierarchy_scope,position: position)
88
+ end
89
+ end
90
+ if item
91
+ after_position = after_this(wrk_parent,after_node,hierarchy_scope)
92
+ before_position = before_this(before_node,hierarchy_scope)
93
+ position = (after_position+before_position)/2.0
94
+ wrk_item = self.find_by(descendant_id:item.id,ancestor_id:item.id,generation: 0,hierarchy_scope: hierarchy_scope)
95
+ if wrk_item
96
+ wrk_item.position = position
97
+ else
98
+ wrk_item=self.create(descendant_id:item.id,ancestor_id:item.id,generation: 0,hierarchy_scope: hierarchy_scope,position: position)
99
+ end
100
+ temp_name = SecureRandom.hex
101
+ create_tree(wrk_item,wrk_parent,temp_name)
102
+ delete_item_ancestors(wrk_item)
103
+ delete_ancestors_of_item_children(wrk_item,hierarchy_scope)
104
+ reset_descendant_position(wrk_item,before_position,temp_name)
105
+ rename_tree(temp_name,hierarchy_scope)
106
+ end
44
107
  end
45
108
 
46
109
  private
110
+ # the new position is after the maximum of the after_node, the parent, the current maximum of all
111
+ def self.after_this(wrk_parent,after_node,hierarchy_scope)
112
+ if after_node
113
+ position = after_node.position(hierarchy_scope)
114
+ elsif wrk_parent
115
+ position = wrk_parent.position
116
+ else
117
+ position = self.where(hierarchy_scope: hierarchy_scope).maximum(:position) || 0
118
+ end
119
+ position
120
+ end
121
+
122
+ # and before the minimum of the before_node, the parent's next sibling or 10**20
123
+ def self.before_this(before_node,hierarchy_scope)
124
+ if before_node
125
+ position = before_node.position(hierarchy_scope)
126
+ else
127
+ position = UPPER_BOUND
128
+ end
129
+ position
130
+ end
131
+
132
+ def self.create_tree(wrk_item,wrk_parent,temp_name)
133
+ if wrk_parent
134
+ sql=<<-SQL
135
+ insert into #{table_name}(ancestor_id,descendant_id,generation,hierarchy_scope,position)
136
+ select a.ancestor_id,b.descendant_id,a.generation+b.generation+1,'#{temp_name}',b.position
137
+ from #{table_name} a, #{table_name} b
138
+ where a.descendant_id=#{wrk_parent.descendant_id}
139
+ and b.ancestor_id=#{wrk_item.ancestor_id}
140
+ and a.hierarchy_scope = b.hierarchy_scope
141
+ and a.hierarchy_scope = '#{wrk_item.hierarchy_scope}'
142
+ union
143
+ select c.ancestor_id,c.descendant_id,c.generation,'#{temp_name}',c.position
144
+ from #{table_name} c
145
+ where c.ancestor_id = #{wrk_item.descendant_id}
146
+ and c.hierarchy_scope = '#{wrk_item.hierarchy_scope}'
147
+ SQL
148
+ else
149
+ sql=<<-SQL
150
+ insert into #{table_name}(ancestor_id,descendant_id,generation,hierarchy_scope,position)
151
+ select c.ancestor_id,c.descendant_id,c.generation,'#{temp_name}',c.position
152
+ from #{table_name} c
153
+ where c.ancestor_id = #{wrk_item.descendant_id}
154
+ and c.hierarchy_scope = '#{wrk_item.hierarchy_scope}'
155
+ SQL
156
+ end
157
+ connection.execute(sql)
158
+ end
159
+
160
+ def self.delete_item_ancestors(wrk_item)
161
+ sql=<<-SQL
162
+ delete from #{table_name}
163
+ where hierarchy_scope='#{wrk_item.hierarchy_scope}'
164
+ and descendant_id=#{wrk_item.descendant_id}
165
+ SQL
166
+ connection.execute(sql)
167
+ end
168
+
169
+ def self.rename_tree(old_name,new_name)
170
+ sql=<<-SQL
171
+ update #{table_name}
172
+ set hierarchy_scope='#{new_name}'
173
+ where hierarchy_scope='#{old_name}'
174
+ SQL
175
+ connection.execute(sql)
176
+ end
177
+
47
178
  def self.delete_ancestors(item,hierarchy_scope)
48
- delete_all(descendant_id: item.id,hierarchy_scope: hierarchy_scope )
179
+ delete.where(descendant_id: item.id,hierarchy_scope: hierarchy_scope ).where.not(generation: 0)
49
180
  end
50
181
 
51
182
  def self.delete_ancestors_of_item_children(item,hierarchy_scope)
52
183
  sql = <<-SQL
53
184
  delete from #{table_name} as p using #{table_name} as p1
54
185
  where p.descendant_id = p1.descendant_id
55
- and p1.ancestor_id = #{item.id}
56
- and p.generation > p1.generation
186
+ and p1.ancestor_id = #{item.descendant_id}
57
187
  and p.hierarchy_scope = p1.hierarchy_scope
58
188
  and p1.hierarchy_scope = '#{hierarchy_scope}'
189
+ and p.generation > 0
59
190
  SQL
60
191
  connection.execute(sql)
61
192
  end
@@ -74,65 +205,46 @@ module ActsAsManyTrees
74
205
  connection.execute(sql)
75
206
  end
76
207
 
77
- def self.fill_in_parent_for(new_parent,item,hierarchy_scope='',after_node=nil,before_node=nil)
78
- if new_parent
79
- p_rec = find_by(descendant_id: new_parent.id,hierarchy_scope: hierarchy_scope)
80
- unless p_rec
81
- p_rec=create!(ancestor_id: new_parent.id,descendant_id: new_parent.id,hierarchy_scope: hierarchy_scope,generation:0,position:Random.rand(1000000))
82
- end
83
- # p "p_rec.position = #{p_rec.position}"
84
- a_rec = nil
85
- if after_node
86
- a_rec = after_node
87
- a_rec_h = find_by(ancestor_id: new_parent.id, descendant_id:a_rec.id,hierarchy_scope: hierarchy_scope)
88
- a_rec_pos = a_rec_h.position
89
- elsif new_parent.children.last
90
- a_rec = new_parent.children.last
91
- a_rec_h = find_by(ancestor_id: new_parent.id, descendant_id:a_rec.id,hierarchy_scope: hierarchy_scope)
92
- a_rec_pos = a_rec_h.position
93
- else
94
- a_rec_pos = p_rec.position
95
- end
96
-
97
- if before_node
98
- b_rec = find_by(descendant_id: before_node.id,hierarchy_scope: hierarchy_scope,generation: 1)
99
- if b_rec
100
- b_position = b_rec.position
101
- end
102
- end
103
- if b_position && !after_node
104
- # p "b_position #{b_position} parent position #{p_rec.position}"
105
- new_position = (Random.rand(10)*(b_position - p_rec.position)/11)+p_rec.position
106
- elsif b_position && after_node
107
- # p "b_position #{b_position} after position #{a_rec_pos}"
108
- new_position = (Random.rand(10)*(b_position - a_rec_pos)/11)+a_rec_pos
109
- else
110
- new_position = a_rec_pos + Random.rand(1000000)
111
- end
112
- #create(ancestor_id: item.id,descendant_id: item.id,hierarchy_scope: hierarchy_scope,position:new_position,generation:0)
113
- if item
114
- # p "id = #{item.id} position=#{new_position}"
115
- create(ancestor_id: new_parent.id,descendant_id: item.id,generation: 1,hierarchy_scope: hierarchy_scope,position:new_position)
116
- end
117
- end
118
- end
119
-
120
- def self.fill_in_ancestors_for(new_parent,item,hierarchy_scope)
121
- if new_parent
122
- sql=<<-SQL
123
- insert into #{table_name}(ancestor_id,descendant_id,generation,hierarchy_scope,position)
124
- select it.ancestor_id,new_itm.descendant_id,it.generation+1,it.hierarchy_scope,new_itm.position
125
- from #{table_name} it
126
- join #{table_name} new_itm on it.descendant_id = new_itm.ancestor_id and it.hierarchy_scope=new_itm.hierarchy_scope
127
- where new_itm.ancestor_id=#{new_parent.id}
128
- and new_itm.descendant_id=#{item.id}
129
- and (it.ancestor_id <> it.descendant_id)
130
- and it.hierarchy_scope = '#{hierarchy_scope}'
131
- SQL
132
- ActiveRecord::Base.connection.execute(sql)
133
- end
208
+ def self.reset_descendant_position(parent,before_position,hierarchy_scope='')
209
+ after_position = parent.position
210
+ gap = before_position - after_position
211
+ # p "before position: #{before_position}, after_position: #{after_position} gap: #{gap}"
212
+ # sql = <<-SQL
213
+ # select ancestor_id,descendant_id,hierarchy_scope,(#{after_position} + (
214
+ # (CAST ((rank() over (partition by ancestor_id order by position)-1) AS numeric))
215
+ # /( CAST (count(*) over (partition by ancestor_id) AS numeric)) * #{gap})) as position
216
+ # from #{table_name}
217
+ # where ancestor_id=#{parent.descendant_id}
218
+ # and hierarchy_scope='#{hierarchy_scope}'
219
+ # SQL
220
+ # res = connection.execute(sql)
221
+ # res.each_row do |row|
222
+ # p row
223
+ # end
224
+ sql = <<-SQL
225
+ with new_position as (select ancestor_id,descendant_id,hierarchy_scope,(#{after_position} + (
226
+ (CAST ((rank() over (partition by ancestor_id order by position)-1) AS numeric))
227
+ /( CAST (count(*) over (partition by ancestor_id) AS numeric)) * #{gap})) as position
228
+ from #{table_name}
229
+ where ancestor_id=#{parent.descendant_id}
230
+ and hierarchy_scope='#{hierarchy_scope}'
231
+ )
232
+ update
233
+ #{table_name} as t
234
+ set position = new_position.position
235
+ from new_position
236
+ where t.descendant_id = new_position.descendant_id
237
+ and t.hierarchy_scope = new_position.hierarchy_scope
238
+ SQL
239
+ connection.execute(sql)
240
+ # sql=<<-SQL
241
+ # select * from #{table_name} where hierarchy_scope='#{hierarchy_scope}' order by position
242
+ # SQL
243
+ # res = connection.execute(sql)
244
+ # res.each_row do |row|
245
+ # p row
246
+ # end
134
247
  end
135
-
136
248
  end
137
249
  end
138
250
  end
@@ -1,3 +1,3 @@
1
1
  module ActsAsManyTrees
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.5"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_many_trees
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Small
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-11 00:00:00.000000000 Z
11
+ date: 2015-02-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -28,44 +28,44 @@ dependencies:
28
28
  name: pg
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 0.17.1
34
34
  type: :development
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: '0'
40
+ version: 0.17.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec-rails
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 3.1.0
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: 3.1.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: factory_girl_rails
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 4.5.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: 4.5.0
69
69
  description: Uses the closure tree pattern with a scope field to maintain separate
70
70
  hierarchies
71
71
  email: