acts_as_nested_interval 0.2.0.pre → 0.2.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/lib/acts_as_nested_interval/callbacks.rb +4 -4
- data/lib/acts_as_nested_interval/class_methods.rb +1 -1
- data/lib/acts_as_nested_interval/configuration.rb +35 -0
- data/lib/acts_as_nested_interval/constants.rb +8 -0
- data/lib/acts_as_nested_interval/instance_methods.rb +29 -31
- data/lib/acts_as_nested_interval/version.rb +1 -1
- data/lib/acts_as_nested_interval.rb +28 -18
- data/test/acts_as_nested_interval_test.rb +9 -5
- data/test/dummy/db/schema.rb +0 -3
- data/test/dummy/log/development.log +15 -0
- data/test/dummy/log/test.log +9265 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0b2b592385c77289fa506e90644ef852fba8e8f
|
4
|
+
data.tar.gz: a3c26bcb5485afb6922d413cb821137d2f8e9abd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8143a530d3281764572e7d7c48309225c3a7754298e23f36d61efd19bfe337fda61e49b56f8d96681786021b3124e9c899fb526fbe02783adbb87b1a76470a58
|
7
|
+
data.tar.gz: 5695037b46285612a58e79b1832d23d6632c861d1d5374516c75ec22bf1cc252df215e44c21b59f5176c468b48be222cf0380f2de22a5470372fb060a8d5f318
|
data/README.md
CHANGED
@@ -17,7 +17,7 @@ module ActsAsNestedInterval
|
|
17
17
|
|
18
18
|
# Creates record.
|
19
19
|
def create_nested_interval
|
20
|
-
if read_attribute(
|
20
|
+
if read_attribute(nested_interval.foreign_key).nil?
|
21
21
|
set_nested_interval_for_top
|
22
22
|
else
|
23
23
|
set_nested_interval *parent.lock!.next_child_lft
|
@@ -27,10 +27,10 @@ module ActsAsNestedInterval
|
|
27
27
|
# Updates record, updating descendants if parent association updated,
|
28
28
|
# in which case caller should first acquire table lock.
|
29
29
|
def update_nested_interval
|
30
|
-
changed = send(:"#{
|
30
|
+
changed = send(:"#{nested_interval.foreign_key}_changed?")
|
31
31
|
if !changed
|
32
|
-
db_self = self.class.find(id
|
33
|
-
write_attribute(
|
32
|
+
db_self = self.class.find(id).lock!
|
33
|
+
write_attribute(nested_interval.foreign_key, db_self.read_attribute(nested_interval.foreign_key))
|
34
34
|
set_nested_interval db_self.lftp, db_self.lftq
|
35
35
|
else
|
36
36
|
# No locking in this case -- caller should have acquired table lock.
|
@@ -21,7 +21,7 @@ module ActsAsNestedInterval
|
|
21
21
|
update_subtree = ->(node){
|
22
22
|
node.create_nested_interval
|
23
23
|
node.save
|
24
|
-
node.class.unscoped.where(
|
24
|
+
node.class.unscoped.where(nested_interval.foreign_key => node.id).find_each &update_subtree
|
25
25
|
}
|
26
26
|
unscoped.roots.find_each &update_subtree
|
27
27
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ActsAsNestedInterval
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
attr_reader :foreign_key, :dependent, :scope_columns
|
5
|
+
|
6
|
+
# 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
|
+
@multiple_roots = !!virtual_root
|
9
|
+
@foreign_key = foreign_key
|
10
|
+
@dependent = dependent
|
11
|
+
@scope_columns = *scope_columns
|
12
|
+
|
13
|
+
check_model_columns( model )
|
14
|
+
end
|
15
|
+
|
16
|
+
def multiple_roots?
|
17
|
+
@multiple_roots
|
18
|
+
end
|
19
|
+
|
20
|
+
def fraction_cache?
|
21
|
+
@fraction_cache
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def check_model_columns( model )
|
27
|
+
if missing_column = Constants::REQUIRED_COLUMNS.detect { |col| !model.columns_hash.has_key?( col.to_s ) }
|
28
|
+
raise Constants::MissingColumn.new( missing_column )
|
29
|
+
end
|
30
|
+
|
31
|
+
@fraction_cache = [:lft, :rgt].all? { |col| model.columns_hash.has_key?( col.to_s ) }
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -5,9 +5,7 @@ module ActsAsNestedInterval
|
|
5
5
|
# selectively define #descendants according to table features
|
6
6
|
included do
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
if columns_hash["lft"]
|
8
|
+
if nested_interval.fraction_cache?
|
11
9
|
|
12
10
|
def descendants
|
13
11
|
#quoted_table_name = self.class.quoted_table_name
|
@@ -18,31 +16,31 @@ module ActsAsNestedInterval
|
|
18
16
|
nested_interval_scope.where( "lftp > :lftp AND lft BETWEEN :lft AND :rgt", lftp: lftp, rgt: rgt, lft: lft )
|
19
17
|
end
|
20
18
|
|
21
|
-
elsif nested_interval_lft_index
|
19
|
+
#elsif nested_interval_lft_index
|
22
20
|
|
23
|
-
def descendants
|
24
|
-
quoted_table_name = self.class.quoted_table_name
|
25
|
-
nested_interval_scope.where <<-SQL
|
26
|
-
|
27
|
-
1.0 * #{quoted_table_name}.lftp / #{quoted_table_name}.lftq BETWEEN
|
28
|
-
|
29
|
-
|
30
|
-
SQL
|
31
|
-
end
|
21
|
+
#def descendants
|
22
|
+
#quoted_table_name = self.class.quoted_table_name
|
23
|
+
#nested_interval_scope.where <<-SQL
|
24
|
+
##{lftp} < #{quoted_table_name}.lftp AND
|
25
|
+
#1.0 * #{quoted_table_name}.lftp / #{quoted_table_name}.lftq BETWEEN
|
26
|
+
##{1.0 * lftp / lftq} AND
|
27
|
+
##{1.0 * rgtp / rgtq}
|
28
|
+
#SQL
|
29
|
+
#end
|
32
30
|
|
33
|
-
elsif connection.adapter_name == "MySQL"
|
31
|
+
#elsif connection.adapter_name == "MySQL"
|
34
32
|
|
35
|
-
def descendants
|
36
|
-
quoted_table_name = self.class.quoted_table_name
|
37
|
-
nested_interval_scope.where <<-SQL
|
38
|
-
( #{quoted_table_name}.lftp != #{rgtp} OR
|
39
|
-
|
40
|
-
) AND
|
41
|
-
|
42
|
-
1 + #{quoted_table_name}.lftq * #{lftp} DIV #{lftq} AND
|
43
|
-
|
44
|
-
SQL
|
45
|
-
end
|
33
|
+
#def descendants
|
34
|
+
#quoted_table_name = self.class.quoted_table_name
|
35
|
+
#nested_interval_scope.where <<-SQL
|
36
|
+
#( #{quoted_table_name}.lftp != #{rgtp} OR
|
37
|
+
##{quoted_table_name}.lftq != #{rgtq}
|
38
|
+
#) AND
|
39
|
+
##{quoted_table_name}.lftp BETWEEN
|
40
|
+
#1 + #{quoted_table_name}.lftq * #{lftp} DIV #{lftq} AND
|
41
|
+
##{quoted_table_name}.lftq * #{rgtp} DIV #{rgtq}
|
42
|
+
#SQL
|
43
|
+
#end
|
46
44
|
|
47
45
|
else
|
48
46
|
|
@@ -71,7 +69,7 @@ module ActsAsNestedInterval
|
|
71
69
|
end
|
72
70
|
|
73
71
|
def set_nested_interval_for_top
|
74
|
-
if
|
72
|
+
if nested_interval.multiple_roots?
|
75
73
|
set_nested_interval(*next_root_lft)
|
76
74
|
else
|
77
75
|
set_nested_interval 0, 1
|
@@ -80,7 +78,7 @@ module ActsAsNestedInterval
|
|
80
78
|
|
81
79
|
def nested_interval_scope
|
82
80
|
conditions = {}
|
83
|
-
|
81
|
+
nested_interval.scope_columns.each do |column_name|
|
84
82
|
conditions[column_name] = send(column_name)
|
85
83
|
end
|
86
84
|
self.class.where conditions
|
@@ -88,18 +86,18 @@ module ActsAsNestedInterval
|
|
88
86
|
|
89
87
|
# Rewrite method
|
90
88
|
def update_nested_interval_move
|
91
|
-
return if self.class.readonly_attributes.include?(
|
89
|
+
return if self.class.readonly_attributes.include?(nested_interval.foreign_key.to_sym) # Fix issue #9
|
92
90
|
begin
|
93
91
|
db_self = self.class.find(id)
|
94
|
-
db_parent = self.class.find(read_attribute(
|
92
|
+
db_parent = self.class.find(read_attribute(nested_interval.foreign_key))
|
95
93
|
if db_self.ancestor_of?(db_parent)
|
96
|
-
errors.add
|
94
|
+
errors.add nested_interval.foreign_key, "is descendant"
|
97
95
|
raise ActiveRecord::RecordInvalid, self
|
98
96
|
end
|
99
97
|
rescue ActiveRecord::RecordNotFound => e # root
|
100
98
|
end
|
101
99
|
|
102
|
-
if read_attribute(
|
100
|
+
if read_attribute(nested_interval.foreign_key).nil? # root move
|
103
101
|
set_nested_interval_for_top
|
104
102
|
else # child move
|
105
103
|
set_nested_interval *parent.next_child_lft
|
@@ -6,6 +6,8 @@
|
|
6
6
|
|
7
7
|
require 'acts_as_nested_interval/core_ext/integer'
|
8
8
|
require 'acts_as_nested_interval/version'
|
9
|
+
require 'acts_as_nested_interval/constants'
|
10
|
+
require 'acts_as_nested_interval/configuration'
|
9
11
|
require 'acts_as_nested_interval/callbacks'
|
10
12
|
require 'acts_as_nested_interval/instance_methods'
|
11
13
|
require 'acts_as_nested_interval/class_methods'
|
@@ -25,33 +27,41 @@ module ActsAsNestedInterval
|
|
25
27
|
# * <tt>:virtual_root</tt> -- whether to compute root's interval as in an upper root (default false)
|
26
28
|
# * <tt>:dependent</tt> -- dependency between the parent node and children nodes (default :restrict)
|
27
29
|
def acts_as_nested_interval(options = {})
|
28
|
-
|
29
|
-
|
30
|
+
# Refactored
|
31
|
+
# TODO: table_exists?
|
32
|
+
cattr_accessor :nested_interval
|
33
|
+
|
34
|
+
self.nested_interval = Configuration.new( self, **options )
|
35
|
+
|
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
|
30
48
|
cattr_accessor :nested_interval_lft_index
|
31
|
-
cattr_accessor :nested_interval_dependent
|
49
|
+
#cattr_accessor :nested_interval_dependent
|
32
50
|
|
33
51
|
cattr_accessor :virtual_root
|
34
52
|
self.virtual_root = !!options[:virtual_root]
|
35
53
|
|
36
|
-
self.nested_interval_foreign_key = options[:foreign_key] || :parent_id
|
37
|
-
self.nested_interval_scope_columns = Array(options[:scope_columns])
|
54
|
+
#self.nested_interval_foreign_key = options[:foreign_key] || :parent_id
|
55
|
+
#self.nested_interval_scope_columns = Array(options[:scope_columns])
|
38
56
|
self.nested_interval_lft_index = options[:lft_index]
|
39
|
-
self.nested_interval_dependent = options[:dependent] || :restrict_with_exception
|
57
|
+
#self.nested_interval_dependent = options[:dependent] || :restrict_with_exception
|
40
58
|
|
41
|
-
belongs_to :parent, class_name: name, foreign_key:
|
42
|
-
has_many :children, class_name: name, foreign_key:
|
43
|
-
dependent:
|
44
|
-
scope :roots, -> { where(
|
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) }
|
45
63
|
|
46
64
|
if self.table_exists? # Fix problem with migrating without table
|
47
|
-
if columns_hash["rgt"]
|
48
|
-
scope :preorder, -> { order('rgt DESC, lftp ASC') }
|
49
|
-
elsif columns_hash["rgtp"] && columns_hash["rgtq"]
|
50
|
-
scope :preorder, -> { order('1.0 * rgtp / rgtq DESC, lftp ASC') }
|
51
|
-
else
|
52
|
-
scope :preorder, -> { order('nested_interval_rgt(lftp, lftq) DESC, lftp ASC') }
|
53
|
-
end
|
54
|
-
|
55
65
|
include ActsAsNestedInterval::InstanceMethods
|
56
66
|
include ActsAsNestedInterval::Callbacks
|
57
67
|
extend ActsAsNestedInterval::ClassMethods
|
@@ -1,5 +1,9 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
+
def set_virtual_root( value )
|
4
|
+
Region.nested_interval.instance_eval("@multiple_roots = #{ value }")
|
5
|
+
end
|
6
|
+
|
3
7
|
class ActsAsNestedIntervalTest < ActiveSupport::TestCase
|
4
8
|
def test_modular_inverse
|
5
9
|
assert_equal [nil, 1, 5, nil, 7, 2, nil, 4, 8], (0...9).map { |k| k.inverse(9) }
|
@@ -195,7 +199,7 @@ class ActsAsNestedIntervalTest < ActiveSupport::TestCase
|
|
195
199
|
end
|
196
200
|
|
197
201
|
def test_virtual_root_order
|
198
|
-
|
202
|
+
set_virtual_root( true )
|
199
203
|
r1 = Region.create name: "1"
|
200
204
|
r2 = Region.create name: "2"
|
201
205
|
r3 = Region.create name: "3"
|
@@ -204,7 +208,7 @@ class ActsAsNestedIntervalTest < ActiveSupport::TestCase
|
|
204
208
|
end
|
205
209
|
|
206
210
|
def test_virtual_root_allocation
|
207
|
-
|
211
|
+
set_virtual_root( true )
|
208
212
|
r1 = Region.create name: "Europe"
|
209
213
|
r2 = Region.create name: "Romania", :parent => r1
|
210
214
|
r3 = Region.create name: "Asia"
|
@@ -215,7 +219,7 @@ class ActsAsNestedIntervalTest < ActiveSupport::TestCase
|
|
215
219
|
end
|
216
220
|
|
217
221
|
def test_rebuild_nested_interval_tree
|
218
|
-
|
222
|
+
set_virtual_root( true )
|
219
223
|
r1 = Region.create name: "Europe"
|
220
224
|
r2 = Region.create name: "Romania", parent: r1
|
221
225
|
r3 = Region.create name: "Asia"
|
@@ -227,7 +231,7 @@ class ActsAsNestedIntervalTest < ActiveSupport::TestCase
|
|
227
231
|
end
|
228
232
|
|
229
233
|
def test_root_update_keeps_interval
|
230
|
-
|
234
|
+
set_virtual_root( true )
|
231
235
|
r1 = Region.create name: "Europe"
|
232
236
|
r2 = Region.create name: "Romania", parent: r1
|
233
237
|
r3 = Region.create name: "Asia"
|
@@ -239,7 +243,7 @@ class ActsAsNestedIntervalTest < ActiveSupport::TestCase
|
|
239
243
|
end
|
240
244
|
|
241
245
|
def test_move_to_root_recomputes_interval
|
242
|
-
|
246
|
+
set_virtual_root( true )
|
243
247
|
r1 = Region.create name: "Europe"
|
244
248
|
r2 = Region.create name: "Romania", parent: r1
|
245
249
|
r3 = Region.create name: "Asia"
|
data/test/dummy/db/schema.rb
CHANGED
@@ -13,9 +13,6 @@
|
|
13
13
|
|
14
14
|
ActiveRecord::Schema.define(version: 20121004204252) do
|
15
15
|
|
16
|
-
# These are extensions that must be enabled in order to support this database
|
17
|
-
enable_extension "plpgsql"
|
18
|
-
|
19
16
|
create_table "regions", force: true do |t|
|
20
17
|
t.boolean "fiction", default: false, null: false
|
21
18
|
t.integer "region_id"
|
@@ -15,3 +15,18 @@ Migrating to ChangeIntervalPrecision (20121004204252)
|
|
15
15
|
[1m[36mSQL (1.0ms)[0m [1mINSERT INTO "schema_migrations" ("version") VALUES ($1)[0m [["version", "20121004204252"]]
|
16
16
|
[1m[35m (17.3ms)[0m COMMIT
|
17
17
|
[1m[36mActiveRecord::SchemaMigration Load (0.9ms)[0m [1mSELECT "schema_migrations".* FROM "schema_migrations"[0m
|
18
|
+
[1m[36m (1710.7ms)[0m [1mCREATE TABLE `schema_migrations` (`version` varchar(255) NOT NULL) ENGINE=InnoDB[0m
|
19
|
+
[1m[35m (119.1ms)[0m CREATE UNIQUE INDEX `unique_schema_migrations` ON `schema_migrations` (`version`)
|
20
|
+
[1m[36mActiveRecord::SchemaMigration Load (0.9ms)[0m [1mSELECT `schema_migrations`.* FROM `schema_migrations`[0m
|
21
|
+
Migrating to CreateRegions (20120302143528)
|
22
|
+
[1m[35m (87.2ms)[0m CREATE TABLE `regions` (`id` int(11) auto_increment PRIMARY KEY, `fiction` tinyint(1) DEFAULT 0 NOT NULL, `region_id` int(11), `lftp` int(11) NOT NULL, `lftq` int(11) NOT NULL, `rgtp` int(11) NOT NULL, `rgtq` int(11) NOT NULL, `lft` float NOT NULL, `rgt` float NOT NULL, `name` varchar(255) NOT NULL) ENGINE=InnoDB
|
23
|
+
[1m[36m (0.4ms)[0m [1mBEGIN[0m
|
24
|
+
[1m[35mSQL (0.9ms)[0m INSERT INTO `schema_migrations` (`version`) VALUES ('20120302143528')
|
25
|
+
[1m[36m (23.7ms)[0m [1mCOMMIT[0m
|
26
|
+
Migrating to ChangeIntervalPrecision (20121004204252)
|
27
|
+
[1m[35m (157.5ms)[0m ALTER TABLE `regions` CHANGE `lft` `lft` decimal(31,30) NOT NULL
|
28
|
+
[1m[36m (102.6ms)[0m [1mALTER TABLE `regions` CHANGE `rgt` `rgt` decimal(31,30) NOT NULL[0m
|
29
|
+
[1m[35m (0.5ms)[0m BEGIN
|
30
|
+
[1m[36mSQL (1.4ms)[0m [1mINSERT INTO `schema_migrations` (`version`) VALUES ('20121004204252')[0m
|
31
|
+
[1m[35m (23.8ms)[0m COMMIT
|
32
|
+
[1m[36mActiveRecord::SchemaMigration Load (0.8ms)[0m [1mSELECT `schema_migrations`.* FROM `schema_migrations`[0m
|