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.
Files changed (44) hide show
  1. data/.travis.yml +1 -0
  2. data/README.rdoc +10 -9
  3. data/Rakefile +15 -5
  4. data/changelog.txt +18 -0
  5. data/features/append.feature +0 -2
  6. data/features/basic.feature +7 -7
  7. data/features/collection_proxy.feature +140 -0
  8. data/features/flat_indexing.feature +9 -8
  9. data/features/{fred.feature → persistence.feature} +5 -8
  10. data/features/{assoc_indexing.feature → relationship_indexing.feature} +36 -0
  11. data/features/segmented_indexing.feature +6 -6
  12. data/features/steps/collection_proxy.rb +89 -0
  13. data/features/steps/model.rb +15 -3
  14. data/features/steps/rod.rb +1 -1
  15. data/features/support/mocha.rb +16 -0
  16. data/features/update.feature +263 -0
  17. data/lib/rod.rb +10 -2
  18. data/lib/rod/abstract_database.rb +49 -111
  19. data/lib/rod/abstract_model.rb +26 -6
  20. data/lib/rod/collection_proxy.rb +235 -34
  21. data/lib/rod/constants.rb +1 -1
  22. data/lib/rod/database.rb +5 -6
  23. data/lib/rod/exception.rb +1 -1
  24. data/lib/rod/index/base.rb +97 -0
  25. data/lib/rod/index/flat_index.rb +72 -0
  26. data/lib/rod/index/segmented_index.rb +100 -0
  27. data/lib/rod/model.rb +172 -185
  28. data/lib/rod/reference_updater.rb +85 -0
  29. data/lib/rod/utils.rb +29 -0
  30. data/rod.gemspec +4 -1
  31. data/tests/migration_create.rb +33 -12
  32. data/tests/migration_migrate.rb +24 -7
  33. data/tests/migration_model1.rb +5 -0
  34. data/tests/migration_model2.rb +36 -0
  35. data/tests/migration_verify.rb +49 -42
  36. data/tests/missing_class_create.rb +21 -0
  37. data/tests/missing_class_verify.rb +20 -0
  38. data/tests/properties_order_create.rb +16 -0
  39. data/tests/properties_order_verify.rb +17 -0
  40. data/tests/unit/abstract_database.rb +13 -0
  41. data/tests/unit/model_tests.rb +3 -3
  42. data/utils/convert_index.rb +1 -1
  43. metadata +62 -18
  44. 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
@@ -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
@@ -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.8.3","< 4.0.0"])
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
@@ -7,19 +7,40 @@ Rod::Database.development_mode = true
7
7
 
8
8
  Database.instance.create_database("tmp/migration")
9
9
 
10
- files = 10.times.map{|i| UserFile.new(:data => "#{i} data")}
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
- account = Account.new(:login => "john")
14
- account.store
15
- user = User.new(:name => "John", :account => account,
16
- :files => [files[0],files[1],files[2]])
17
- user.store
18
-
19
- account = Account.new(:login => "amanda")
20
- account.store
21
- user = User.new(:name => "Amanda", :account => account,
22
- :files => [files[0],files[4],files[5],nil,files[6]])
23
- user.store
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
@@ -3,19 +3,36 @@ require 'rod'
3
3
  require File.join(".",File.dirname(__FILE__),"migration_model2")
4
4
  require 'rspec/expectations'
5
5
 
6
- $ROD_DEBUG = true
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
- user = User[0]
13
- user.accounts << Account[0]
14
- user.store
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
- user = User[1]
17
- user.accounts << Account[1]
18
- user.store
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
 
@@ -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
@@ -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
 
@@ -7,50 +7,57 @@ Rod::Database.development_mode = true
7
7
 
8
8
  Database.instance.open_database("tmp/migration")
9
9
 
10
- user = User[0]
11
- user.should_not == nil
12
- user = User.find_by_name("John")
13
- user.should_not == nil
14
- user.name.should == "John"
15
- user.age.should == 0
16
- user.account.should_not == nil
17
- user.account.should == Account[0]
18
- user.files.size.should == 3
19
- user.accounts.size.should == 1
20
- user.accounts[0].should == user.account
21
-
22
- account = Account[0]
23
- account.login.should == "john"
24
- account.password.should == ""
25
- User.find_all_by_account(account).size.should == 1
26
- User.find_all_by_account(account)[0].should == user
27
- User.find_by_account(account).should_not == nil
28
- user.account.should == account
29
-
30
- user = User.find_by_name("Amanda")
31
- user.should_not == nil
32
- user.name.should == "Amanda"
33
- user.age.should == 0
34
- user.account.should_not == nil
35
- user.files.size.should == 5
36
- user.accounts.size.should == 1
37
- user.accounts[0].should == user.account
38
-
39
- account = Account[1]
40
- account.login.should == "amanda"
41
- account.password.should == ""
42
- User.find_by_account(account).should_not == nil
43
- user.account.should == account
44
-
45
- file = UserFile[0]
46
- file.data.should == "0 data"
47
-
48
- UserFile.each do |file|
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.name.should == ""
53
+ file.data.should == "#{index} data"
54
+ file.name.should_not == nil
55
+ file.name.should == "file#{index}"
51
56
  end
52
- users = User.find_all_by_files(file)
57
+
58
+ users = User.find_all_by_files(UserFile[0])
53
59
  users.size.should == 2
54
- users[1].should == user
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)