acts_as_many_trees 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +45 -0
- data/lib/acts_as_many_trees/base.rb +75 -0
- data/lib/acts_as_many_trees/hierarchy_table.rb +91 -0
- data/lib/acts_as_many_trees/version.rb +3 -0
- data/lib/acts_as_many_trees.rb +11 -0
- data/lib/generators/acts_as_many_trees/migration_generator.rb +30 -0
- data/lib/generators/acts_as_many_trees/templates/create_hierarchies_table.rb.erb +21 -0
- data/lib/tasks/acts_as_many_trees_tasks.rake +4 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2e3aaaa29d8492c978bc86b0b61b9cb50eb29c14
|
4
|
+
data.tar.gz: 360dfdbedf00f63acb234ebb692104dee7940c83
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ebf6c139330b618a017e6254ea52f033e769fff0c49326081ccc3562ca38b6fa5f07a3fa15caedb2bbbcc6c4870140a256d1ccc53ba7db5cd2113bc1aa7ab874
|
7
|
+
data.tar.gz: 643a394d45595339c8e48e8a645f96a997773fc3d104293d366bd06c10ba4728ccd789d6a10bc391dab447e2e790407e708c3f44ce1fc51c1107b2a7e7475703
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'ActsAsManyTrees'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
Bundler::GemHelper.install_tasks
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'rspec/core/rake_task'
|
20
|
+
|
21
|
+
RSpec::Core::RakeTask.new(:spec)
|
22
|
+
|
23
|
+
if defined?(RSpec)
|
24
|
+
desc 'Run factory specs.'
|
25
|
+
RSpec::Core::RakeTask.new(:factory_specs) do |t|
|
26
|
+
t.pattern = './spec/factories_spec.rb'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# task spec: :factory_specs
|
31
|
+
task :default => :spec
|
32
|
+
#http://erniemiller.org/
|
33
|
+
desc 'run the console'
|
34
|
+
task :console do
|
35
|
+
require 'irb'
|
36
|
+
require 'irb/completion'
|
37
|
+
require 'lib/acts_as_many_trees'
|
38
|
+
ARGV.clear
|
39
|
+
IRB.start
|
40
|
+
end
|
41
|
+
rescue LoadError
|
42
|
+
# no rspec available
|
43
|
+
end
|
44
|
+
|
45
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ActsAsManyTrees
|
2
|
+
module Base
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def acts_as_many_trees(options = {})
|
10
|
+
class_attribute :hierarchy_class
|
11
|
+
self.hierarchy_class = (name+'Hierarchy').constantize
|
12
|
+
include ActsAsManyTrees::InstanceMethods
|
13
|
+
extend ActsAsManyTrees::ClassMethods
|
14
|
+
hierarchy_class.send :include,ActsAsManyTrees::HierarchyTable
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
module ClassMethods
|
20
|
+
def hierarchy_table_name
|
21
|
+
hierarchy_class.table_name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
module InstanceMethods
|
25
|
+
extend ActiveSupport::Concern
|
26
|
+
included do
|
27
|
+
has_many :unscoped_descendant_links,
|
28
|
+
class_name:hierarchy_class.to_s,
|
29
|
+
foreign_key: 'ancestor_id',
|
30
|
+
dependent: :delete_all,
|
31
|
+
inverse_of: :unscoped_ancestor
|
32
|
+
|
33
|
+
has_many :unscoped_ancestor_links,
|
34
|
+
class_name: hierarchy_class.to_s,
|
35
|
+
foreign_key: 'descendant_id',
|
36
|
+
dependent: :delete_all,
|
37
|
+
inverse_of: :unscoped_descendant
|
38
|
+
has_many :unscoped_ancestors,through: :unscoped_ancestor_links
|
39
|
+
has_many :unscoped_descendants, through: :unscoped_descendant_links
|
40
|
+
scope :roots , ->(hierarchy=''){
|
41
|
+
on = Arel::Nodes::On.new(Arel::Nodes::Equality.new(arel_table[:id],hierarchy_class.arel_table[:descendant_id])
|
42
|
+
.and(hierarchy_class.arel_table[:hierarchy_scope].eq(hierarchy))
|
43
|
+
)
|
44
|
+
outer_join = Arel::Nodes::OuterJoin.new(hierarchy_class.arel_table,on)
|
45
|
+
joins(outer_join).merge(hierarchy_class.where(ancestor_id: nil))
|
46
|
+
}
|
47
|
+
end
|
48
|
+
delegate :hierarchy_class, to: :class
|
49
|
+
def parent=(new_parent,hierarchy_scope='')
|
50
|
+
hierarchy_class.set_parent_of(self,new_parent,hierarchy_scope)
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_parent(new_parent,hierarchy_scope='')
|
54
|
+
hierarchy_class.set_parent_of(self,new_parent,hierarchy_scope)
|
55
|
+
end
|
56
|
+
|
57
|
+
def parent(hierarchy_scope='')
|
58
|
+
ancestors(hierarchy_scope).where('generation=1').first
|
59
|
+
end
|
60
|
+
|
61
|
+
def children(hierarchy_scope='')
|
62
|
+
descendants(hierarchy_scope).where('generation=1')
|
63
|
+
end
|
64
|
+
|
65
|
+
def ancestors(hierarchy='')
|
66
|
+
unscoped_ancestors.merge(hierarchy_class.scope_hierarchy(hierarchy))
|
67
|
+
end
|
68
|
+
|
69
|
+
def descendants(hierarchy='')
|
70
|
+
unscoped_descendants.merge(hierarchy_class.scope_hierarchy(hierarchy))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
ActiveRecord::Base.send :include, ActsAsManyTrees::Base
|
@@ -0,0 +1,91 @@
|
|
1
|
+
|
2
|
+
module ActsAsManyTrees
|
3
|
+
module HierarchyTable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
class_attribute :item_class_name
|
8
|
+
self.item_class_name = self.to_s.gsub('Hierarchy','')
|
9
|
+
class_attribute :item_class
|
10
|
+
self.item_class = item_class_name.constantize
|
11
|
+
|
12
|
+
belongs_to :unscoped_ancestor,
|
13
|
+
class_name: item_class_name,
|
14
|
+
foreign_key: 'ancestor_id',
|
15
|
+
inverse_of: :unscoped_descendant_links
|
16
|
+
|
17
|
+
belongs_to :unscoped_descendant,
|
18
|
+
class_name: item_class_name,
|
19
|
+
foreign_key: 'descendant_id',
|
20
|
+
inverse_of: :unscoped_ancestor_links
|
21
|
+
|
22
|
+
scope :scope_hierarchy,->(scope_hierarchy=''){ where hierarchy_scope: scope_hierarchy}
|
23
|
+
# 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
|
+
scope :roots,->do
|
25
|
+
t1 = arel_table
|
26
|
+
t2 = arel_table.alias
|
27
|
+
t1.project(Arel::star).join(t2,Arel::Nodes::OuterJoin)
|
28
|
+
.on(t1[:ancestor_id]
|
29
|
+
.eq(t2[:descendant_id])
|
30
|
+
.and(t1[:hierarchy_scope].eq(t2[:hierarchy_scope])
|
31
|
+
)
|
32
|
+
)
|
33
|
+
.where(t2[:ancestor_id].eq(nil)
|
34
|
+
)
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.set_parent_of(item,new_parent,hierarchy_scope='')
|
39
|
+
self.delete_ancestors(item,hierarchy_scope)
|
40
|
+
self.fill_in_ancestors_for(new_parent,item,hierarchy_scope)
|
41
|
+
self.delete_ancestors_of_item_children(item,hierarchy_scope)
|
42
|
+
self.set_new_ancestors_of_item_children(item,hierarchy_scope)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def self.delete_ancestors(item,hierarchy_scope)
|
47
|
+
delete_all(descendant_id: item.id,hierarchy_scope: hierarchy_scope)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.delete_ancestors_of_item_children(item,hierarchy_scope)
|
51
|
+
sql = <<-SQL
|
52
|
+
delete from #{table_name} as p using #{table_name} as p1
|
53
|
+
where p.descendant_id = p1.descendant_id
|
54
|
+
and p1.ancestor_id = #{item.id}
|
55
|
+
and p.generation > p1.generation
|
56
|
+
and p.hierarchy_scope = p1.hierarchy_scope
|
57
|
+
and p1.hierarchy_scope = '#{hierarchy_scope}'
|
58
|
+
SQL
|
59
|
+
connection.execute(sql)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.set_new_ancestors_of_item_children(item,hierarchy_scope)
|
63
|
+
sql=<<-SQL
|
64
|
+
insert into #{table_name}(ancestor_id,descendant_id,generation,hierarchy_scope)
|
65
|
+
select it.ancestor_id,ct.descendant_id,it.generation+ct.generation,it.hierarchy_scope
|
66
|
+
from #{table_name} it
|
67
|
+
join #{table_name} ct
|
68
|
+
on ct.ancestor_id = it.descendant_id
|
69
|
+
and ct.hierarchy_scope = it.hierarchy_scope
|
70
|
+
where it.descendant_id=#{item.id}
|
71
|
+
and it.hierarchy_scope = '#{hierarchy_scope}'
|
72
|
+
SQL
|
73
|
+
connection.execute(sql)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.fill_in_ancestors_for(new_parent,item,hierarchy_scope)
|
77
|
+
if new_parent
|
78
|
+
create(ancestor_id: new_parent.id,descendant_id: item.id,generation: 1,hierarchy_scope: hierarchy_scope)
|
79
|
+
sql=<<-SQL
|
80
|
+
insert into #{table_name}(ancestor_id,descendant_id,generation,hierarchy_scope)
|
81
|
+
select it.ancestor_id,#{item.id},it.generation+1,it.hierarchy_scope
|
82
|
+
from #{table_name} it
|
83
|
+
where it.descendant_id=#{new_parent.id}
|
84
|
+
and it.hierarchy_scope = '#{hierarchy_scope}'
|
85
|
+
SQL
|
86
|
+
ActiveRecord::Base.connection.execute(sql)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rails/generators/named_base'
|
2
|
+
require 'rails/generators/active_record/migration'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module ActsAsManyTrees
|
6
|
+
module Generators # :nodoc:
|
7
|
+
class MigrationGenerator < ::Rails::Generators::NamedBase # :nodoc:
|
8
|
+
include ActiveRecord::Generators::Migration
|
9
|
+
|
10
|
+
extend Forwardable
|
11
|
+
def_delegators :hierarchy_table_name
|
12
|
+
|
13
|
+
def self.default_generator_root
|
14
|
+
File.dirname(__FILE__)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_migration_file
|
18
|
+
migration_template 'create_hierarchies_table.rb.erb', "db/migrate/create_#{hierarchy_table_name}.rb"
|
19
|
+
end
|
20
|
+
|
21
|
+
def migration_class_name
|
22
|
+
"Create#{hierarchy_table_name.camelize}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def hierarchy_table_name
|
26
|
+
(class_name+'Hierarchy').tableize
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :<%= hierarchy_table_name %>, id: false do |t|
|
4
|
+
t.integer :ancestor_id, null: false
|
5
|
+
t.integer :descendant_id, null: false
|
6
|
+
t.integer :generation, null: false
|
7
|
+
t.string :hierarchy_scope,null: false
|
8
|
+
t.decimal :position
|
9
|
+
end
|
10
|
+
|
11
|
+
add_index :<%= hierarchy_table_name %>, [:ancestor_id, :descendant_id,:hierarchy_scope],
|
12
|
+
unique: true,
|
13
|
+
name:'<%="#{hierarchy_table_name}_anc_desc_scope_idx" %>'
|
14
|
+
|
15
|
+
add_index :<%= hierarchy_table_name -%>, [:descendant_id,:hierarchy_scope],
|
16
|
+
name: '<%="#{hierarchy_table_name}_desc_scope_idx" %>'
|
17
|
+
|
18
|
+
add_index :<%= hierarchy_table_name -%>, [:ancestor_id,:hierarchy_scope,:position],
|
19
|
+
name: '<%="#{hierarchy_table_name}_anc_scope_pos_idx" %>'
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acts_as_many_trees
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Small
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.1.7
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.1.7
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: factory_girl_rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Uses the closure tree pattern with a scope field to maintain separate
|
70
|
+
hierarchies
|
71
|
+
email:
|
72
|
+
- jds340+rubygems@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- MIT-LICENSE
|
78
|
+
- README.rdoc
|
79
|
+
- Rakefile
|
80
|
+
- lib/acts_as_many_trees.rb
|
81
|
+
- lib/acts_as_many_trees/base.rb
|
82
|
+
- lib/acts_as_many_trees/hierarchy_table.rb
|
83
|
+
- lib/acts_as_many_trees/version.rb
|
84
|
+
- lib/generators/acts_as_many_trees/migration_generator.rb
|
85
|
+
- lib/generators/acts_as_many_trees/templates/create_hierarchies_table.rb.erb
|
86
|
+
- lib/tasks/acts_as_many_trees_tasks.rake
|
87
|
+
homepage: ''
|
88
|
+
licenses:
|
89
|
+
- MIT
|
90
|
+
metadata: {}
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 2.2.2
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: ActiveRecord acts as tree, with many trees
|
111
|
+
test_files: []
|