Rubernate 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.
- 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
|