acts_as_many_trees 0.0.3 → 0.0.5

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: 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: