forestify 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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