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.
Files changed (3) hide show
  1. data/README.md +27 -5
  2. data/lib/forestify.rb +144 -71
  3. 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.is_leaf?
34
+ car.leaf?
24
35
  # => false
25
- car.is_node?
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
- t.integer :left_position
38
- t.integer :right_position
39
- t.integer :level
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
- unless included_modules.include? InstanceMethods
4
- include InstanceMethods
5
- end
6
- before_create :initialize_position
7
- before_destroy :update_positions_after_delete
8
- attr_accessor :parent_id
9
- end
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
- module InstanceMethods
44
+ module InstanceMethods
12
45
 
13
- # Initialize position fields
14
- # Should be run only once
15
- def initialize_position
16
- # @parent = -1 is the option 'No parent'
17
- if @parent_id.nil? || @parent_id == "-1"
18
- # No parent has been specified, we need to add this leaf
19
- # to the right side of the last root node.
20
- last = self.class.order("right_position DESC").first
21
- self.left_position = (last.nil?) ? 0 : last.right_position + 1
22
- self.right_position = self.left_position + 1
23
- self.level = 0
24
- else
25
- # Makes sure it's an integer
26
- @parent_id = @parent_id.to_i
27
- p = self.class.find(@parent_id)
28
- self.left_position = p.right_position
29
- self.right_position = self.left_position + 1
30
- self.level = p.level + 1
31
- # update nodes on the right hand side of parent
32
- self.class.update_all "left_position = left_position + 2", ['left_position > ?', p.right_position]
33
- self.class.update_all "right_position = right_position + 2", ['right_position > ?', p.right_position]
34
- # update parent
35
- p.update_attribute 'right_position', p.right_position + 2
36
- end
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
- def update_positions_after_delete
40
- if is_node?
41
- # Update nodes to the right
42
- self.class.update_all "left_position = left_position - 2", ['left_position > ?', self.right_position]
43
- self.class.update_all "right_position = right_position - 2", ['right_position > ?', self.right_position]
44
- # Update children
45
- self.class.update_all "level = level - 1", ['left_position > ? AND right_position < ?', self.left_position, self.right_position]
46
- self.class.update_all "left_position = left_position - 1, right_position = right_position - 1", ['left_position > ? AND right_position < ?', self.left_position, self.right_position]
47
- else
48
- self.class.update_all "left_position = left_position - 2", ['left_position > ?', self.right_position]
49
- self.class.update_all "right_position = right_position - 2", ['right_position > ?', self.right_position]
50
- end
51
- end
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
- def parents
54
- self.class.where('left_position < ?', self.left_position).where('right_position > ?', self.right_position)
55
- end
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
- def parent
58
- self.parents.where('level = ?', self.level - 1).first
59
- end
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
- def children
62
- [] if is_leaf?
63
- self.class.where('left_position > ?', self.left_position).where('right_position < ?', self.right_position)
64
- end
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
- def siblings
67
- if self.parent.nil?
68
- self.class.where('level = 0').where('id != ?', self.id)
69
- else
70
- self.parent.children.where('level = ?', self.level).where('id != ?', self.id)
71
- end
72
- end
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
- def is_node?
75
- (self.right_position - self.left_position) > 1
76
- end
77
-
78
- def is_leaf?
79
- !is_node?
80
- end
81
- end
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.1
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-06 00:00:00.000000000 Z
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