ribs 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README +54 -24
- data/Rakefile +1 -1
- data/lib/ribs.jar +0 -0
- data/lib/ribs.rb +32 -11
- data/lib/ribs/db.rb +10 -10
- data/lib/ribs/definition.rb +115 -151
- data/lib/ribs/{session.rb → handle.rb} +30 -24
- data/lib/ribs/repository.rb +243 -0
- data/lib/ribs/rib.rb +100 -0
- data/test/artist_spec.rb +22 -20
- data/test/define_accessors_spec.rb +27 -0
- data/test/define_model_spec.rb +41 -0
- data/test/identity_map_spec.rb +63 -0
- data/test/repository_spec.rb +79 -0
- data/test/rib_spec.rb +73 -0
- data/test/simple_select_spec.rb +22 -22
- data/test/test_helper.rb +89 -8
- data/test/track_spec.rb +90 -78
- metadata +10 -4
- data/lib/ribs/meat.rb +0 -33
@@ -1,26 +1,26 @@
|
|
1
1
|
module Ribs
|
2
|
-
# A Ribs
|
3
|
-
# there are no more Ribs
|
2
|
+
# A Ribs handle maps many-to-one with a Hibernate session. When
|
3
|
+
# there are no more Ribs handles left, the Hibernate session will
|
4
4
|
# be released too.
|
5
5
|
#
|
6
|
-
# The methods in
|
6
|
+
# The methods in Handle is not to be used outside the framework in
|
7
7
|
# most cases, since they are internal, brittle, not based on
|
8
8
|
# Hibernate in all cases, and very much subject to change. Currently
|
9
9
|
# they provide capabilities that aren't part of the framework yet,
|
10
10
|
# such as migrations and setting up test data.
|
11
11
|
#
|
12
|
-
class
|
13
|
-
# This error is thrown when an operation on a
|
12
|
+
class Handle
|
13
|
+
# This error is thrown when an operation on a handle is
|
14
14
|
# attempted but the internal Hibernate session has already
|
15
15
|
# been closed.
|
16
16
|
#
|
17
17
|
class NotConnectedError < StandardError;end
|
18
18
|
|
19
19
|
class << self
|
20
|
-
# Returns a
|
20
|
+
# Returns a handle object from the database +from+. The
|
21
21
|
# available values for from is either one of the existing
|
22
22
|
# defined database names, or <tt>:default</tt>, which will give
|
23
|
-
# a
|
23
|
+
# a handle to the default database.
|
24
24
|
#
|
25
25
|
def get(from = :default)
|
26
26
|
db = case from
|
@@ -31,14 +31,14 @@ module Ribs
|
|
31
31
|
else
|
32
32
|
Ribs::DB::get(from)
|
33
33
|
end
|
34
|
-
db.
|
34
|
+
db.handle
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
# The current database for this
|
38
|
+
# The current database for this handle
|
39
39
|
attr_reader :db
|
40
40
|
|
41
|
-
# Creates a new
|
41
|
+
# Creates a new handle that points to the provided +db+ and
|
42
42
|
# +hibernate_session+
|
43
43
|
#
|
44
44
|
def initialize(db, hibernate_session)
|
@@ -47,7 +47,7 @@ module Ribs
|
|
47
47
|
@hibernate_session = hibernate_session
|
48
48
|
end
|
49
49
|
|
50
|
-
# Releases this
|
50
|
+
# Releases this handle from the database. This will possibly
|
51
51
|
# result in closing the internal Hibernate session, if this is the
|
52
52
|
# last Ribs session pointing to that Hibernate session.
|
53
53
|
def release
|
@@ -62,29 +62,35 @@ module Ribs
|
|
62
62
|
end
|
63
63
|
|
64
64
|
# LOW LEVEL - shouldn't be used except by Ribs
|
65
|
-
def
|
65
|
+
def get(entity_name, id) # :nodoc:
|
66
66
|
chk_conn
|
67
|
-
|
68
|
-
@hibernate_session.create_criteria(entity_name).list.to_a
|
69
|
-
else
|
70
|
-
@hibernate_session.get(entity_name, java.lang.Integer.new(id))
|
71
|
-
end
|
67
|
+
@hibernate_session.get(entity_name, java.lang.Integer.new(id))
|
72
68
|
end
|
73
69
|
|
74
70
|
# LOW LEVEL - shouldn't be used except by Ribs
|
75
|
-
def
|
71
|
+
def all(entity_name) # :nodoc:
|
72
|
+
chk_conn
|
73
|
+
@hibernate_session.create_criteria(entity_name).list.to_a
|
74
|
+
end
|
75
|
+
|
76
|
+
# LOW LEVEL - shouldn't be used except by Ribs
|
77
|
+
def update_obj(obj) # :nodoc:
|
76
78
|
chk_conn
|
77
79
|
tx = @hibernate_session.begin_transaction
|
78
|
-
|
79
|
-
@hibernate_session.update(obj)
|
80
|
-
else
|
81
|
-
@hibernate_session.save(obj)
|
82
|
-
obj.__ribs_meat.persistent = true
|
83
|
-
end
|
80
|
+
@hibernate_session.update(obj)
|
84
81
|
tx.commit
|
85
82
|
obj
|
86
83
|
end
|
87
84
|
|
85
|
+
# LOW LEVEL - shouldn't be used except by Ribs
|
86
|
+
def insert_obj(obj) # :nodoc:
|
87
|
+
chk_conn
|
88
|
+
tx = @hibernate_session.begin_transaction
|
89
|
+
@hibernate_session.save(obj)
|
90
|
+
tx.commit
|
91
|
+
obj
|
92
|
+
end
|
93
|
+
|
88
94
|
# LOW LEVEL - shouldn't be used except by Ribs
|
89
95
|
def delete(obj) # :nodoc:
|
90
96
|
chk_conn
|
@@ -0,0 +1,243 @@
|
|
1
|
+
module Ribs
|
2
|
+
# Gets a Repository object for the object in question. If the object
|
3
|
+
# is a class, the repository returned will be a Repository::Class,
|
4
|
+
# otherwise it will be a Repository::Instance. Specifically, what
|
5
|
+
# you will get for the class FooBar will be the class
|
6
|
+
# Ribs::Repository::DB_default::FooBar and you will get an instance
|
7
|
+
# of that class if the object is an instance of FooBar. This allows
|
8
|
+
# you to add specific functionality to these repositories. The
|
9
|
+
# class Ribs::Repository::DB_default::FooBar will also include
|
10
|
+
# Ribs::Repository::FooBar and extend
|
11
|
+
# Ribs::Repository::FooBar::ClassMethods so you can add behavior to
|
12
|
+
# these that map over all databases.
|
13
|
+
def self.Repository(obj, db = :default)
|
14
|
+
db_name = "DB_#{db}"
|
15
|
+
model_type = case obj
|
16
|
+
when Class
|
17
|
+
obj
|
18
|
+
else
|
19
|
+
obj.class
|
20
|
+
end
|
21
|
+
|
22
|
+
dbmod = Ribs::Repository::const_get(db_name)
|
23
|
+
name = model_type.name.split(/::/).join("_")
|
24
|
+
|
25
|
+
if !dbmod.constants.include?(name)
|
26
|
+
Repository::create_repository(name, dbmod)
|
27
|
+
end
|
28
|
+
|
29
|
+
cls = dbmod::const_get(name.to_sym)
|
30
|
+
Ribs::Repository.ensure_repository(name, cls, model_type, db)
|
31
|
+
|
32
|
+
return cls if obj.kind_of?(Class)
|
33
|
+
|
34
|
+
ret = cls.allocate
|
35
|
+
ret.send :initialize
|
36
|
+
ret.instance_variable_set :@database, db
|
37
|
+
ret.instance_variable_set :@model, obj
|
38
|
+
ret
|
39
|
+
end
|
40
|
+
|
41
|
+
# A Repository is the main gateway into all functionality in
|
42
|
+
# Ribs. This is where you send your objects to live in the DB, etc.
|
43
|
+
#
|
44
|
+
# A Repository is a combination implementation of both Data Mapper and Repository.
|
45
|
+
module Repository
|
46
|
+
# The ClassMethods are everything that's available when getting
|
47
|
+
# the repository for a specific model class. It includes most of
|
48
|
+
# the things you'd expect to do on the class itself in
|
49
|
+
# ActiveRecord - stuff like finders, creators and things like
|
50
|
+
# that.
|
51
|
+
module ClassMethods
|
52
|
+
include Repository
|
53
|
+
|
54
|
+
# Get the meta data for this model
|
55
|
+
def metadata
|
56
|
+
@metadata
|
57
|
+
end
|
58
|
+
|
59
|
+
# Define accessors for this model
|
60
|
+
def define_accessors
|
61
|
+
self.metadata.properties_and_identity.each do |name, _|
|
62
|
+
self.model.send :attr_accessor, name.downcase
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Makes a specific instance of this class be marked persistent
|
67
|
+
def persistent(obj)
|
68
|
+
(@persistent ||= {})[obj.object_id] = true
|
69
|
+
end
|
70
|
+
|
71
|
+
# Checks if a specific instance is marked as persistent
|
72
|
+
def persistent?(obj)
|
73
|
+
@persistent && @persistent[obj.object_id]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Makes a specific instance of this class be marked destroyed
|
77
|
+
def destroyed(obj)
|
78
|
+
(@destroyed ||= {})[obj.object_id] = true
|
79
|
+
end
|
80
|
+
|
81
|
+
# Checks if a specific instance is marked as destroyed
|
82
|
+
def destroyed?(obj)
|
83
|
+
@destroyed && @destroyed[obj.object_id]
|
84
|
+
end
|
85
|
+
|
86
|
+
# Create a new instance of this model object, optionally setting
|
87
|
+
# properties based on +attrs+.
|
88
|
+
def new(attrs = {})
|
89
|
+
obj = self.model.new
|
90
|
+
attrs.each do |k,v|
|
91
|
+
obj.send("#{k}=", v)
|
92
|
+
end
|
93
|
+
obj
|
94
|
+
end
|
95
|
+
|
96
|
+
# First creates a model object based on the values in +attrs+ and
|
97
|
+
# then saves this to the database directly.
|
98
|
+
def create(attrs = {})
|
99
|
+
val = new(attrs)
|
100
|
+
R(val, self.database).save
|
101
|
+
val
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns all instances for the current model
|
105
|
+
def all
|
106
|
+
Ribs.with_handle(self.database) do |h|
|
107
|
+
h.all(self.metadata.persistent_class.entity_name)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Will get the instance with +id+ or return nil if no such entity
|
112
|
+
# exists.
|
113
|
+
def get(id)
|
114
|
+
Ribs.with_handle(self.database) do |h|
|
115
|
+
h.get(self.metadata.persistent_class.entity_name, id)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Destroys the model with the id +id+.
|
120
|
+
def destroy(id)
|
121
|
+
Ribs.with_handle(self.database) do |h|
|
122
|
+
h.delete(get(id))
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# InstanceMethods are those that become available when you the
|
128
|
+
# repository for a specific instance. This allows things like
|
129
|
+
# saving and destroying a specific instance.
|
130
|
+
module InstanceMethods
|
131
|
+
include Repository
|
132
|
+
|
133
|
+
# Get the meta data for this model
|
134
|
+
def metadata
|
135
|
+
self.class.metadata
|
136
|
+
end
|
137
|
+
|
138
|
+
# Removes this instance from the database.
|
139
|
+
def destroy!
|
140
|
+
self.class.destroyed(self.model)
|
141
|
+
Ribs.with_handle(self.database) do |h|
|
142
|
+
h.delete(self.model)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Saves this instance to the database. If the instance already
|
147
|
+
# exists, it will update the database, otherwise it will create
|
148
|
+
# it.
|
149
|
+
def save
|
150
|
+
Ribs.with_handle(self.database) do |h|
|
151
|
+
if self.class.persistent?(self.model)
|
152
|
+
h.update_obj(self.model)
|
153
|
+
else
|
154
|
+
h.insert_obj(self.model)
|
155
|
+
self.class.persistent(self.model)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
self.model
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Every repository is tied to a specific database
|
163
|
+
attr_reader :database
|
164
|
+
# Every repository is tied to a specific model. This is either a
|
165
|
+
# class or an instance of a class
|
166
|
+
attr_reader :model
|
167
|
+
|
168
|
+
class << self
|
169
|
+
# Makes sure the class sent in is actually a repostiroy. It
|
170
|
+
# checks all invariants for a Repository and makes them true if
|
171
|
+
# they aren't already.
|
172
|
+
def ensure_repository(name, cls, real, db)
|
173
|
+
unless cls.kind_of?(Repository)
|
174
|
+
mod1 = if Repository.constants.include?(name)
|
175
|
+
Repository.const_get(name)
|
176
|
+
else
|
177
|
+
mod = Module.new
|
178
|
+
Repository.const_set(name, mod)
|
179
|
+
mod
|
180
|
+
end
|
181
|
+
|
182
|
+
unless mod1.kind_of?(Repository)
|
183
|
+
mod1.send :include, Repository::InstanceMethods
|
184
|
+
end
|
185
|
+
|
186
|
+
unless mod1.constants.include?("ClassMethods")
|
187
|
+
mod1.const_set(:ClassMethods, Module.new)
|
188
|
+
end
|
189
|
+
|
190
|
+
cls.send :include, mod1
|
191
|
+
cls.send :extend, mod1.const_get(:ClassMethods)
|
192
|
+
cls.send :extend, Repository::ClassMethods
|
193
|
+
cls.instance_variable_set :@database, db
|
194
|
+
cls.instance_variable_set :@model, real
|
195
|
+
cls.instance_variable_set :@metadata, Ribs::metadata_for(db, real, cls)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Create a repository for a model inside a database
|
200
|
+
def create_repository(name, dbmod)
|
201
|
+
c = Class.new
|
202
|
+
mod1 = if Repository.constants.include?(name)
|
203
|
+
Repository.const_get(name)
|
204
|
+
else
|
205
|
+
mod = Module.new
|
206
|
+
Repository.const_set(name, mod)
|
207
|
+
mod
|
208
|
+
end
|
209
|
+
dbmod.const_set name, c
|
210
|
+
end
|
211
|
+
|
212
|
+
# Dynamically create new database modules
|
213
|
+
def const_missing(name)
|
214
|
+
if /^DB_(.*?)$/ =~ name.to_s
|
215
|
+
db_name = $1
|
216
|
+
mod = Module.new
|
217
|
+
mod.instance_variable_set :@database_name, db_name.to_sym
|
218
|
+
const_set name, mod
|
219
|
+
mod
|
220
|
+
else
|
221
|
+
super
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
module Kernel
|
229
|
+
# Gets a Repository object for the object in question. If the object
|
230
|
+
# is a class, the repository returned will be a Repository::Class,
|
231
|
+
# otherwise it will be a Repository::Instance. Specifically, what
|
232
|
+
# you will get for the class FooBar will be the class
|
233
|
+
# Ribs::Repository::DB_default::FooBar and you will get an instance
|
234
|
+
# of that class if the object is an instance of FooBar. This allows
|
235
|
+
# you to add specific functionality to these repositories. The
|
236
|
+
# class Ribs::Repository::DB_default::FooBar will also include
|
237
|
+
# Ribs::Repository::FooBar and extend
|
238
|
+
# Ribs::Repository::FooBar::ClassMethods so you can add behavior to
|
239
|
+
# these that map over all databases.
|
240
|
+
def R(obj, db=:default)
|
241
|
+
Ribs::Repository(obj, db)
|
242
|
+
end
|
243
|
+
end
|
data/lib/ribs/rib.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
module Ribs
|
2
|
+
# Contains the mapping data that gets created when calling the
|
3
|
+
# {Ribs!} method.
|
4
|
+
class Rib
|
5
|
+
# The proxy object returned when a property name is used in a call
|
6
|
+
# to Rib without any parameters.
|
7
|
+
class RibColumn
|
8
|
+
# Sets the name and all the reference values
|
9
|
+
def initialize(name, columns, primary_keys, to_avoid, default_values)
|
10
|
+
@name, @columns, @primary_keys, @to_avoid, @default_values =
|
11
|
+
name.to_s, columns, primary_keys, to_avoid, default_values
|
12
|
+
end
|
13
|
+
|
14
|
+
# This name is a primary key
|
15
|
+
def primary_key!
|
16
|
+
@primary_keys << @name
|
17
|
+
end
|
18
|
+
|
19
|
+
# This name should be avoided in the database
|
20
|
+
def avoid!
|
21
|
+
@to_avoid << @name.downcase
|
22
|
+
end
|
23
|
+
|
24
|
+
# This name is mapped to a specific column
|
25
|
+
def column=(col)
|
26
|
+
@columns[@name] = [col.to_s, {}]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Value object that contains references to the Rib data
|
31
|
+
class ColumnData
|
32
|
+
# List of all the columns defined
|
33
|
+
attr_reader :columns
|
34
|
+
# List of all primary keys defined
|
35
|
+
attr_reader :primary_keys
|
36
|
+
# List of all columns to avoid
|
37
|
+
attr_reader :to_avoid
|
38
|
+
# List of default values for columns
|
39
|
+
attr_reader :default_values
|
40
|
+
|
41
|
+
# Sets all values
|
42
|
+
def initialize(columns, primary_keys, to_avoid, default_values)
|
43
|
+
@columns, @primary_keys, @to_avoid, @default_values = columns, primary_keys, to_avoid, default_values
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# These are the only methods left alone - this becomes a blank
|
48
|
+
# slate, mostly.
|
49
|
+
METHODS_TO_LEAVE_ALONE = ['__id__', '__send__']
|
50
|
+
undef_method *(instance_methods - METHODS_TO_LEAVE_ALONE)
|
51
|
+
|
52
|
+
# Create all value parts
|
53
|
+
def initialize
|
54
|
+
@columns = { }
|
55
|
+
@primary_keys = []
|
56
|
+
@to_avoid = []
|
57
|
+
@default_values = { }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns a reference object that allow access to the resulting
|
61
|
+
# data objects
|
62
|
+
def __column_data__
|
63
|
+
ColumnData.new(@columns, @primary_keys, @to_avoid, @default_values)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Handles property names. The only ones that aren't possibly to
|
67
|
+
# use is "initialize", "__column_data__", "__id__",
|
68
|
+
# "method_missing" and "__send__". Everything else is
|
69
|
+
# kosher. ... Maybe there should be a way of getting those last
|
70
|
+
# ones too...
|
71
|
+
#
|
72
|
+
# If no arguments are supplied, will return a RibColumn
|
73
|
+
def method_missing(name, *args, &block)
|
74
|
+
if args.empty?
|
75
|
+
RibColumn.new(name, @columns, @primary_keys, @to_avoid, @default_values)
|
76
|
+
else
|
77
|
+
hsh = args.grep(Hash).first || {}
|
78
|
+
args.grep(Symbol).each do |ss|
|
79
|
+
hsh[ss] = true
|
80
|
+
end
|
81
|
+
|
82
|
+
hsh = {:column => name}.merge(hsh)
|
83
|
+
|
84
|
+
if hsh[:primary_key]
|
85
|
+
@primary_keys << name.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
if hsh[:avoid]
|
89
|
+
@to_avoid << name.to_s.downcase
|
90
|
+
if hsh[:default]
|
91
|
+
@default_values[name.to_s.downcase] = hsh[:default]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
@columns[name.to_s] = [hsh[:column].to_s, hsh]
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|