ribs 0.0.1 → 0.0.2
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/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
|