odb 0.1.0
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/LICENSE +22 -0
- data/README.rdoc +5 -0
- data/Rakefile +52 -0
- data/lib/odb.rb +236 -0
- data/spec/persistence_spec.rb +28 -0
- metadata +59 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009 Loren Segal
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/lib/odb'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
|
5
|
+
WINDOWS = (PLATFORM =~ /win32|cygwin/ ? true : false) rescue false
|
6
|
+
SUDO = WINDOWS ? '' : 'sudo'
|
7
|
+
|
8
|
+
task :default => :specs
|
9
|
+
|
10
|
+
load 'odb.gemspec'
|
11
|
+
Rake::GemPackageTask.new(SPEC) do |pkg|
|
12
|
+
pkg.gem_spec = SPEC
|
13
|
+
pkg.need_zip = true
|
14
|
+
pkg.need_tar = true
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Install the gem locally"
|
18
|
+
task :install => :gem do
|
19
|
+
sh "#{SUDO} gem install pkg/#{SPEC.name}-#{SPEC.version}.gem --local --no-rdoc --no-ri"
|
20
|
+
sh "rm -rf pkg/odb-#{SPEC.version}" unless ENV['KEEP_FILES']
|
21
|
+
end
|
22
|
+
|
23
|
+
begin
|
24
|
+
require 'spec'
|
25
|
+
require 'spec/rake/spectask'
|
26
|
+
|
27
|
+
desc "Run all specs"
|
28
|
+
Spec::Rake::SpecTask.new("specs") do |t|
|
29
|
+
$DEBUG = true if ENV['DEBUG']
|
30
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
31
|
+
t.spec_opts += ["--require", File.join(File.dirname(__FILE__), 'spec', 'spec_helper')]
|
32
|
+
t.spec_files = Dir["spec/**/*_spec.rb"].sort
|
33
|
+
|
34
|
+
if ENV['RCOV']
|
35
|
+
hide = '_spec\.rb$,spec_helper\.rb$,ruby_lex\.rb$,autoload\.rb$'
|
36
|
+
hide += ',legacy\/.+_handler,html_syntax_highlight_helper18\.rb$' if RUBY19
|
37
|
+
hide += ',ruby_parser\.rb$,ast_node\.rb$,handlers\/ruby\/[^\/]+\.rb$,html_syntax_highlight_helper\.rb$' if RUBY18
|
38
|
+
t.rcov = true
|
39
|
+
t.rcov_opts = ['-x', hide]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
task :spec => :specs
|
43
|
+
rescue LoadError
|
44
|
+
warn "warn: RSpec tests not available. `gem install rspec` to enable them."
|
45
|
+
end
|
46
|
+
|
47
|
+
begin
|
48
|
+
require 'yard'
|
49
|
+
YARD::Rake::YardocTask.new
|
50
|
+
rescue LoadError
|
51
|
+
warn "warn: YARD is not available. `gem install yard` to enable documentation generation"
|
52
|
+
end
|
data/lib/odb.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module ODB
|
5
|
+
def self.new(store = nil)
|
6
|
+
Database.new(store || HashStore.new)
|
7
|
+
end
|
8
|
+
|
9
|
+
module Persistent
|
10
|
+
end
|
11
|
+
|
12
|
+
class Database
|
13
|
+
attr_accessor :store
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :current
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(store)
|
20
|
+
self.store = store
|
21
|
+
self.store.db = self
|
22
|
+
self.class.current = self unless self.class.current
|
23
|
+
end
|
24
|
+
|
25
|
+
def transaction(&block)
|
26
|
+
Transaction.new(self, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class DataStore
|
31
|
+
attr_reader :name, :key_cache
|
32
|
+
attr_accessor :db
|
33
|
+
|
34
|
+
def initialize(name)
|
35
|
+
@name = name
|
36
|
+
@key_cache = {}
|
37
|
+
@object_map = {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def read(key)
|
41
|
+
key = key_cache[key] if Symbol === key
|
42
|
+
if oid = @object_map[key]
|
43
|
+
ObjectSpace._id2ref(oid)
|
44
|
+
else
|
45
|
+
obj = read_object(key)
|
46
|
+
@object_map[key] = obj.object_id
|
47
|
+
obj
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def write(key, value)
|
52
|
+
if Symbol === key
|
53
|
+
key = (key_cache[key] = value.object_id)
|
54
|
+
@object_map[key] = value.object_id
|
55
|
+
return
|
56
|
+
end
|
57
|
+
@object_map[key] = value.object_id
|
58
|
+
write_object(key, value)
|
59
|
+
end
|
60
|
+
|
61
|
+
def [](key) read(key) end
|
62
|
+
def []=(key, value) write(key, value) end
|
63
|
+
|
64
|
+
def after_commit; end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def read_object(key) raise NotImplementedError end
|
69
|
+
def write_object(key, value) raise NotImplementedError end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Transaction
|
73
|
+
attr_accessor :objects
|
74
|
+
|
75
|
+
def self.transactions
|
76
|
+
Thread.current['__odb_transactions'] ||= []
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.current
|
80
|
+
transactions.last
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize(db = ODB.current, &block)
|
84
|
+
@objects = Set.new
|
85
|
+
@db = db
|
86
|
+
transaction(&block) if block_given?
|
87
|
+
end
|
88
|
+
|
89
|
+
def transaction(&block)
|
90
|
+
objects_before = persistent_objects
|
91
|
+
self.class.transactions << self
|
92
|
+
yield
|
93
|
+
self.objects << (persistent_objects - objects_before)
|
94
|
+
commit
|
95
|
+
self.class.transactions.pop
|
96
|
+
end
|
97
|
+
|
98
|
+
def commit
|
99
|
+
self.objects = objects.to_a
|
100
|
+
while objects.size > 0
|
101
|
+
object = objects.pop
|
102
|
+
@db.store[object.object_id] = object
|
103
|
+
end
|
104
|
+
@db.store.after_commit
|
105
|
+
end
|
106
|
+
|
107
|
+
def persistent_objects
|
108
|
+
ObjectSpace.each_object(Persistent).to_a
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class FileStore < DataStore
|
113
|
+
def initialize(name)
|
114
|
+
super(name)
|
115
|
+
FileUtils.mkdir_p(name)
|
116
|
+
if File.file?(resource("__key_cache"))
|
117
|
+
@key_cache = Marshal.load(IO.read(resource("__key_cache")))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def after_commit
|
122
|
+
File.open(resource("__key_cache"), "wb") do |file|
|
123
|
+
file.write(Marshal.dump(key_cache))
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
protected
|
128
|
+
|
129
|
+
def resource(key)
|
130
|
+
File.join(name, key.to_s)
|
131
|
+
end
|
132
|
+
|
133
|
+
def read_object(key)
|
134
|
+
Marshal.load(IO.read(resource(key))).__deserialize__
|
135
|
+
end
|
136
|
+
|
137
|
+
def write_object(key, value)
|
138
|
+
resource = resource(key)
|
139
|
+
FileUtils.mkdir_p(File.dirname(resource))
|
140
|
+
File.open(resource, "wb") do |file|
|
141
|
+
file.write(Marshal.dump(value.__serialize__))
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class HashStore < DataStore
|
147
|
+
def initialize
|
148
|
+
super(nil)
|
149
|
+
@store = {}
|
150
|
+
end
|
151
|
+
|
152
|
+
def read_object(key)
|
153
|
+
@store[key].__deserialize__(db)
|
154
|
+
end
|
155
|
+
|
156
|
+
def write_object(key, value)
|
157
|
+
@store[key] = value.__serialize__
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class Object
|
163
|
+
def __serialize__(transaction = ODB::Transaction.current)
|
164
|
+
obj = {:class => self.class, :ivars => {}}
|
165
|
+
instance_variables.each do |ivar|
|
166
|
+
subobj = instance_variable_get(ivar)
|
167
|
+
transaction.objects << subobj unless Fixnum === subobj
|
168
|
+
obj[:ivars][ivar[1..-1]] = Fixnum === subobj ? subobj.__serialize__ : subobj.object_id
|
169
|
+
end
|
170
|
+
obj
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class String
|
175
|
+
def __serialize__(transaction = nil)
|
176
|
+
super().update(:value => self)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class Array
|
181
|
+
def __serialize__(transaction = ODB::Transaction.current)
|
182
|
+
obj = super
|
183
|
+
obj[:type] = :array
|
184
|
+
obj[:items] = map do |item|
|
185
|
+
transaction.objects << item
|
186
|
+
item.object_id
|
187
|
+
end
|
188
|
+
obj
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class Fixnum
|
193
|
+
def __serialize__(transaction = nil)
|
194
|
+
{:class => Fixnum, :value => self}
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class Float
|
199
|
+
def __serialize__(transaction = nil)
|
200
|
+
super().update(:value => self)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class Hash
|
205
|
+
def __serialize__(transaction = ODB::Transaction.current)
|
206
|
+
obj = super
|
207
|
+
obj[:type] = :hash
|
208
|
+
obj[:items] = map do |k, v|
|
209
|
+
transaction.objects.push(k.object_id, v.object_id)
|
210
|
+
[k.object_id, v.object_id]
|
211
|
+
end
|
212
|
+
obj
|
213
|
+
end
|
214
|
+
|
215
|
+
def __deserialize__(db = ODB::Database.current)
|
216
|
+
object = self[:value] ? self[:value] : self[:class].allocate
|
217
|
+
self[:ivars].each do |ivar, value|
|
218
|
+
object.instance_variable_set("@#{ivar}", Hash === value ? value[:value] : db.store[value])
|
219
|
+
end
|
220
|
+
case self[:type]
|
221
|
+
when :array
|
222
|
+
object.replace(self[:list].map {|item| db.store[item] })
|
223
|
+
when :hash
|
224
|
+
self[:list].each do |values|
|
225
|
+
object[db.store[values[0]]] = object[db.store[values[1]]]
|
226
|
+
end
|
227
|
+
end
|
228
|
+
object
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
class NilClass
|
233
|
+
def __deserialize__(db = nil)
|
234
|
+
nil
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../lib/odb"
|
2
|
+
|
3
|
+
class Post
|
4
|
+
include ODB::Persistent
|
5
|
+
attr_accessor :title, :author, :comment
|
6
|
+
end
|
7
|
+
|
8
|
+
class Comment
|
9
|
+
def initialize
|
10
|
+
@name = 1
|
11
|
+
@value = 2.5
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ODB::Persistent do
|
16
|
+
it "should save a post" do
|
17
|
+
db = ODB.new
|
18
|
+
post = Post.new.tap {|p| p.title = "x"; p.author = "Joe"; p.comment = Comment.new }
|
19
|
+
db.transaction do
|
20
|
+
db.store[:post] = post
|
21
|
+
db.store[:comment] = post.comment
|
22
|
+
end
|
23
|
+
|
24
|
+
db.store[:post].should == post
|
25
|
+
db.store[:post].comment.object_id.should == db.store[:comment].object_id
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: odb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Loren Segal
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-25 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: lsegal@soen.ca
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/odb.rb
|
26
|
+
- spec/persistence_spec.rb
|
27
|
+
- LICENSE
|
28
|
+
- README.rdoc
|
29
|
+
- Rakefile
|
30
|
+
has_rdoc: yard
|
31
|
+
homepage: http://gnuu.org
|
32
|
+
licenses: []
|
33
|
+
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
requirements: []
|
52
|
+
|
53
|
+
rubyforge_project: odb
|
54
|
+
rubygems_version: 1.3.5
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: An Object Oriented Database for Ruby
|
58
|
+
test_files: []
|
59
|
+
|