acts_as_nested_interval 0.2.0 → 0.3.0.pre
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 +4 -4
- data/README.md +11 -4
- data/lib/acts_as_nested_interval.rb +3 -30
- data/lib/acts_as_nested_interval/associations.rb +41 -0
- data/lib/acts_as_nested_interval/callbacks.rb +18 -8
- data/lib/acts_as_nested_interval/configuration.rb +9 -2
- data/lib/acts_as_nested_interval/core_ext/rational.rb +7 -0
- data/lib/acts_as_nested_interval/instance_methods.rb +49 -106
- data/lib/acts_as_nested_interval/version.rb +1 -1
- data/test/acts_as_nested_interval_test.rb +64 -160
- metadata +35 -87
- data/test/dummy/README.rdoc +0 -28
- data/test/dummy/Rakefile +0 -6
- data/test/dummy/app/assets/javascripts/application.js +0 -13
- data/test/dummy/app/assets/stylesheets/application.css +0 -15
- data/test/dummy/app/controllers/application_controller.rb +0 -5
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/models/region.rb +0 -5
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/bin/bundle +0 -3
- data/test/dummy/bin/rails +0 -4
- data/test/dummy/bin/rake +0 -4
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -23
- data/test/dummy/config/boot.rb +0 -5
- data/test/dummy/config/database.yml +0 -31
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -37
- data/test/dummy/config/environments/production.rb +0 -83
- data/test/dummy/config/environments/test.rb +0 -39
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/cookies_serializer.rb +0 -3
- data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
- data/test/dummy/config/initializers/inflections.rb +0 -16
- data/test/dummy/config/initializers/mime_types.rb +0 -4
- data/test/dummy/config/initializers/session_store.rb +0 -3
- data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/test/dummy/config/locales/en.yml +0 -23
- data/test/dummy/config/routes.rb +0 -56
- data/test/dummy/config/secrets.yml +0 -22
- data/test/dummy/db/migrate/20120302143528_create_regions.rb +0 -15
- data/test/dummy/db/migrate/20121004204252_change_interval_precision.rb +0 -6
- data/test/dummy/db/schema.rb +0 -31
- data/test/dummy/log/development.log +0 -140
- data/test/dummy/log/test.log +0 -7048
- data/test/dummy/public/404.html +0 -67
- data/test/dummy/public/422.html +0 -67
- data/test/dummy/public/500.html +0 -66
- data/test/dummy/public/favicon.ico +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 727dd5393b81d22ca456a5ac3f2661287f909a42
|
4
|
+
data.tar.gz: ff0dc310818337c838c1ac91c9f6a9e7054bc282
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fadcf0336a54a3d6deb0ae23d5a11c96c1e229a39653dca8568a1f9bf0b174057e7cb422751517409a1635857690d1a89925d959986896eeb68fcdb57b2fe39b
|
7
|
+
data.tar.gz: b33657f0b7cc2458a00827bb7b0b7fd24d39e8217bec60253acbbce2ff2487d572e0fe0669ee091d30bf7c09460bae0c7e6385b52b005af4a96d3016781707c2
|
data/README.md
CHANGED
@@ -17,8 +17,10 @@ If your database supports recursive queryes (`WITH RECURSIVE`) or specific custo
|
|
17
17
|
## Install
|
18
18
|
|
19
19
|
```ruby
|
20
|
-
# add to Gemfile
|
21
|
-
gem 'acts_as_nested_interval'
|
20
|
+
# add to Gemfile when you use Ruby > 2.0 or Rails >= 4.0
|
21
|
+
gem 'acts_as_nested_interval', '~> 0.2.0'
|
22
|
+
# Orherwise
|
23
|
+
gem 'acts_as_nested_interval', '~> 0.1.1' # This version could have less features than actual version, check legacy branch.
|
22
24
|
```
|
23
25
|
|
24
26
|
```sh
|
@@ -49,6 +51,7 @@ add_index :regions, :lftp
|
|
49
51
|
add_index :regions, :lftq
|
50
52
|
add_index :regions, :lft
|
51
53
|
add_index :regions, :rgt
|
54
|
+
add_index :regions, [:lftp, :lftq, :rgtq, :rgtp], unique: true
|
52
55
|
```
|
53
56
|
|
54
57
|
## Usage
|
@@ -59,8 +62,11 @@ point data types in the database.
|
|
59
62
|
This act provides these named scopes:
|
60
63
|
|
61
64
|
```ruby
|
62
|
-
Region.roots
|
63
|
-
Region.preorder
|
65
|
+
Region.roots # returns roots of tree.
|
66
|
+
Region.preorder # returns records for preorder traversal.
|
67
|
+
Region.ancestors_of(node) # returns all ancestors of given node
|
68
|
+
Region.descendants_of(node) # returns all descendants of given node
|
69
|
+
Region.siblings_of(node) # returns all siblings of given node
|
64
70
|
```
|
65
71
|
|
66
72
|
This act provides these instance methods:
|
@@ -70,6 +76,7 @@ Region.parent # returns parent of record.
|
|
70
76
|
Region.children # returns children of record.
|
71
77
|
Region.ancestors # returns scoped ancestors of record.
|
72
78
|
Region.descendants # returns scoped descendants of record.
|
79
|
+
Region.siblings # returns scoped siblings of record.
|
73
80
|
Region.depth # returns depth of record.
|
74
81
|
```
|
75
82
|
|
@@ -5,12 +5,14 @@
|
|
5
5
|
# https://github.com/clyfe
|
6
6
|
|
7
7
|
require 'acts_as_nested_interval/core_ext/integer'
|
8
|
+
require 'acts_as_nested_interval/core_ext/rational'
|
8
9
|
require 'acts_as_nested_interval/version'
|
9
10
|
require 'acts_as_nested_interval/constants'
|
10
11
|
require 'acts_as_nested_interval/configuration'
|
11
12
|
require 'acts_as_nested_interval/callbacks'
|
12
13
|
require 'acts_as_nested_interval/instance_methods'
|
13
14
|
require 'acts_as_nested_interval/class_methods'
|
15
|
+
require 'acts_as_nested_interval/associations'
|
14
16
|
|
15
17
|
# This act implements a nested-interval tree. You can find all descendants
|
16
18
|
# or all ancestors with just one select query. You can insert and delete
|
@@ -28,46 +30,17 @@ module ActsAsNestedInterval
|
|
28
30
|
# * <tt>:dependent</tt> -- dependency between the parent node and children nodes (default :restrict)
|
29
31
|
def acts_as_nested_interval(options = {})
|
30
32
|
# Refactored
|
31
|
-
# TODO: table_exists?
|
32
33
|
cattr_accessor :nested_interval
|
33
34
|
|
34
35
|
self.nested_interval = Configuration.new( self, **options )
|
35
36
|
|
36
|
-
if nested_interval.fraction_cache?
|
37
|
-
scope :preorder, -> { order(rgt: :desc, lftp: :asc) }
|
38
|
-
else
|
39
|
-
scope :preorder, -> { order('1.0 * rgtp / rgtq DESC, lftp ASC') }
|
40
|
-
end
|
41
|
-
# When?
|
42
|
-
#scope :preorder, -> { order('nested_interval_rgt(lftp, lftq) DESC, lftp ASC') }
|
43
|
-
|
44
|
-
|
45
|
-
# OLD CODE
|
46
|
-
#cattr_accessor :nested_interval_foreign_key
|
47
|
-
#cattr_accessor :nested_interval_scope_columns
|
48
|
-
cattr_accessor :nested_interval_lft_index
|
49
|
-
#cattr_accessor :nested_interval_dependent
|
50
|
-
|
51
|
-
cattr_accessor :virtual_root
|
52
|
-
self.virtual_root = !!options[:virtual_root]
|
53
|
-
|
54
|
-
#self.nested_interval_foreign_key = options[:foreign_key] || :parent_id
|
55
|
-
#self.nested_interval_scope_columns = Array(options[:scope_columns])
|
56
|
-
self.nested_interval_lft_index = options[:lft_index]
|
57
|
-
#self.nested_interval_dependent = options[:dependent] || :restrict_with_exception
|
58
|
-
|
59
|
-
belongs_to :parent, class_name: name, foreign_key: nested_interval.foreign_key
|
60
|
-
has_many :children, class_name: name, foreign_key: nested_interval.foreign_key,
|
61
|
-
dependent: nested_interval.dependent
|
62
|
-
scope :roots, -> { where(nested_interval.foreign_key => nil) }
|
63
|
-
|
64
37
|
if self.table_exists? # Fix problem with migrating without table
|
65
38
|
include ActsAsNestedInterval::InstanceMethods
|
66
39
|
include ActsAsNestedInterval::Callbacks
|
40
|
+
include ActsAsNestedInterval::Associations
|
67
41
|
extend ActsAsNestedInterval::ClassMethods
|
68
42
|
end
|
69
43
|
end
|
70
44
|
end
|
71
45
|
end
|
72
46
|
|
73
|
-
#ActiveRecord::Base.send :extend, ActsAsNestedInterval
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ActsAsNestedInterval
|
2
|
+
module Associations
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
belongs_to :parent, class_name: name, foreign_key: nested_interval.foreign_key
|
7
|
+
has_many :children, class_name: name, foreign_key: nested_interval.foreign_key,
|
8
|
+
dependent: nested_interval.dependent
|
9
|
+
scope :roots, -> { where(nested_interval.foreign_key => nil) }
|
10
|
+
|
11
|
+
scope :ancestors_of, ->(node){ where("rgt >= CAST(:rgt AS FLOAT) AND lft < CAST(:lft AS FLOAT)", rgt: node.rgt, lft: node.lft) }
|
12
|
+
scope :subtree_of, ->(node){ where( "lft BETWEEN :lft AND :rgt", rgt: node.rgt, lft: node.lft ) } # Simple version
|
13
|
+
scope :descendants_of, ->(node){ subtree_of(node).where.not(id: node.id) }
|
14
|
+
scope :siblings_of, ->(node){ fkey = nested_interval.foreign_key; where( fkey => node.send(fkey) ).where.not(id: node.id) }
|
15
|
+
|
16
|
+
if nested_interval.fraction_cache?
|
17
|
+
scope :preorder, -> { order(rgt: :desc, lftp: :asc) }
|
18
|
+
else
|
19
|
+
scope :preorder, -> { order('1.0 * rgtp / rgtq DESC, lftp ASC') }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def ancestor_of?(node)
|
24
|
+
left < node.left && right >= node.right
|
25
|
+
end
|
26
|
+
|
27
|
+
def ancestors
|
28
|
+
nested_interval_scope.ancestors_of(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def descendants
|
32
|
+
nested_interval_scope.descendants_of(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def siblings
|
36
|
+
nested_interval_scope.siblings_of(self)
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -17,26 +17,36 @@ module ActsAsNestedInterval
|
|
17
17
|
|
18
18
|
# Creates record.
|
19
19
|
def create_nested_interval
|
20
|
-
|
21
|
-
set_nested_interval_for_top
|
22
|
-
else
|
23
|
-
set_nested_interval *parent.lock!.next_child_lft
|
24
|
-
end
|
20
|
+
set_nested_interval(parent.present? ? parent.next_child_lft : next_root_lft )
|
25
21
|
end
|
26
22
|
|
27
23
|
# Updates record, updating descendants if parent association updated,
|
28
24
|
# in which case caller should first acquire table lock.
|
29
25
|
def update_nested_interval
|
30
|
-
|
31
|
-
|
26
|
+
return if moving?
|
27
|
+
unless node_moved?
|
32
28
|
db_self = self.class.find(id).lock!
|
33
29
|
write_attribute(nested_interval.foreign_key, db_self.read_attribute(nested_interval.foreign_key))
|
34
|
-
set_nested_interval db_self.lftp, db_self.lftq
|
30
|
+
set_nested_interval Rational(db_self.lftp, db_self.lftq)
|
35
31
|
else
|
36
32
|
# No locking in this case -- caller should have acquired table lock.
|
37
33
|
update_nested_interval_move
|
38
34
|
end
|
39
35
|
end
|
36
|
+
|
37
|
+
def move!
|
38
|
+
return if moving?
|
39
|
+
self.class.nested_interval.moving = true
|
40
|
+
transaction do
|
41
|
+
yield
|
42
|
+
end
|
43
|
+
ensure
|
44
|
+
self.class.nested_interval.moving = false
|
45
|
+
end
|
46
|
+
|
47
|
+
def moving?
|
48
|
+
!!self.class.nested_interval.moving
|
49
|
+
end
|
40
50
|
|
41
51
|
end
|
42
52
|
end
|
@@ -2,13 +2,16 @@ module ActsAsNestedInterval
|
|
2
2
|
class Configuration
|
3
3
|
|
4
4
|
attr_reader :foreign_key, :dependent, :scope_columns
|
5
|
+
attr_accessor :moving
|
5
6
|
|
6
7
|
# multiple_roots - allow more than one root
|
7
|
-
def initialize( model, virtual_root: false, foreign_key: :parent_id, dependent: :restrict_with_exception, scope_columns: [] )
|
8
|
+
def initialize( model, virtual_root: false, foreign_key: :parent_id, dependent: :restrict_with_exception, scope_columns: [], moveable: true)
|
8
9
|
@multiple_roots = !!virtual_root
|
9
|
-
@foreign_key = foreign_key
|
10
|
+
@foreign_key = foreign_key.to_sym
|
10
11
|
@dependent = dependent
|
11
12
|
@scope_columns = *scope_columns
|
13
|
+
@moveable = moveable && !model.readonly_attributes.include?(foreign_key) # Fix issue #9
|
14
|
+
@moving = false
|
12
15
|
|
13
16
|
check_model_columns( model )
|
14
17
|
end
|
@@ -21,6 +24,10 @@ module ActsAsNestedInterval
|
|
21
24
|
@fraction_cache
|
22
25
|
end
|
23
26
|
|
27
|
+
def moveable?
|
28
|
+
@moveable
|
29
|
+
end
|
30
|
+
|
24
31
|
private
|
25
32
|
|
26
33
|
def check_model_columns( model )
|
@@ -1,50 +1,23 @@
|
|
1
|
+
using Mediant
|
2
|
+
|
1
3
|
module ActsAsNestedInterval
|
2
4
|
module InstanceMethods
|
5
|
+
|
3
6
|
extend ActiveSupport::Concern
|
4
7
|
|
5
8
|
# selectively define #descendants according to table features
|
6
9
|
included do
|
7
|
-
|
8
|
-
if nested_interval.fraction_cache?
|
9
|
-
|
10
|
-
def descendants
|
11
|
-
nested_interval_scope.where( "lftp > :lftp AND lft BETWEEN :lft AND :rgt", lftp: lftp, rgt: rgt, lft: lft )
|
12
|
-
end
|
13
|
-
|
14
|
-
else
|
15
|
-
|
16
|
-
def descendants
|
17
|
-
quoted_table_name = self.class.quoted_table_name
|
18
|
-
nested_interval_scope.where <<-SQL
|
19
|
-
( #{quoted_table_name}.lftp != #{rgtp} OR
|
20
|
-
#{quoted_table_name}.lftq != #{rgtq}
|
21
|
-
) AND
|
22
|
-
#{quoted_table_name}.lftp BETWEEN
|
23
|
-
1 + #{quoted_table_name}.lftq * CAST(#{lftp} AS BIGINT) / #{lftq} AND
|
24
|
-
#{quoted_table_name}.lftq * CAST(#{rgtp} AS BIGINT) / #{rgtq}
|
25
|
-
SQL
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
|
10
|
+
validate :disallow_circular_dependency
|
30
11
|
end
|
31
12
|
|
32
|
-
def set_nested_interval(
|
33
|
-
self.lftp, self.lftq =
|
13
|
+
def set_nested_interval(rational)
|
14
|
+
self.lftp, self.lftq = rational.numerator, rational.denominator
|
34
15
|
self.rgtp = rgtp if has_attribute?(:rgtp)
|
35
16
|
self.rgtq = rgtq if has_attribute?(:rgtq)
|
36
17
|
self.lft = lft if has_attribute?(:lft)
|
37
18
|
self.rgt = rgt if has_attribute?(:rgt)
|
38
19
|
end
|
39
20
|
|
40
|
-
def set_nested_interval_for_top
|
41
|
-
if nested_interval.multiple_roots?
|
42
|
-
set_nested_interval(*next_root_lft)
|
43
|
-
else
|
44
|
-
set_nested_interval 0, 1
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
21
|
def nested_interval_scope
|
49
22
|
conditions = {}
|
50
23
|
nested_interval.scope_columns.each do |column_name|
|
@@ -53,80 +26,33 @@ module ActsAsNestedInterval
|
|
53
26
|
self.class.where conditions
|
54
27
|
end
|
55
28
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
errors.add nested_interval.foreign_key, "is descendant"
|
64
|
-
raise ActiveRecord::RecordInvalid, self
|
65
|
-
end
|
66
|
-
rescue ActiveRecord::RecordNotFound => e # root
|
29
|
+
def recalculate_nested_interval!
|
30
|
+
move! do
|
31
|
+
lftr = parent.present? ? parent.next_child_lft : next_root_lft
|
32
|
+
set_nested_interval( lftr )
|
33
|
+
save!
|
34
|
+
self.recalculate_nested_interval!
|
35
|
+
children.preorder.map(&:recalculate_nested_interval!)
|
67
36
|
end
|
68
|
-
|
69
|
-
if read_attribute(nested_interval.foreign_key).nil? # root move
|
70
|
-
set_nested_interval_for_top
|
71
|
-
else # child move
|
72
|
-
set_nested_interval *parent.next_child_lft
|
73
|
-
end
|
74
|
-
cpp = db_self.lftq * rgtp - db_self.rgtq * lftp
|
75
|
-
cpq = db_self.rgtp * lftp - db_self.lftp * rgtp
|
76
|
-
cqp = db_self.lftq * rgtq - db_self.rgtq * lftq
|
77
|
-
cqq = db_self.rgtp * lftq - db_self.lftp * rgtq
|
37
|
+
end
|
78
38
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
mysql = false #["MySQL", "Mysql2"].include?(connection.adapter_name)
|
83
|
-
var = ->(v) { mysql ? vars.add?(v) ? "(@#{v} := #{v})" : "@#{v}" : v }
|
84
|
-
multiply = ->(c, b) { "#{c} * #{var.(b)}" }
|
85
|
-
add = ->(a, b) { "#{a} + #{b}" }
|
86
|
-
one = sprintf("%#.30f", 1)
|
87
|
-
divide = ->(p, q) { "#{one} * (#{p}) / (#{q})" }
|
39
|
+
# Rewrite method
|
40
|
+
def update_nested_interval_move
|
41
|
+
return unless self.class.nested_interval.moveable?
|
88
42
|
|
89
|
-
if
|
90
|
-
|
91
|
-
|
92
|
-
updates[:rgt] = -> { divide.(updates[:rgtp].(), updates[:rgtq].()) } if has_attribute?(:rgt)
|
43
|
+
if parent.present? and self.ancestor_of?(parent)
|
44
|
+
errors.add nested_interval.foreign_key, "is descendant"
|
45
|
+
raise ActiveRecord::RecordInvalid, self
|
93
46
|
end
|
94
47
|
|
95
|
-
|
96
|
-
|
97
|
-
updates[:lft] = -> { divide.(updates[:lftp].(), updates[:lftq].()) } if has_attribute?(:lft)
|
98
|
-
|
99
|
-
sql = updates.map { |k, v| "#{k} = #{v.()}" }.join(', ')
|
100
|
-
|
101
|
-
db_self.descendants.update_all sql
|
48
|
+
# TODO: Do it by DB
|
49
|
+
self.recalculate_nested_interval!
|
102
50
|
end
|
103
51
|
|
104
|
-
def ancestor_of?(node)
|
105
|
-
node.lftp == lftp && node.lftq == lftq ||
|
106
|
-
node.lftp > node.lftq * lftp / lftq &&
|
107
|
-
node.lftp <= node.lftq * rgtp / rgtq &&
|
108
|
-
(node.lftp != rgtp || node.lftq != rgtq)
|
109
|
-
end
|
110
|
-
|
111
|
-
def ancestors
|
112
|
-
nested_interval_scope.where("rgt >= CAST(:rgt AS FLOAT) AND lft < CAST(:lft AS FLOAT)", rgt: rgt, lft: lft)
|
113
|
-
end
|
114
|
-
|
115
|
-
#def ancestors
|
116
|
-
#sqls = ['0 = 1']
|
117
|
-
#p, q = lftp, lftq
|
118
|
-
#while p != 0
|
119
|
-
#x = p.inverse(q)
|
120
|
-
#p, q = (x * p - 1) / q, x
|
121
|
-
#sqls << "lftq = #{q} AND lftp = #{p}"
|
122
|
-
#end
|
123
|
-
#nested_interval_scope.where(sqls * ' OR ')
|
124
|
-
#end
|
125
|
-
|
126
52
|
# Returns depth by counting ancestors up to 0 / 1.
|
127
53
|
def depth
|
128
54
|
if new_record?
|
129
|
-
if
|
55
|
+
if parent.nil?
|
130
56
|
return 0
|
131
57
|
else
|
132
58
|
return parent.depth + 1
|
@@ -167,22 +93,39 @@ module ActsAsNestedInterval
|
|
167
93
|
# Returns left end of interval for next child.
|
168
94
|
def next_child_lft
|
169
95
|
if child = children.order('lftq DESC').first
|
170
|
-
return
|
96
|
+
return left.mediant( child.left )
|
171
97
|
else
|
172
|
-
return
|
98
|
+
return left.mediant( right )
|
173
99
|
end
|
174
100
|
end
|
175
101
|
|
176
102
|
# Returns left end of interval for next root.
|
177
103
|
def next_root_lft
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
return vr.lftp + child.lftp, vr.lftq + child.lftq
|
182
|
-
else
|
183
|
-
return vr.lftp + vr.rgtp, vr.lftq + vr.rgtq
|
184
|
-
end
|
104
|
+
last_root = nested_interval_scope.roots.order( rgtp: :desc, rgtq: :desc ).first
|
105
|
+
raise Exception.new("Only one root allowed") if last_root.present? && !self.class.nested_interval.multiple_roots?
|
106
|
+
last_root.try(:right) || 0.to_r
|
185
107
|
end
|
186
108
|
|
109
|
+
# Check if node is moved (parent changed)
|
110
|
+
def node_moved?
|
111
|
+
send(:"#{nested_interval.foreign_key}_changed?") # TODO: Check if parent moved?
|
112
|
+
end
|
113
|
+
|
114
|
+
def left
|
115
|
+
Rational(lftp, lftq)
|
116
|
+
end
|
117
|
+
|
118
|
+
def right
|
119
|
+
Rational(rgtp, rgtq)
|
120
|
+
end
|
121
|
+
|
122
|
+
protected
|
123
|
+
|
124
|
+
def disallow_circular_dependency
|
125
|
+
if parent == self
|
126
|
+
errors.add(self.class.nested_interval.foreign_key, 'cannot refer back to self')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
187
130
|
end
|
188
131
|
end
|