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.
Files changed (6) hide show
  1. data/LICENSE +22 -0
  2. data/README.rdoc +5 -0
  3. data/Rakefile +52 -0
  4. data/lib/odb.rb +236 -0
  5. data/spec/persistence_spec.rb +28 -0
  6. 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.
@@ -0,0 +1,5 @@
1
+ = ODB
2
+
3
+ A Ruby Object Database
4
+
5
+ Loren Segal © 2009
@@ -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
@@ -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
+