forestify 1.0.1 → 1.0.2
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.
- data/README.md +27 -5
- data/lib/forestify.rb +144 -71
- metadata +2 -2
data/README.md
CHANGED
@@ -14,16 +14,31 @@ end
|
|
14
14
|
You can then do something like this :
|
15
15
|
|
16
16
|
```ruby
|
17
|
+
# This produces the following tree
|
18
|
+
# { left_position, name, right_position, level }
|
19
|
+
# { 0, Vehicle, 9, 0 } { 10, Animal, 11, 0}
|
20
|
+
# { 1, Car, 4, 1 } { 5, Plane, 6, 1 } { 7, Boat, 8, 1 }
|
21
|
+
# { 2, Audi, 3, 2}
|
22
|
+
|
17
23
|
vehicle = Tag.create!(name: "Vehicle")
|
24
|
+
animal = Tag.create!(name: "Animal")
|
18
25
|
car = Tag.create!(name: "Car", parent_id: vehicle.id)
|
26
|
+
plane = Tag.create!(name: "plane", parent_id: vehicle.id)
|
27
|
+
boat = Tag.create!(name: "Boat", parent_id: vehicle.id)
|
19
28
|
audi = Tag.create!(name: "Audi", parent_id: car.id)
|
20
29
|
|
30
|
+
[vehicle, animal, car, plane, boat, audi].each { |n| n.reload }
|
31
|
+
|
21
32
|
audi.parents
|
22
33
|
# => [vehicle, car]
|
23
|
-
car.
|
34
|
+
car.leaf?
|
24
35
|
# => false
|
25
|
-
car.
|
36
|
+
car.node?
|
37
|
+
# => true
|
38
|
+
vehicle.parent.nil?
|
26
39
|
# => true
|
40
|
+
car.siblings.all
|
41
|
+
# => [plane, boat]
|
27
42
|
```
|
28
43
|
|
29
44
|
# Installation
|
@@ -34,12 +49,19 @@ Although I will add generators later, you still need to manually add migrations
|
|
34
49
|
|
35
50
|
```ruby
|
36
51
|
change_table :tags do |t|
|
37
|
-
|
38
|
-
|
39
|
-
|
52
|
+
t.integer :left_position
|
53
|
+
t.integer :right_position
|
54
|
+
t.integer :level
|
40
55
|
end
|
41
56
|
```
|
42
57
|
|
58
|
+
# Updates
|
59
|
+
## 2012-02-06 version 1.0.1
|
60
|
+
* Cleaned up tests, added two methods: 'siblings' and 'parent'
|
61
|
+
|
62
|
+
## 2012-02-05 version 1.0.0
|
63
|
+
* First draft
|
64
|
+
|
43
65
|
# LICENSE
|
44
66
|
|
45
67
|
Copyright 2012 Gabriel Malkas. Released under MIT License. See LICENSE for details.
|
data/lib/forestify.rb
CHANGED
@@ -1,84 +1,157 @@
|
|
1
|
+
# == Forestify
|
2
|
+
#
|
3
|
+
# Provides a tree structure to Active Record models.
|
4
|
+
#
|
5
|
+
# New leaves are added to the right.
|
6
|
+
#
|
7
|
+
# For example, a Tag model could implement it like this :
|
8
|
+
#
|
9
|
+
# class Tag < ActiveRecord::Base
|
10
|
+
# forestify
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# We'll use the following example throughout this documentation :
|
14
|
+
#
|
15
|
+
# @vehicle = Tag.create!(name: "Vehicle")
|
16
|
+
# @animal = Tag.create!(name: "Animal")
|
17
|
+
# @car = Tag.create!(name: "Car", parent_id: @vehicle.id)
|
18
|
+
# @plane = Tag.create!(name: "plane", parent_id: @vehicle.id)
|
19
|
+
# @boat = Tag.create!(name: "Boat", parent_id: @vehicle.id)
|
20
|
+
# @audi = Tag.create!(name: "Audi", parent_id: @car.id)
|
21
|
+
#
|
22
|
+
# This code produces the following tree :
|
23
|
+
#
|
24
|
+
# { forestify_left_position, name, forestify_right_position, forestify_level }
|
25
|
+
# { 0, Vehicle, 9, 0 } { 10, Animal, 11, 0}
|
26
|
+
# { 1, Car, 4, 1 } { 5, Plane, 6, 1 } { 7, Boat, 8, 1 }
|
27
|
+
# { 2, Audi, 3, 2}
|
28
|
+
#
|
1
29
|
module Forestify
|
30
|
+
|
2
31
|
def forestify
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
32
|
+
unless included_modules.include? InstanceMethods
|
33
|
+
include InstanceMethods
|
34
|
+
end
|
35
|
+
|
36
|
+
before_create :initialize_position
|
37
|
+
before_destroy :update_positions_after_delete
|
38
|
+
|
39
|
+
private :initialize_position, :update_positions_after_delete
|
40
|
+
|
41
|
+
attr_accessor :parent_id
|
42
|
+
end
|
10
43
|
|
11
|
-
|
44
|
+
module InstanceMethods
|
12
45
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
46
|
+
# Initialize position fields
|
47
|
+
# Should be run only once
|
48
|
+
def initialize_position
|
49
|
+
# @parent = -1 is the option 'No parent'
|
50
|
+
if @parent_id.nil? || @parent_id == "-1"
|
51
|
+
# No parent has been specified, we need to add this leaf
|
52
|
+
# to the right side of the last root node.
|
53
|
+
last = self.class.order("forestify_right_position DESC").first
|
54
|
+
self.forestify_left_position = (last.nil?) ? 0 : last.forestify_right_position + 1
|
55
|
+
self.forestify_right_position = self.forestify_left_position + 1
|
56
|
+
self.forestify_level = 0
|
57
|
+
else
|
58
|
+
@parent_id = @parent_id.to_i
|
59
|
+
p = self.class.find(@parent_id)
|
60
|
+
self.forestify_left_position = p.forestify_right_position
|
61
|
+
self.forestify_right_position = self.forestify_left_position + 1
|
62
|
+
self.forestify_level = p.forestify_level + 1
|
63
|
+
# update nodes on the right hand side of parent
|
64
|
+
self.class.update_all "forestify_left_position = forestify_left_position + 2", ['forestify_left_position > ?', p.forestify_right_position]
|
65
|
+
self.class.update_all "forestify_right_position = forestify_right_position + 2", ['forestify_right_position > ?', p.forestify_right_position]
|
66
|
+
# update parent
|
67
|
+
p.update_attribute 'forestify_right_position', p.forestify_right_position + 2
|
68
|
+
end
|
69
|
+
end
|
38
70
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
71
|
+
def update_positions_after_delete
|
72
|
+
if node?
|
73
|
+
# Update nodes to the right
|
74
|
+
self.class.update_all "forestify_left_position = forestify_left_position - 2", ['forestify_left_position > ?', self.forestify_right_position]
|
75
|
+
self.class.update_all "forestify_right_position = forestify_right_position - 2", ['forestify_right_position > ?', self.forestify_right_position]
|
76
|
+
# Update children
|
77
|
+
self.class.update_all "forestify_level = forestify_level - 1", ['forestify_left_position > ? AND forestify_right_position < ?', self.forestify_left_position, self.forestify_right_position]
|
78
|
+
self.class.update_all "forestify_left_position = forestify_left_position - 1, forestify_right_position = forestify_right_position - 1", ['forestify_left_position > ? AND forestify_right_position < ?', self.forestify_left_position, self.forestify_right_position]
|
79
|
+
else
|
80
|
+
# Update nodes to the right
|
81
|
+
self.class.update_all "forestify_left_position = forestify_left_position - 2", ['forestify_left_position > ?', self.forestify_right_position]
|
82
|
+
self.class.update_all "forestify_right_position = forestify_right_position - 2", ['forestify_right_position > ?', self.forestify_right_position]
|
83
|
+
end
|
84
|
+
end
|
52
85
|
|
53
|
-
|
54
|
-
|
55
|
-
|
86
|
+
# Returns an ActiveRecord::Relation looking for ancestors.
|
87
|
+
#
|
88
|
+
# Example :
|
89
|
+
#
|
90
|
+
# @audi.parents.all # => [@vehicle, @car]
|
91
|
+
#
|
92
|
+
def parents
|
93
|
+
self.class.where('forestify_left_position < ?', self.forestify_left_position).where('forestify_right_position > ?', self.forestify_right_position)
|
94
|
+
end
|
56
95
|
|
57
|
-
|
58
|
-
|
59
|
-
|
96
|
+
# Returns the direct parent, or +nil+ if none exists.
|
97
|
+
#
|
98
|
+
# Example :
|
99
|
+
#
|
100
|
+
# @vehicle.parent # => nil
|
101
|
+
# @car.parent # => @vehicle
|
102
|
+
#
|
103
|
+
def parent
|
104
|
+
self.parents.where('forestify_level = ?', self.forestify_level - 1).first
|
105
|
+
end
|
60
106
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
107
|
+
# Returns an ActiveRecord::Relation looking for descendents.
|
108
|
+
#
|
109
|
+
# Example :
|
110
|
+
#
|
111
|
+
# @audi.children.all # => []
|
112
|
+
# @vehicle.children.all # => [@car, @plane, @boat, @audi]
|
113
|
+
#
|
114
|
+
def children
|
115
|
+
[] if leaf?
|
116
|
+
self.class.where('forestify_left_position > ?', self.forestify_left_position).where('forestify_right_position < ?', self.forestify_right_position)
|
117
|
+
end
|
65
118
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
119
|
+
# Returns an ActiveRecord::Relation looking for siblings.
|
120
|
+
#
|
121
|
+
# Example :
|
122
|
+
#
|
123
|
+
# @vehicle.siblings.all => # [@animal]
|
124
|
+
#
|
125
|
+
def siblings
|
126
|
+
if self.parent.nil?
|
127
|
+
self.class.where('forestify_level = 0').where('id != ?', self.id)
|
128
|
+
else
|
129
|
+
self.parent.children.where('forestify_level = ?', self.forestify_level).where('id != ?', self.id)
|
130
|
+
end
|
131
|
+
end
|
73
132
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
133
|
+
# Returns whether the instance is a node or not.
|
134
|
+
#
|
135
|
+
# Example :
|
136
|
+
#
|
137
|
+
# @car.node? # => true
|
138
|
+
# @animal.node? # => false
|
139
|
+
#
|
140
|
+
def node?
|
141
|
+
(self.forestify_right_position - self.forestify_left_position) > 1
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns whether the instance is a leaf or not.
|
145
|
+
#
|
146
|
+
# Example :
|
147
|
+
#
|
148
|
+
# @car.leaf? # => false
|
149
|
+
# @animal.leaf? # => true
|
150
|
+
#
|
151
|
+
def leaf?
|
152
|
+
!node?
|
153
|
+
end
|
154
|
+
end
|
82
155
|
end
|
83
156
|
|
84
157
|
ActiveRecord::Base.send :extend, Forestify
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forestify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-02-
|
12
|
+
date: 2012-02-07 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Forestify brings a tree data-structure to your Active Record models
|
15
15
|
email: gabriel.malkas@gmail.com
|