odb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+