rod 0.6.2 → 0.6.3
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.
- data/.travis.yml +1 -0
- data/README.rdoc +10 -9
- data/Rakefile +15 -5
- data/changelog.txt +18 -0
- data/features/append.feature +0 -2
- data/features/basic.feature +7 -7
- data/features/collection_proxy.feature +140 -0
- data/features/flat_indexing.feature +9 -8
- data/features/{fred.feature → persistence.feature} +5 -8
- data/features/{assoc_indexing.feature → relationship_indexing.feature} +36 -0
- data/features/segmented_indexing.feature +6 -6
- data/features/steps/collection_proxy.rb +89 -0
- data/features/steps/model.rb +15 -3
- data/features/steps/rod.rb +1 -1
- data/features/support/mocha.rb +16 -0
- data/features/update.feature +263 -0
- data/lib/rod.rb +10 -2
- data/lib/rod/abstract_database.rb +49 -111
- data/lib/rod/abstract_model.rb +26 -6
- data/lib/rod/collection_proxy.rb +235 -34
- data/lib/rod/constants.rb +1 -1
- data/lib/rod/database.rb +5 -6
- data/lib/rod/exception.rb +1 -1
- data/lib/rod/index/base.rb +97 -0
- data/lib/rod/index/flat_index.rb +72 -0
- data/lib/rod/index/segmented_index.rb +100 -0
- data/lib/rod/model.rb +172 -185
- data/lib/rod/reference_updater.rb +85 -0
- data/lib/rod/utils.rb +29 -0
- data/rod.gemspec +4 -1
- data/tests/migration_create.rb +33 -12
- data/tests/migration_migrate.rb +24 -7
- data/tests/migration_model1.rb +5 -0
- data/tests/migration_model2.rb +36 -0
- data/tests/migration_verify.rb +49 -42
- data/tests/missing_class_create.rb +21 -0
- data/tests/missing_class_verify.rb +20 -0
- data/tests/properties_order_create.rb +16 -0
- data/tests/properties_order_verify.rb +17 -0
- data/tests/unit/abstract_database.rb +13 -0
- data/tests/unit/model_tests.rb +3 -3
- data/utils/convert_index.rb +1 -1
- metadata +62 -18
- data/lib/rod/segmented_index.rb +0 -85
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'rod/exception'
|
2
|
+
|
3
|
+
module Rod
|
4
|
+
# This class provides the set of reference updaters, that is objects
|
5
|
+
# used to break down the process of data storage into separate steps.
|
6
|
+
# If there is an object A which reference object B, there might be two
|
7
|
+
# cases: object A is stored *before* object B is stored or *after* the object
|
8
|
+
# B is stored. In the first case, the id of the object B is not know, so
|
9
|
+
# it might be updated only after the object is stored. If the object B
|
10
|
+
# stored the reference to object A (to update its reference to the object
|
11
|
+
# B), then the object A could not be GC'ed until object B is stored.
|
12
|
+
# For large nets of objects, this would result in large non-GCable collections
|
13
|
+
# of objects. The reference updater splits the reference of object B to A
|
14
|
+
# and allows for GC of A, even thou B is not yet stored.
|
15
|
+
class ReferenceUpdater
|
16
|
+
# Singular reference updater holds the +rod_id+ and +class_id+ of the
|
17
|
+
# object that has to be updated and the name of the
|
18
|
+
# +property+ of the reference to be updated.
|
19
|
+
class SingularUpdater
|
20
|
+
def initialize(database,rod_id,class_id,property)
|
21
|
+
@database = database
|
22
|
+
@rod_id = rod_id
|
23
|
+
@class_id = class_id
|
24
|
+
@property = property
|
25
|
+
end
|
26
|
+
|
27
|
+
# Updates the id of the referenced +object+.
|
28
|
+
def update(object)
|
29
|
+
referee = Model.get_class(@class_id).find_by_rod_id(@rod_id)
|
30
|
+
referee.update_singular_association(@property,object)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Plural reference updater holds the +collection+ proxy
|
35
|
+
# that includes the reference to be updated and its +index+
|
36
|
+
# within that collection.
|
37
|
+
class PluralUpdater
|
38
|
+
def initialize(database,collection,index)
|
39
|
+
@database = database
|
40
|
+
@collection = collection
|
41
|
+
@index = index
|
42
|
+
end
|
43
|
+
|
44
|
+
# Updates the id of the referenced +object+.
|
45
|
+
def update(object)
|
46
|
+
@collection.send(:update_reference_id,object.rod_id,@index)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# This updater is used when there is an index of Rod objects
|
51
|
+
# and one of its keys is an object which is not yet stored.
|
52
|
+
# The key of the index is set to the rod_id, when the object
|
53
|
+
# is stored.
|
54
|
+
class IndexUpdater
|
55
|
+
# The updater is initialized with the +index+ to be updated.
|
56
|
+
def initialize(index)
|
57
|
+
@index = index
|
58
|
+
end
|
59
|
+
|
60
|
+
# Updates the index by providing the object with the updated +rod_id+.
|
61
|
+
def update(object)
|
62
|
+
@index.key_persisted(object)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Creates singular reference updater of for the +property+
|
67
|
+
# of the +object+ that belongs to the +database+.
|
68
|
+
def self.for_singular(object,property,database)
|
69
|
+
SingularUpdater.new(database,object.rod_id,object.class.name_hash,property)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Creates plural reference updater for given
|
73
|
+
# +collection+ proxy with given +index+.
|
74
|
+
def self.for_plural(collection,index,database)
|
75
|
+
PluralUpdater.new(database,collection,index)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Creates reference updater for an index. It is used
|
79
|
+
# when the indexed plural association includes objects
|
80
|
+
# that are not yet persisted.
|
81
|
+
def self.for_index(index)
|
82
|
+
IndexUpdater.new(index)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/rod/utils.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Rod
|
5
|
+
module Utils
|
6
|
+
# Removes single file.
|
7
|
+
def remove_file(file_name)
|
8
|
+
if test(?f,file_name)
|
9
|
+
File.delete(file_name)
|
10
|
+
puts "Removing #{file_name}" if $ROD_DEBUG
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Remove all files matching the +pattern+.
|
15
|
+
# If +skip+ given, the file with the given name is not deleted.
|
16
|
+
def remove_files(pattern,skip=nil)
|
17
|
+
Dir.glob(pattern).each do |file_name|
|
18
|
+
remove_file(file_name) unless file_name == skip
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Removes all files which are similar (i.e. are generated
|
23
|
+
# by RubyInline for the same class) to +name+
|
24
|
+
# excluding the file with exactly the name given.
|
25
|
+
def remove_files_but(name)
|
26
|
+
remove_files(name.sub(INLINE_PATTERN_RE,"*"),name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/rod.gemspec
CHANGED
@@ -5,6 +5,7 @@ Gem::Specification.new do |s|
|
|
5
5
|
s.name = "rod"
|
6
6
|
s.version = Rod::VERSION
|
7
7
|
s.date = "#{Time.now.strftime("%Y-%m-%d")}"
|
8
|
+
s.required_ruby_version = '>= 1.9.2'
|
8
9
|
# TODO set to Linux/MacOSX and Ruby 1.9
|
9
10
|
s.platform = Gem::Platform::RUBY
|
10
11
|
s.authors = ['Aleksander Pohl']
|
@@ -21,10 +22,12 @@ Gem::Specification.new do |s|
|
|
21
22
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
23
|
s.require_path = "lib"
|
23
24
|
|
24
|
-
s.add_dependency("RubyInline", [">= 3.
|
25
|
+
s.add_dependency("RubyInline", [">= 3.10.0","< 4.0.0"])
|
25
26
|
s.add_dependency("english", [">= 0.5.0","< 0.6.0"])
|
26
27
|
s.add_dependency("activemodel", [">= 3.0.7","< 3.1.0"])
|
28
|
+
s.add_dependency("bsearch", [">= 1.5.0","< 1.6.0"])
|
27
29
|
s.add_development_dependency("mocha", [">= 0.9.8","< 1.0.0"])
|
28
30
|
s.add_development_dependency("cucumber", "~> 1.0.0")
|
29
31
|
s.add_development_dependency("rspec", [">= 2.2.0","< 2.3.0"])
|
32
|
+
s.add_development_dependency("rake", [">= 0.9.0","< 1.0.0"])
|
30
33
|
end
|
data/tests/migration_create.rb
CHANGED
@@ -7,19 +7,40 @@ Rod::Database.development_mode = true
|
|
7
7
|
|
8
8
|
Database.instance.create_database("tmp/migration")
|
9
9
|
|
10
|
-
|
10
|
+
count = (ARGV[0] || 10).to_i
|
11
|
+
puts "Count in migration test: #{count}"
|
12
|
+
|
13
|
+
files = count.times.map{|i| UserFile.new(:data => "#{i} data")}
|
11
14
|
files.each{|f| f.store}
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
account
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
users = []
|
17
|
+
count.times do |index|
|
18
|
+
account = Account.new(:login => "john#{index}",
|
19
|
+
:nick => "j#{index}")
|
20
|
+
account.store
|
21
|
+
user1 = User.new(:name => "John#{index}",
|
22
|
+
:surname => "Smith#{index}",
|
23
|
+
:account => account,
|
24
|
+
:mother => users[index-1],
|
25
|
+
:father => users[index-2],
|
26
|
+
:friends => [users[index-3],users[index-4]],
|
27
|
+
:files => [files[index],files[index + 1],files[index + 2]])
|
28
|
+
user1.store
|
29
|
+
|
30
|
+
account = Account.new(:login => "amanda#{index}",
|
31
|
+
:nick => "a#{index}")
|
32
|
+
account.store
|
33
|
+
user2 = User.new(:name => "Amanda#{index}",
|
34
|
+
:surname => "Amanda#{index}",
|
35
|
+
:account => account,
|
36
|
+
:mother => users[index-1],
|
37
|
+
:father => users[index-2],
|
38
|
+
:friends => [users[index-5],users[index-6]],
|
39
|
+
:files => [files[index],files[index+4],files[index+5],
|
40
|
+
nil,files[index+6]])
|
41
|
+
user2.store
|
42
|
+
users << user1
|
43
|
+
users << user2
|
44
|
+
end
|
24
45
|
|
25
46
|
Database.instance.close_database
|
data/tests/migration_migrate.rb
CHANGED
@@ -3,19 +3,36 @@ require 'rod'
|
|
3
3
|
require File.join(".",File.dirname(__FILE__),"migration_model2")
|
4
4
|
require 'rspec/expectations'
|
5
5
|
|
6
|
-
|
6
|
+
#$ROD_DEBUG = true
|
7
7
|
Rod::Database.development_mode = true
|
8
8
|
|
9
9
|
Database.instance.open_database("tmp/migration", :migrate => true,
|
10
10
|
:readonly => false)
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
count = (ARGV[0] || 10).to_i
|
13
|
+
count.times do |index|
|
14
|
+
account1 = Account[index * 2]
|
15
|
+
account1.password = "pass#{index * 2}"
|
16
|
+
account1.store
|
17
|
+
file = UserFile[index]
|
18
|
+
file.name = "file#{index}"
|
19
|
+
file.store
|
20
|
+
user = User[index*2]
|
21
|
+
user.age = index
|
22
|
+
user.file = file
|
23
|
+
user.accounts << account1
|
24
|
+
user.store
|
15
25
|
|
16
|
-
|
17
|
-
|
18
|
-
|
26
|
+
account2 = Account[index * 2 + 1]
|
27
|
+
account2.password = "pass#{index * 2 + 1}"
|
28
|
+
account2.store
|
29
|
+
user = User[index*2 + 1]
|
30
|
+
user.age = index * 2
|
31
|
+
user.file = file
|
32
|
+
user.accounts << account1
|
33
|
+
user.accounts << account2
|
34
|
+
user.store
|
35
|
+
end
|
19
36
|
|
20
37
|
Database.instance.close_database
|
21
38
|
|
data/tests/migration_model1.rb
CHANGED
@@ -11,13 +11,18 @@ class User < Model
|
|
11
11
|
field :name, :string, :index => :flat
|
12
12
|
field :surname, :string
|
13
13
|
has_one :account, :index => :flat
|
14
|
+
has_one :mother, :class_name => "User"
|
15
|
+
has_one :father, :class_name => "User"
|
14
16
|
has_many :files, :index => :flat, :class_name => "UserFile"
|
17
|
+
has_many :friends, :class_name => "User"
|
15
18
|
end
|
16
19
|
|
17
20
|
class Account < Model
|
18
21
|
field :login, :string
|
22
|
+
field :nick, :string
|
19
23
|
end
|
20
24
|
|
21
25
|
class UserFile < Model
|
22
26
|
field :data, :string
|
27
|
+
field :path, :string
|
23
28
|
end
|
data/tests/migration_model2.rb
CHANGED
@@ -8,20 +8,56 @@ class Model < Rod::Model
|
|
8
8
|
end
|
9
9
|
|
10
10
|
class User < Model
|
11
|
+
# present
|
11
12
|
field :name, :string, :index => :flat
|
13
|
+
|
14
|
+
# removed
|
15
|
+
# field :surname, :string
|
16
|
+
|
17
|
+
# added
|
12
18
|
field :age, :integer
|
19
|
+
|
20
|
+
# present
|
13
21
|
has_one :account, :index => :flat
|
22
|
+
|
23
|
+
# removed
|
24
|
+
# has_one :mother, :class_name => "User"
|
25
|
+
|
26
|
+
# removed
|
27
|
+
# has_one :father, :class_name => "User"
|
28
|
+
|
29
|
+
# added
|
30
|
+
has_one :file, :class_name => "UserFile"
|
31
|
+
|
32
|
+
# present
|
14
33
|
has_many :files, :index => :flat, :class_name => "UserFile"
|
34
|
+
|
35
|
+
# added
|
15
36
|
has_many :accounts, :index => :flat
|
37
|
+
|
38
|
+
# removed
|
39
|
+
# has_many :friends, :class_name => "User"
|
16
40
|
end
|
17
41
|
|
18
42
|
class Account < Model
|
43
|
+
# present
|
19
44
|
field :login, :string
|
45
|
+
|
46
|
+
# removed
|
47
|
+
# field :nick, :string
|
48
|
+
|
49
|
+
# added
|
20
50
|
field :password, :string
|
21
51
|
end
|
22
52
|
|
23
53
|
class UserFile < Model
|
54
|
+
# present
|
24
55
|
field :data, :string
|
56
|
+
|
57
|
+
# removed
|
58
|
+
# field :path, :string
|
59
|
+
|
60
|
+
# added
|
25
61
|
field :name, :string, :index => :flat
|
26
62
|
end
|
27
63
|
|
data/tests/migration_verify.rb
CHANGED
@@ -7,50 +7,57 @@ Rod::Database.development_mode = true
|
|
7
7
|
|
8
8
|
Database.instance.open_database("tmp/migration")
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
user
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
account
|
23
|
-
account.
|
24
|
-
account.
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
user
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
account
|
40
|
-
account.
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
10
|
+
count = (ARGV[0] || 10).to_i
|
11
|
+
count.times do |index|
|
12
|
+
user1 = User[index*2]
|
13
|
+
user1.should_not == nil
|
14
|
+
user = User.find_by_name("John#{index}")
|
15
|
+
user1.should == user
|
16
|
+
user1.name.should == "John#{index}"
|
17
|
+
user1.age.should == index
|
18
|
+
user1.account.should_not == nil
|
19
|
+
user1.account.should == Account[index * 2]
|
20
|
+
user1.account.login.should == "john#{index}"
|
21
|
+
user1.account.password.should == "pass#{index * 2}"
|
22
|
+
User.find_all_by_account(user1.account).size.should == 1
|
23
|
+
User.find_all_by_account(user1.account)[0].should == user
|
24
|
+
User.find_by_account(user1.account).should_not == nil
|
25
|
+
user1.files.size.should == 3
|
26
|
+
user1.files[0].data.should == "#{index} data"
|
27
|
+
user1.files[0].name.should == "file#{index}"
|
28
|
+
user1.accounts.size.should == 1
|
29
|
+
user1.accounts[0].should == user1.account
|
30
|
+
|
31
|
+
user2 = User[index*2+1]
|
32
|
+
user2.should_not == nil
|
33
|
+
user = User.find_by_name("Amanda#{index}")
|
34
|
+
user2.should == user
|
35
|
+
user2.name.should == "Amanda#{index}"
|
36
|
+
user2.age.should == index * 2
|
37
|
+
user2.account.should_not == nil
|
38
|
+
user2.account.should == Account[index * 2 + 1]
|
39
|
+
user2.account.password.should == "pass#{index * 2 + 1}"
|
40
|
+
User.find_by_account(user2.account).should == user2
|
41
|
+
user2.files.size.should == 5
|
42
|
+
user2.files[0].data.should == "#{index} data"
|
43
|
+
user2.files[0].name.should == "file#{index}"
|
44
|
+
user2.files[3].data.should == nil unless user2.files[3].nil?
|
45
|
+
user2.accounts.size.should == 2
|
46
|
+
user2.accounts[0].should == user1.account
|
47
|
+
user2.accounts[1].should == user2.account
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
UserFile.each.with_index do |file,index|
|
49
52
|
file.data.should_not == nil
|
50
|
-
file.
|
53
|
+
file.data.should == "#{index} data"
|
54
|
+
file.name.should_not == nil
|
55
|
+
file.name.should == "file#{index}"
|
51
56
|
end
|
52
|
-
|
57
|
+
|
58
|
+
users = User.find_all_by_files(UserFile[0])
|
53
59
|
users.size.should == 2
|
54
|
-
users[
|
60
|
+
users[0].should == User[0]
|
61
|
+
users[1].should == User[1]
|
55
62
|
|
56
63
|
Database.instance.close_database
|
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.unshift("lib")
|
2
|
+
require 'rod'
|
3
|
+
|
4
|
+
class User < Rod::Model
|
5
|
+
database_class Rod::Database
|
6
|
+
field :name, :string
|
7
|
+
end
|
8
|
+
|
9
|
+
class Item < Rod::Model
|
10
|
+
database_class Rod::Database
|
11
|
+
field :name, :string
|
12
|
+
end
|
13
|
+
|
14
|
+
Rod::Database.development_mode = true
|
15
|
+
|
16
|
+
Rod::Database.instance.create_database("tmp/missing_class")
|
17
|
+
user = User.new(:name => "John")
|
18
|
+
user.store
|
19
|
+
item = Item.new(:name => "hammer")
|
20
|
+
item.store
|
21
|
+
Rod::Database.instance.close_database
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.unshift("lib")
|
2
|
+
require 'rod'
|
3
|
+
require 'rspec/expectations'
|
4
|
+
include RSpec::Matchers
|
5
|
+
|
6
|
+
class User < Rod::Model
|
7
|
+
database_class Rod::Database
|
8
|
+
field :name, :string
|
9
|
+
end
|
10
|
+
|
11
|
+
# This class is missing in the runtime
|
12
|
+
#class Item < Rod::Model
|
13
|
+
# database_class Rod::Database
|
14
|
+
# field :name
|
15
|
+
#end
|
16
|
+
|
17
|
+
Rod::Database.development_mode = true
|
18
|
+
|
19
|
+
(lambda {Rod::Database.instance.open_database("tmp/missing_class")}).
|
20
|
+
should raise_error(Rod::DatabaseError)
|