acts_as_nested_interval 0.2.0.pre → 0.2.0.pre2
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 +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
|