Rubernate 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +30 -0
- data/db/mysql.sql +35 -0
- data/db/oracle.sql +29 -0
- data/lib/rubernate/callbacks.rb +70 -0
- data/lib/rubernate/entity.rb +85 -0
- data/lib/rubernate/impl/dbigeneric.rb +286 -0
- data/lib/rubernate/impl/dbimysql.rb +28 -0
- data/lib/rubernate/impl/dbioracle.rb +29 -0
- data/lib/rubernate/impl/memory.rb +147 -0
- data/lib/rubernate/mixins.rb +91 -0
- data/lib/rubernate/peer.rb +70 -0
- data/lib/rubernate/queries.rb +444 -0
- data/lib/rubernate/runtime.rb +215 -0
- data/lib/rubernate.rb +127 -0
- data/tests/README +16 -0
- data/tests/all_tests.rb +12 -0
- data/tests/config.rb +34 -0
- data/tests/rubernate/callbacks_test.rb +120 -0
- data/tests/rubernate/fixtures.rb +27 -0
- data/tests/rubernate/impl/dbigeneric_stub.rb +635 -0
- data/tests/rubernate/impl/dbimysql_test.rb +19 -0
- data/tests/rubernate/impl/dbioracle_test.rb +19 -0
- data/tests/rubernate/impl/memory_test.rb +188 -0
- data/tests/rubernate/queries_test.rb +176 -0
- data/tests/rubernate/rubernate_test.rb +326 -0
- data/tests/rubernate/utils_test.rb +42 -0
- metadata +74 -0
@@ -0,0 +1,444 @@
|
|
1
|
+
# Contains users methods for building queries
|
2
|
+
module Rubernate
|
3
|
+
module Queries
|
4
|
+
# Log instance for Queries
|
5
|
+
Log = Log4r::Logger.new self.name
|
6
|
+
|
7
|
+
# Re-Expr express prefix that starts all queries on RQL.
|
8
|
+
RQL_PREFIX_REGEXP = /^\s*Select\s+:/
|
9
|
+
|
10
|
+
# Contains operations definitions
|
11
|
+
module Operations
|
12
|
+
# Declares +And+ sql clause.
|
13
|
+
def And expr1, expr2
|
14
|
+
@factory.bin_op expr1, expr2, 'and'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Declares +Or+ sql clause.
|
18
|
+
def Or expr1, expr2
|
19
|
+
@factory.bin_op expr1, expr2, 'or', true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Declares +=+ sql clause.
|
23
|
+
def Eq expr1, expr2
|
24
|
+
@factory.bin_op expr1, expr2, '='
|
25
|
+
end
|
26
|
+
|
27
|
+
# Declares +Not+ sql clause.
|
28
|
+
def Not expr
|
29
|
+
@factory.un_op expr, 'not', true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Declares +is null+ sql clause
|
33
|
+
def IsNil expr
|
34
|
+
@factory.bin_op expr, @factory.expr(nil), 'is'
|
35
|
+
end
|
36
|
+
|
37
|
+
# Declares +is not null+ sql clause
|
38
|
+
def IsNotNil expr
|
39
|
+
@factory.bin_op expr, @factory.expr(nil), 'is not'
|
40
|
+
end
|
41
|
+
|
42
|
+
# Declares +in+ sql clause
|
43
|
+
def In expr, list
|
44
|
+
@factory.bin_op expr, @factory.list(list), 'in'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Factory method, creates queries based on Generic factory.
|
49
|
+
def self.query q_text=nil, &q_block
|
50
|
+
if block_given?
|
51
|
+
@@generic_factory.query(&q_block)
|
52
|
+
else
|
53
|
+
@@generic_factory.query q_text
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Defines query elements factory. By default it creates elements defined
|
58
|
+
# in module +Generic+, but this befavior can be changed by seting of appropriate class.
|
59
|
+
# Following example changes implementation of BinOpConst
|
60
|
+
#
|
61
|
+
# :call-seq:
|
62
|
+
# f = Factory.new
|
63
|
+
# f.bin_op = MyBinOpImpl
|
64
|
+
# f.bin_op expr1, expr2, '=' -> instance of MyBinOpImpl properly initialized
|
65
|
+
#
|
66
|
+
# New implementations of elements MUST accept factory as it's first parameter.
|
67
|
+
class Factory
|
68
|
+
# Initalizes default implementations
|
69
|
+
def initialize
|
70
|
+
@expr = Generic::Expr
|
71
|
+
@un_op = Generic::UnOpConstr
|
72
|
+
@bin_op = Generic::BinOpConstr
|
73
|
+
@field = Generic::FieldExpr
|
74
|
+
@key_ref = Generic::KeyRefExpr
|
75
|
+
@list = Generic::ExprsList
|
76
|
+
@r_param = Generic::RParam
|
77
|
+
@r_object = Generic::RObject
|
78
|
+
@query = Generic::Query
|
79
|
+
end
|
80
|
+
def query query=nil, &block
|
81
|
+
@query.new self, query, &block
|
82
|
+
end
|
83
|
+
private
|
84
|
+
# Defines factory method that will instantiate element instance
|
85
|
+
# and pass +self+ as element factory.
|
86
|
+
def self.def_factory name
|
87
|
+
module_eval %{
|
88
|
+
attr_writer :#{name}
|
89
|
+
def #{name} *params
|
90
|
+
@#{name}.new self, *params
|
91
|
+
end
|
92
|
+
}
|
93
|
+
end
|
94
|
+
# The following factory methdos are available.
|
95
|
+
for p in %w{expr un_op bin_op field key_ref list r_param r_object}
|
96
|
+
def_factory p
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Contains classes for standart ANSY SQL.
|
101
|
+
module Generic
|
102
|
+
# Represent abstract expression
|
103
|
+
class Expr
|
104
|
+
include Operations
|
105
|
+
attr_reader :r_params, :markers
|
106
|
+
|
107
|
+
def initialize factory, value = nil
|
108
|
+
@factory, @value, @r_params, @markers = factory, value, [], []
|
109
|
+
@markers << @value if @value.is_a? Symbol
|
110
|
+
end
|
111
|
+
|
112
|
+
# Generates SQL for expression
|
113
|
+
def to_sql
|
114
|
+
case @value
|
115
|
+
when Symbol: '?'
|
116
|
+
when Integer: @value
|
117
|
+
when nil: 'null'
|
118
|
+
else "'#{@value.to_s}'"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
private
|
122
|
+
def fit_type expr
|
123
|
+
case expr
|
124
|
+
when RObject: expr.pk
|
125
|
+
when RParam: expr.ref
|
126
|
+
when Expr: expr
|
127
|
+
else @factory.expr expr
|
128
|
+
end
|
129
|
+
end
|
130
|
+
def self.def_bin_op ruby_op, sql_op
|
131
|
+
module_eval %{
|
132
|
+
def #{ruby_op} (other) @factory.bin_op self, other, '#{sql_op}'; end
|
133
|
+
}
|
134
|
+
end
|
135
|
+
for op in [['==', '='], ['=~', '<>'], '<', '>', '<=', '>=']
|
136
|
+
if op.is_a? Array
|
137
|
+
def_bin_op(*op)
|
138
|
+
else
|
139
|
+
def_bin_op op, op
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Represents constraint for one columns.
|
145
|
+
class UnOpConstr < Expr
|
146
|
+
def initialize factory, expr, op, braces = false
|
147
|
+
@factory, @op, @braces = factory, op, braces
|
148
|
+
@expr = fit_type expr
|
149
|
+
end
|
150
|
+
|
151
|
+
def r_params
|
152
|
+
@expr.r_params
|
153
|
+
end
|
154
|
+
|
155
|
+
def markers
|
156
|
+
@expr.markers
|
157
|
+
end
|
158
|
+
|
159
|
+
def to_sql
|
160
|
+
if @braces
|
161
|
+
"#{@op} (#{@expr.to_sql})"
|
162
|
+
else
|
163
|
+
"#{@op} #{@expr.to_sql}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Represent constraint that applied on two columns.
|
169
|
+
class BinOpConstr < Expr
|
170
|
+
def initialize factory, expr1, expr2, sign, braces = false
|
171
|
+
@factory, @sign, @braces = factory, sign, braces
|
172
|
+
@expr1, @expr2 = fit_type(expr1), fit_type(expr2)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns r_params used in both expressions
|
176
|
+
def r_params
|
177
|
+
@expr1.r_params + @expr2.r_params
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns markers used in both expressions
|
181
|
+
def markers
|
182
|
+
@expr1.markers + @expr2.markers
|
183
|
+
end
|
184
|
+
|
185
|
+
# Generates SQL for constraint
|
186
|
+
def to_sql
|
187
|
+
if @braces
|
188
|
+
"(#{@expr1.to_sql} #{@sign} #{@expr2.to_sql})"
|
189
|
+
else
|
190
|
+
"#{@expr1.to_sql} #{@sign} #{@expr2.to_sql}"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Represent expression with table's field
|
196
|
+
class FieldExpr < Expr
|
197
|
+
def initialize factory, table, field
|
198
|
+
@factory, @table, @field, @markers = factory, table, field, []
|
199
|
+
@r_params = table.is_a?(RParam) ? [table] : []
|
200
|
+
end
|
201
|
+
# Creates constraint that check if this field is nil
|
202
|
+
def is_nil
|
203
|
+
IsNil self
|
204
|
+
end
|
205
|
+
# Creates constraint that check if this field is not nil
|
206
|
+
def is_not_nil
|
207
|
+
IsNotNil self
|
208
|
+
end
|
209
|
+
|
210
|
+
def to_sql
|
211
|
+
@table.to_sql + '.' + @field
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Represent +r_params+ for hashes and arrays constrained by key
|
216
|
+
class KeyRefExpr < BinOpConstr
|
217
|
+
def initialize factory, r_param, key_field, key_value
|
218
|
+
@factory, @r_param, @key_field, @key_value = factory, r_param, key_field, key_value
|
219
|
+
super factory, key_field, key_value, '='
|
220
|
+
end
|
221
|
+
def == expr
|
222
|
+
And self, Eq(@r_param.ref, expr)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Reresents List of expressions
|
227
|
+
class ExprsList < Expr
|
228
|
+
def initialize factory, list
|
229
|
+
@factory, @exprs = factory, list.collect{|expr| Expr === expr ? expr : factory.expr(expr)}
|
230
|
+
end
|
231
|
+
def to_sql
|
232
|
+
'(' + @exprs.collect{|expr| expr.to_sql}.join(', ') + ')'
|
233
|
+
end
|
234
|
+
def markers
|
235
|
+
@exprs.inject([]) {|res, expr| res.concat expr.markers}
|
236
|
+
end
|
237
|
+
def r_params
|
238
|
+
@exprs.inject([]) {|res, expr| res.concat expr.r_params}
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Represents r_params table
|
243
|
+
class RParam < Expr
|
244
|
+
include Rubernate::DBI
|
245
|
+
|
246
|
+
attr_reader :r_object, :name
|
247
|
+
|
248
|
+
# Init Param accepts table and name of param
|
249
|
+
def initialize factory, r_object, name
|
250
|
+
@factory, @r_object, @name, @r_params = factory, r_object, name, [self]
|
251
|
+
end
|
252
|
+
|
253
|
+
# Returns full param table name with object tables prefix
|
254
|
+
def to_sql
|
255
|
+
@r_object.to_sql + @name
|
256
|
+
end
|
257
|
+
|
258
|
+
# Fields accessors.
|
259
|
+
def pk () f_expr 'object_pk'; end
|
260
|
+
def int () f_expr 'int_value'; end
|
261
|
+
def str () f_expr 'str_value'; end
|
262
|
+
def time () f_expr 'dat_value'; end
|
263
|
+
def date () f_expr 'dat_value'; end # TODO: make proper convertation
|
264
|
+
def ref () f_expr 'ref_value'; end
|
265
|
+
def flags() f_expr 'flags'; end
|
266
|
+
|
267
|
+
# The following methods checks +r_param.flags+ value. (r_param.flags)
|
268
|
+
def is_int () Eq flags, PARAM_FLAG_INT; end
|
269
|
+
def is_str () Eq flags, PARAM_FLAG_STRING; end
|
270
|
+
def is_time() Eq flags, PARAM_FLAG_TIME; end
|
271
|
+
def is_ref () Eq flags, PARAM_FLAG_REF; end
|
272
|
+
|
273
|
+
# Shortcut for +Eq+ method
|
274
|
+
def == expr
|
275
|
+
expr.is_a?(RObject) ?
|
276
|
+
@factory.bin_op(ref, expr.pk, '=') :
|
277
|
+
@factory.bin_op(ref, expr, '=')
|
278
|
+
end
|
279
|
+
|
280
|
+
# Shortcut for arrays and hashes
|
281
|
+
def [] key
|
282
|
+
case key
|
283
|
+
when Integer: key_ref int, key
|
284
|
+
when String: key_ref str, key
|
285
|
+
when Time: key_ref dat, key
|
286
|
+
when Symbol: key_ref int, key
|
287
|
+
else raise "invalid key value #{key}"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
private
|
291
|
+
# Creates +FieldExpr+ witch field of this (r_param) table
|
292
|
+
def f_expr field
|
293
|
+
@factory.field self, field
|
294
|
+
end
|
295
|
+
def key_ref key_field, key_value
|
296
|
+
@factory.key_ref self, key_field, key_value
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Represent r_objects table
|
301
|
+
class RObject < Expr
|
302
|
+
# Init Table accepts query and name of table
|
303
|
+
def initialize factory, name
|
304
|
+
@factory, @to_sql, @r_params = factory, name.to_s + '_', {}
|
305
|
+
@pk, @klass = @factory.field(self, 'object_pk'), @factory.field(self, 'object_class')
|
306
|
+
end
|
307
|
+
|
308
|
+
# Fiends access expressions
|
309
|
+
attr_reader :pk, :klass, :to_sql
|
310
|
+
|
311
|
+
# Creates subclasses constraint
|
312
|
+
def derived klass
|
313
|
+
if klass.subclasses and not klass.subclasses.empty?
|
314
|
+
In self.klass, [klass].concat(klass.subclasses)
|
315
|
+
else
|
316
|
+
Eq self.klass, klass
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Tracks missing methods and creates accessor for params.
|
321
|
+
def method_missing name, *params
|
322
|
+
return super if params.size != 0
|
323
|
+
def_param name
|
324
|
+
end
|
325
|
+
|
326
|
+
private
|
327
|
+
# Defines accessor for new param. (joins r_param to r_objects)
|
328
|
+
def def_param name
|
329
|
+
instance_eval "def #{name}() @r_params[:#{name}]; end"
|
330
|
+
@r_params[name] = @factory.r_param self, name.to_s
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# Represents context in which query building executes
|
335
|
+
# Holds constraints tables and so on.
|
336
|
+
class Query < Expr
|
337
|
+
include Operations
|
338
|
+
|
339
|
+
# Accepts query as string or as block and executes it.
|
340
|
+
def initialize factory, query = nil, &block
|
341
|
+
@factory, @r_objects, @exprs, @order, @query = factory, {}, [], [], query
|
342
|
+
@query = block if block_given?
|
343
|
+
end
|
344
|
+
|
345
|
+
# Next section contains methods available during query construction.
|
346
|
+
# Declares objects for selection. The first argument will be result object.
|
347
|
+
def Select main, *tables
|
348
|
+
for t in [main, *tables].flatten
|
349
|
+
@r_objects[t] = @factory.r_object t.to_s
|
350
|
+
instance_eval "def #{t}() @r_objects[:#{t}]; end"
|
351
|
+
end
|
352
|
+
@main = @r_objects[main]
|
353
|
+
end
|
354
|
+
|
355
|
+
# Declares +Where+ clause.
|
356
|
+
def Where *exprs
|
357
|
+
@exprs = exprs
|
358
|
+
end
|
359
|
+
|
360
|
+
# Declares +Order By+ clause.
|
361
|
+
def OrderBy expr, *exprs
|
362
|
+
@order << expr
|
363
|
+
@order.concat exprs
|
364
|
+
end
|
365
|
+
|
366
|
+
# Returns markers used in query in valid order
|
367
|
+
def markers
|
368
|
+
@exprs.inject([]){|result, expr| result.concat expr.markers}
|
369
|
+
end
|
370
|
+
|
371
|
+
# Arranges map: +values+ {marker=>value} to ordered array of values
|
372
|
+
# accroding to markers in query.
|
373
|
+
def params values
|
374
|
+
markers.inject([]){|r, m| r << values[m]}
|
375
|
+
end
|
376
|
+
|
377
|
+
# Generates SQL for entire query.
|
378
|
+
def to_sql
|
379
|
+
eval_query
|
380
|
+
sql = "select #{@main.to_sql}.* from #{tables_sql}"
|
381
|
+
sql+= "\n\twhere #{where_sql}"
|
382
|
+
sql+= "\n\torder by #{order_by_sql}" unless @order.empty?
|
383
|
+
dbg_query sql
|
384
|
+
sql
|
385
|
+
end
|
386
|
+
|
387
|
+
private
|
388
|
+
# Prints debug message
|
389
|
+
def dbg_query sql
|
390
|
+
return unless Log.debug?
|
391
|
+
query = @query.is_a?(Proc) ? 'query given as block' : @query
|
392
|
+
Log.debug "Translate: <<#{query}>> to sql: <<#{sql}>>"
|
393
|
+
end
|
394
|
+
|
395
|
+
# Evaluates query withing the object context.
|
396
|
+
def eval_query
|
397
|
+
return if @evaluated
|
398
|
+
if @query.is_a? Proc
|
399
|
+
instance_eval(&@query)
|
400
|
+
else
|
401
|
+
instance_eval @query
|
402
|
+
end
|
403
|
+
@evaluated = true
|
404
|
+
end
|
405
|
+
|
406
|
+
# Generates +where+ clause for each expression joined by +and+ clause
|
407
|
+
def where_sql
|
408
|
+
r_params.collect{|rp| rp.to_sql + '.name = \'' + rp.name + '\''}.concat(
|
409
|
+
@exprs.collect{|ex| ex.to_sql}).join(" and\n\t\t")
|
410
|
+
end
|
411
|
+
|
412
|
+
# Generates +order by+ sql clause
|
413
|
+
def order_by_sql
|
414
|
+
@order.collect{|ord| ord.to_sql}.join(', ')
|
415
|
+
end
|
416
|
+
|
417
|
+
# Generates left outer join for each +r_params+ used in query
|
418
|
+
def tables_sql
|
419
|
+
r_objects.collect{|r_object| 'r_objects ' + r_object.to_sql}.join(', ') +
|
420
|
+
r_params.collect{|r_param|
|
421
|
+
"\n\tleft outer join r_params " + r_param.to_sql +
|
422
|
+
' on (' + r_param.r_object.to_sql + '.object_pk = ' + r_param.to_sql + '.object_pk)'
|
423
|
+
}.join()
|
424
|
+
end
|
425
|
+
|
426
|
+
# Retruns +r_params+ used in query. List of +RParam+.
|
427
|
+
def r_params
|
428
|
+
res = @exprs.inject([]) {|res, exp| res.concat exp.r_params}
|
429
|
+
@order.inject(res) {|res, ord| res.concat ord.r_params}
|
430
|
+
res.uniq!
|
431
|
+
res
|
432
|
+
end
|
433
|
+
|
434
|
+
# Retruns +r_objects+ used in query. List of +RObject+.
|
435
|
+
def r_objects
|
436
|
+
res = (@r_objects.values + r_params.inject([]){|res, rp| res << rp.r_object})
|
437
|
+
res.uniq!
|
438
|
+
res
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
@@generic_factory = Factory.new
|
443
|
+
end
|
444
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
module Rubernate
|
2
|
+
# Base class for all "Runtime+ implementations.
|
3
|
+
# Most of these methods should be overriden by subclasses.
|
4
|
+
class Runtime
|
5
|
+
include Callbacks::Runtime
|
6
|
+
|
7
|
+
# Log for Runtime events
|
8
|
+
Log = Log4r::Logger.new self.name
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@pool = {} # Contains objects loaded during the session
|
12
|
+
@factory = Queries::Factory.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Finds object by primary key,
|
16
|
+
# raises ObjectNotFoundException if object is not found
|
17
|
+
def find_by_pk pk, load = false
|
18
|
+
result = @pool[pk]
|
19
|
+
unless result
|
20
|
+
result = load_by_pk pk
|
21
|
+
unless result
|
22
|
+
Log.debug {"Find by pk: #{pk} - NOT found"}
|
23
|
+
raise ObjectNotFoundException.new(pk) unless result
|
24
|
+
end
|
25
|
+
@pool[pk] = result
|
26
|
+
end
|
27
|
+
load_by_pk pk if load
|
28
|
+
Log.debug {"Find by pk: #{pk} - found"}
|
29
|
+
result
|
30
|
+
end
|
31
|
+
|
32
|
+
# Finds objects by query. Returns ordered list of objects.
|
33
|
+
# If +params+ if Array the query will be treated as native sql
|
34
|
+
# and won't be processed.
|
35
|
+
def find_by_query query, params={} #TODO: improve working with paramters
|
36
|
+
flush_modified
|
37
|
+
params = case params
|
38
|
+
when Hash: params
|
39
|
+
when Array: params
|
40
|
+
else [params]
|
41
|
+
end
|
42
|
+
if query =~ Queries::RQL_PREFIX_REGEXP
|
43
|
+
objs = load_by_query(*native_sql(query, params))
|
44
|
+
else
|
45
|
+
objs = load_by_query query, params.collect{|p| native_param p}
|
46
|
+
end
|
47
|
+
Log.debug {"Find by query: <<#{query}>>, params: <<#{params}>>,- #{objs.size} objects found,"}
|
48
|
+
objs
|
49
|
+
rescue Exception => e
|
50
|
+
Log.error "Find by query: <<#{query}>>, params: <<#{params}>>, failed: #{e}"
|
51
|
+
raise
|
52
|
+
end
|
53
|
+
|
54
|
+
# Attaches object to session. Causes error if object with equal
|
55
|
+
# primary key already loaded in session.
|
56
|
+
def attach object
|
57
|
+
raise "Can't attach object: #{object.primary_key}" if @pool.has_key? object.primary_key
|
58
|
+
object.peer = Peer.new unless object.peer
|
59
|
+
create object
|
60
|
+
@pool[object.primary_key] = object
|
61
|
+
Log.debug {"Attach #{object.class.name}: #{object.primary_key} to session"}
|
62
|
+
object.on_create
|
63
|
+
end
|
64
|
+
|
65
|
+
# Removes object
|
66
|
+
def remove object
|
67
|
+
object.on_remove
|
68
|
+
@pool.delete object.primary_key
|
69
|
+
Log.debug {"Remove object: #{object.primary_key}"}
|
70
|
+
delete object
|
71
|
+
def object.primary_key
|
72
|
+
raise "object #{@primary_key} has already been deleted"
|
73
|
+
end
|
74
|
+
rescue Exception => ex
|
75
|
+
Log.error "Object remove fails due to error #{ex}"
|
76
|
+
raise
|
77
|
+
end
|
78
|
+
|
79
|
+
# Begins session
|
80
|
+
def begin
|
81
|
+
Log.debug {"Begin session #{self}"}
|
82
|
+
on_begin
|
83
|
+
end
|
84
|
+
|
85
|
+
# Persist all changes and Commit transction.
|
86
|
+
def commit
|
87
|
+
before_commit # callback
|
88
|
+
flush_modified
|
89
|
+
close
|
90
|
+
after_commit # callback
|
91
|
+
Log.debug {"Commit session #{self}"}
|
92
|
+
rescue Exception => ex
|
93
|
+
rollback ex
|
94
|
+
raise
|
95
|
+
end
|
96
|
+
|
97
|
+
# Discard all changes and Rollback transaction.
|
98
|
+
def rollback ex='not specified'
|
99
|
+
on_rollback # callback
|
100
|
+
Log.warn {"Rollback session #{self} due to error: #{ex}"}
|
101
|
+
failed
|
102
|
+
rescue Exception => e
|
103
|
+
Log.error "Rolling back session: #{self} failed: #{e}"
|
104
|
+
raise
|
105
|
+
end
|
106
|
+
|
107
|
+
# Flushes modified objects. Invokes on_store callback before storing.
|
108
|
+
def flush_modified
|
109
|
+
objects = modified
|
110
|
+
before_flush modified # callback
|
111
|
+
return if objects.empty?
|
112
|
+
Log.debug {"Flush #{objects.size} modified objects"}
|
113
|
+
objects.each {|object| object.on_save}
|
114
|
+
save objects
|
115
|
+
after_flush # callback
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
# Creates new object instance or returns pooled one
|
120
|
+
def instantiate pk, klass, peer = nil
|
121
|
+
object = @pool[pk]
|
122
|
+
if object and object.class != klass
|
123
|
+
raise "invalid class #{klass} of object pk: #{pk}: #{object.class}"
|
124
|
+
end
|
125
|
+
unless object
|
126
|
+
object, object.primary_key = klass.allocate, pk
|
127
|
+
@pool[pk] = object
|
128
|
+
end
|
129
|
+
object.peer = peer
|
130
|
+
object
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns modified objects.
|
134
|
+
def modified
|
135
|
+
@pool.values.find_all {|object| object.peer and object.dirty?}
|
136
|
+
end
|
137
|
+
|
138
|
+
# Callback functions invoked when session successfully closed
|
139
|
+
# Should be overriden by subclasses to perform implement closing logic
|
140
|
+
def close
|
141
|
+
end
|
142
|
+
|
143
|
+
# Callback functions invoked when session fails
|
144
|
+
# Should be overriden by subclasses to cleanup resources
|
145
|
+
def failed
|
146
|
+
end
|
147
|
+
|
148
|
+
# The following methods must be overriden by subclasses.
|
149
|
+
# Loads object by primary key
|
150
|
+
def load_by_pk pk
|
151
|
+
raise "method MUST be overiden in subclass"
|
152
|
+
end
|
153
|
+
|
154
|
+
# Loads objects by query
|
155
|
+
def load_by_query query, params=[]
|
156
|
+
raise "method MUST be overiden in subclass"
|
157
|
+
end
|
158
|
+
|
159
|
+
# Saves object or object if parameter is array
|
160
|
+
def save object
|
161
|
+
raise "method MUST be overiden in subclass"
|
162
|
+
end
|
163
|
+
|
164
|
+
# Creates object. Paramter can be class of object.
|
165
|
+
# Implementation should sets dirty flag to +true+ or +false+.
|
166
|
+
# Returns primary_key.
|
167
|
+
def create object
|
168
|
+
raise "method MUST be overiden in subclass"
|
169
|
+
end
|
170
|
+
|
171
|
+
# Deletes object
|
172
|
+
def delete object
|
173
|
+
raise "method MUST be overiden in subclass"
|
174
|
+
end
|
175
|
+
|
176
|
+
def post_load object
|
177
|
+
for value in object.peer.values
|
178
|
+
value.compact! if value.is_a? Array
|
179
|
+
end
|
180
|
+
object.peer.dirty = false
|
181
|
+
object.on_load
|
182
|
+
end
|
183
|
+
|
184
|
+
def native_sql query, params
|
185
|
+
query = @factory.query(query)
|
186
|
+
[query.to_sql, query.params(params).map!{|p| native_param p}]
|
187
|
+
end
|
188
|
+
|
189
|
+
def native_param p
|
190
|
+
case p
|
191
|
+
when Rubernate::Entity: p.primary_key
|
192
|
+
when Class: p.name
|
193
|
+
else p
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# TODO: continue from here!!!
|
198
|
+
class FakeEntity
|
199
|
+
persistent
|
200
|
+
def method_not_defined
|
201
|
+
raise 'object #{primary_key} has undefined class'
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
@@classes = {}
|
206
|
+
# Returns class with specified name or throw error if there is on one.
|
207
|
+
def class_by_name name
|
208
|
+
klass = @@classes[name]
|
209
|
+
return klass if klass
|
210
|
+
klass = Module.find_class name
|
211
|
+
@@classes[name] = klass if klass
|
212
|
+
klass
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|