oracle_enhanced 1.2.5
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/.gitignore +10 -0
- data/History.txt +182 -0
- data/License.txt +20 -0
- data/Manifest.txt +32 -0
- data/README.rdoc +77 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +51 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1661 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +121 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +64 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +393 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +389 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +163 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +168 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +213 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +224 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +11 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +477 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +267 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +206 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +40 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +107 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +984 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +67 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +93 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +370 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +203 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +784 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +114 -0
- metadata +140 -0
@@ -0,0 +1,389 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "oci8"
|
5
|
+
rescue LoadError
|
6
|
+
# OCI8 driver is unavailable.
|
7
|
+
raise LoadError, "ERROR: ActiveRecord oracle_enhanced adapter could not load ruby-oci8 library. Please install ruby-oci8 gem."
|
8
|
+
end
|
9
|
+
|
10
|
+
# check ruby-oci8 version
|
11
|
+
required_oci8_version = [2, 0, 3]
|
12
|
+
oci8_version_ints = OCI8::VERSION.scan(/\d+/).map{|s| s.to_i}
|
13
|
+
if (oci8_version_ints <=> required_oci8_version) < 0
|
14
|
+
raise LoadError, "ERROR: ruby-oci8 version #{OCI8::VERSION} is too old. Please install ruby-oci8 version #{required_oci8_version.join('.')} or later."
|
15
|
+
end
|
16
|
+
|
17
|
+
module ActiveRecord
|
18
|
+
module ConnectionAdapters
|
19
|
+
|
20
|
+
# OCI database interface for MRI
|
21
|
+
class OracleEnhancedOCIConnection < OracleEnhancedConnection #:nodoc:
|
22
|
+
|
23
|
+
def initialize(config)
|
24
|
+
@raw_connection = OCI8EnhancedAutoRecover.new(config, OracleEnhancedOCIFactory)
|
25
|
+
# default schema owner
|
26
|
+
@owner = config[:username].to_s.upcase
|
27
|
+
end
|
28
|
+
|
29
|
+
def auto_retry
|
30
|
+
@raw_connection.auto_retry if @raw_connection
|
31
|
+
end
|
32
|
+
|
33
|
+
def auto_retry=(value)
|
34
|
+
@raw_connection.auto_retry = value if @raw_connection
|
35
|
+
end
|
36
|
+
|
37
|
+
def logoff
|
38
|
+
@raw_connection.logoff
|
39
|
+
@raw_connection.active = false
|
40
|
+
end
|
41
|
+
|
42
|
+
def commit
|
43
|
+
@raw_connection.commit
|
44
|
+
end
|
45
|
+
|
46
|
+
def rollback
|
47
|
+
@raw_connection.rollback
|
48
|
+
end
|
49
|
+
|
50
|
+
def autocommit?
|
51
|
+
@raw_connection.autocommit?
|
52
|
+
end
|
53
|
+
|
54
|
+
def autocommit=(value)
|
55
|
+
@raw_connection.autocommit = value
|
56
|
+
end
|
57
|
+
|
58
|
+
# Checks connection, returns true if active. Note that ping actively
|
59
|
+
# checks the connection, while #active? simply returns the last
|
60
|
+
# known state.
|
61
|
+
def ping
|
62
|
+
@raw_connection.ping
|
63
|
+
rescue OCIException => e
|
64
|
+
raise OracleEnhancedConnectionException, e.message
|
65
|
+
end
|
66
|
+
|
67
|
+
def active?
|
68
|
+
@raw_connection.active?
|
69
|
+
end
|
70
|
+
|
71
|
+
def reset!
|
72
|
+
@raw_connection.reset!
|
73
|
+
rescue OCIException => e
|
74
|
+
raise OracleEnhancedConnectionException, e.message
|
75
|
+
end
|
76
|
+
|
77
|
+
def exec(sql, *bindvars, &block)
|
78
|
+
@raw_connection.exec(sql, *bindvars, &block)
|
79
|
+
end
|
80
|
+
|
81
|
+
def returning_clause(quoted_pk)
|
82
|
+
" RETURNING #{quoted_pk} INTO :insert_id"
|
83
|
+
end
|
84
|
+
|
85
|
+
# execute sql with RETURNING ... INTO :insert_id
|
86
|
+
# and return :insert_id value
|
87
|
+
def exec_with_returning(sql)
|
88
|
+
cursor = @raw_connection.parse(sql)
|
89
|
+
cursor.bind_param(':insert_id', nil, Integer)
|
90
|
+
cursor.exec
|
91
|
+
cursor[':insert_id']
|
92
|
+
ensure
|
93
|
+
cursor.close rescue nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def select(sql, name = nil, return_column_names = false)
|
97
|
+
cursor = @raw_connection.exec(sql)
|
98
|
+
cols = []
|
99
|
+
# Ignore raw_rnum_ which is used to simulate LIMIT and OFFSET
|
100
|
+
cursor.get_col_names.each do |col_name|
|
101
|
+
col_name = oracle_downcase(col_name)
|
102
|
+
cols << col_name unless col_name == 'raw_rnum_'
|
103
|
+
end
|
104
|
+
# Reuse the same hash for all rows
|
105
|
+
column_hash = {}
|
106
|
+
cols.each {|c| column_hash[c] = nil}
|
107
|
+
rows = []
|
108
|
+
get_lob_value = !(name == 'Writable Large Object')
|
109
|
+
|
110
|
+
while row = cursor.fetch
|
111
|
+
hash = column_hash.dup
|
112
|
+
|
113
|
+
cols.each_with_index do |col, i|
|
114
|
+
hash[col] = typecast_result_value(row[i], get_lob_value)
|
115
|
+
end
|
116
|
+
|
117
|
+
rows << hash
|
118
|
+
end
|
119
|
+
|
120
|
+
return_column_names ? [rows, cols] : rows
|
121
|
+
ensure
|
122
|
+
cursor.close if cursor
|
123
|
+
end
|
124
|
+
|
125
|
+
def write_lob(lob, value, is_binary = false)
|
126
|
+
lob.write value
|
127
|
+
end
|
128
|
+
|
129
|
+
def describe(name)
|
130
|
+
# fall back to SELECT based describe if using database link
|
131
|
+
return super if name.to_s.include?('@')
|
132
|
+
quoted_name = OracleEnhancedAdapter.valid_table_name?(name) ? name : "\"#{name}\""
|
133
|
+
@raw_connection.describe(quoted_name)
|
134
|
+
rescue OCIException => e
|
135
|
+
# fall back to SELECT which can handle synonyms to database links
|
136
|
+
super
|
137
|
+
end
|
138
|
+
|
139
|
+
# Return OCIError error code
|
140
|
+
def error_code(exception)
|
141
|
+
exception.code
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def typecast_result_value(value, get_lob_value)
|
147
|
+
case value
|
148
|
+
when Fixnum, Bignum
|
149
|
+
value
|
150
|
+
when String
|
151
|
+
value
|
152
|
+
when Float, BigDecimal
|
153
|
+
# return Fixnum or Bignum if value is integer (to avoid issues with _before_type_cast values for id attributes)
|
154
|
+
value == (v_to_i = value.to_i) ? v_to_i : value
|
155
|
+
when OraNumber
|
156
|
+
# change OraNumber value (returned in early versions of ruby-oci8 2.0.x) to BigDecimal
|
157
|
+
value == (v_to_i = value.to_i) ? v_to_i : BigDecimal.new(value.to_s)
|
158
|
+
when OCI8::LOB
|
159
|
+
if get_lob_value
|
160
|
+
data = value.read
|
161
|
+
# In Ruby 1.9.1 always change encoding to ASCII-8BIT for binaries
|
162
|
+
data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) && value.is_a?(OCI8::BLOB)
|
163
|
+
data
|
164
|
+
else
|
165
|
+
value
|
166
|
+
end
|
167
|
+
# ruby-oci8 1.0 returns OraDate
|
168
|
+
# ruby-oci8 2.0 returns Time or DateTime
|
169
|
+
when OraDate, Time, DateTime
|
170
|
+
if OracleEnhancedAdapter.emulate_dates && date_without_time?(value)
|
171
|
+
value.to_date
|
172
|
+
else
|
173
|
+
create_time_with_default_timezone(value)
|
174
|
+
end
|
175
|
+
else
|
176
|
+
value
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def date_without_time?(value)
|
181
|
+
case value
|
182
|
+
when OraDate
|
183
|
+
value.hour == 0 && value.minute == 0 && value.second == 0
|
184
|
+
else
|
185
|
+
value.hour == 0 && value.min == 0 && value.sec == 0
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def create_time_with_default_timezone(value)
|
190
|
+
year, month, day, hour, min, sec, usec = case value
|
191
|
+
when Time
|
192
|
+
[value.year, value.month, value.day, value.hour, value.min, value.sec, value.usec]
|
193
|
+
when OraDate
|
194
|
+
[value.year, value.month, value.day, value.hour, value.minute, value.second, 0]
|
195
|
+
else
|
196
|
+
[value.year, value.month, value.day, value.hour, value.min, value.sec, 0]
|
197
|
+
end
|
198
|
+
# code from Time.time_with_datetime_fallback
|
199
|
+
begin
|
200
|
+
Time.send(Base.default_timezone, year, month, day, hour, min, sec, usec)
|
201
|
+
rescue
|
202
|
+
offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
|
203
|
+
::DateTime.civil(year, month, day, hour, min, sec, offset)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
# The OracleEnhancedOCIFactory factors out the code necessary to connect and
|
210
|
+
# configure an Oracle/OCI connection.
|
211
|
+
class OracleEnhancedOCIFactory #:nodoc:
|
212
|
+
def self.new_connection(config)
|
213
|
+
username, password, database = config[:username].to_s, config[:password].to_s, config[:database].to_s
|
214
|
+
privilege = config[:privilege] && config[:privilege].to_sym
|
215
|
+
async = config[:allow_concurrency]
|
216
|
+
prefetch_rows = config[:prefetch_rows] || 100
|
217
|
+
cursor_sharing = config[:cursor_sharing] || 'force'
|
218
|
+
# by default VARCHAR2 column size will be interpreted as max number of characters (and not bytes)
|
219
|
+
nls_length_semantics = config[:nls_length_semantics] || 'CHAR'
|
220
|
+
# get session time_zone from configuration or from TZ environment variable
|
221
|
+
time_zone = config[:time_zone] || ENV['TZ']
|
222
|
+
|
223
|
+
conn = OCI8.new username, password, database, privilege
|
224
|
+
conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
|
225
|
+
conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS:FF6'} rescue nil
|
226
|
+
conn.autocommit = true
|
227
|
+
conn.non_blocking = true if async
|
228
|
+
conn.prefetch_rows = prefetch_rows
|
229
|
+
conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
|
230
|
+
conn.exec "alter session set nls_length_semantics = '#{nls_length_semantics}'"
|
231
|
+
conn.exec "alter session set time_zone = '#{time_zone}'" unless time_zone.blank?
|
232
|
+
conn
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
|
242
|
+
class OCI8 #:nodoc:
|
243
|
+
|
244
|
+
class Cursor #:nodoc:
|
245
|
+
if method_defined? :define_a_column
|
246
|
+
# This OCI8 patch is required with the ruby-oci8 1.0.x or lower.
|
247
|
+
# Set OCI8::BindType::Mapping[] to change the column type
|
248
|
+
# when using ruby-oci8 2.0.
|
249
|
+
|
250
|
+
alias :enhanced_define_a_column_pre_ar :define_a_column
|
251
|
+
def define_a_column(i)
|
252
|
+
case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
|
253
|
+
when 8; @stmt.defineByPos(i, String, 65535) # Read LONG values
|
254
|
+
when 187; @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
|
255
|
+
when 108
|
256
|
+
if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
|
257
|
+
@stmt.defineByPos(i, String, 65535)
|
258
|
+
else
|
259
|
+
raise 'unsupported datatype'
|
260
|
+
end
|
261
|
+
else enhanced_define_a_column_pre_ar i
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
if OCI8.public_method_defined?(:describe_table)
|
268
|
+
# ruby-oci8 2.0 or upper
|
269
|
+
|
270
|
+
def describe(name)
|
271
|
+
info = describe_table(name.to_s)
|
272
|
+
raise %Q{"DESC #{name}" failed} if info.nil?
|
273
|
+
[info.obj_schema, info.obj_name]
|
274
|
+
end
|
275
|
+
else
|
276
|
+
# ruby-oci8 1.0.x or lower
|
277
|
+
|
278
|
+
# missing constant from oci8 < 0.1.14
|
279
|
+
OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
|
280
|
+
|
281
|
+
# Uses the describeAny OCI call to find the target owner and table_name
|
282
|
+
# indicated by +name+, parsing through synonynms as necessary. Returns
|
283
|
+
# an array of [owner, table_name].
|
284
|
+
def describe(name)
|
285
|
+
@desc ||= @@env.alloc(OCIDescribe)
|
286
|
+
@desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
|
287
|
+
do_ocicall(@ctx) { @desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) } rescue raise %Q{"DESC #{name}" failed; does it exist?}
|
288
|
+
info = @desc.attrGet(OCI_ATTR_PARAM)
|
289
|
+
|
290
|
+
case info.attrGet(OCI_ATTR_PTYPE)
|
291
|
+
when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
|
292
|
+
owner = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
|
293
|
+
table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
|
294
|
+
[owner, table_name]
|
295
|
+
when OCI_PTYPE_SYN
|
296
|
+
schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
|
297
|
+
name = info.attrGet(OCI_ATTR_NAME)
|
298
|
+
describe(schema + '.' + name)
|
299
|
+
else raise %Q{"DESC #{name}" failed; not a table or view.}
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
end
|
305
|
+
|
306
|
+
# The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
|
307
|
+
# reset functionality. If a call to #exec fails, and autocommit is turned on
|
308
|
+
# (ie., we're not in the middle of a longer transaction), it will
|
309
|
+
# automatically reconnect and try again. If autocommit is turned off,
|
310
|
+
# this would be dangerous (as the earlier part of the implied transaction
|
311
|
+
# may have failed silently if the connection died) -- so instead the
|
312
|
+
# connection is marked as dead, to be reconnected on it's next use.
|
313
|
+
#:stopdoc:
|
314
|
+
class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
|
315
|
+
attr_accessor :active #:nodoc:
|
316
|
+
alias :active? :active #:nodoc:
|
317
|
+
|
318
|
+
cattr_accessor :auto_retry
|
319
|
+
class << self
|
320
|
+
alias :auto_retry? :auto_retry #:nodoc:
|
321
|
+
end
|
322
|
+
@@auto_retry = false
|
323
|
+
|
324
|
+
def initialize(config, factory) #:nodoc:
|
325
|
+
@active = true
|
326
|
+
@config = config
|
327
|
+
@factory = factory
|
328
|
+
@connection = @factory.new_connection @config
|
329
|
+
super @connection
|
330
|
+
end
|
331
|
+
|
332
|
+
# Checks connection, returns true if active. Note that ping actively
|
333
|
+
# checks the connection, while #active? simply returns the last
|
334
|
+
# known state.
|
335
|
+
def ping #:nodoc:
|
336
|
+
@connection.exec("select 1 from dual") { |r| nil }
|
337
|
+
@active = true
|
338
|
+
rescue
|
339
|
+
@active = false
|
340
|
+
raise
|
341
|
+
end
|
342
|
+
|
343
|
+
# Resets connection, by logging off and creating a new connection.
|
344
|
+
def reset! #:nodoc:
|
345
|
+
logoff rescue nil
|
346
|
+
begin
|
347
|
+
@connection = @factory.new_connection @config
|
348
|
+
__setobj__ @connection
|
349
|
+
@active = true
|
350
|
+
rescue
|
351
|
+
@active = false
|
352
|
+
raise
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# ORA-00028: your session has been killed
|
357
|
+
# ORA-01012: not logged on
|
358
|
+
# ORA-03113: end-of-file on communication channel
|
359
|
+
# ORA-03114: not connected to ORACLE
|
360
|
+
# ORA-03135: connection lost contact
|
361
|
+
LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114, 3135 ] #:nodoc:
|
362
|
+
|
363
|
+
# Adds auto-recovery functionality.
|
364
|
+
#
|
365
|
+
# See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
|
366
|
+
def exec(sql, *bindvars, &block) #:nodoc:
|
367
|
+
should_retry = self.class.auto_retry? && autocommit?
|
368
|
+
|
369
|
+
begin
|
370
|
+
@connection.exec(sql, *bindvars, &block)
|
371
|
+
rescue OCIException => e
|
372
|
+
raise unless e.is_a?(OCIError) && LOST_CONNECTION_ERROR_CODES.include?(e.code)
|
373
|
+
@active = false
|
374
|
+
raise unless should_retry
|
375
|
+
should_retry = false
|
376
|
+
reset! rescue nil
|
377
|
+
retry
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# otherwise not working in Ruby 1.9.1
|
382
|
+
if RUBY_VERSION =~ /^1\.9/
|
383
|
+
def describe(name) #:nodoc:
|
384
|
+
@connection.describe(name)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
end
|
389
|
+
#:startdoc:
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# define accessors before requiring ruby-plsql as these accessors are used in clob writing callback and should be
|
2
|
+
# available also if ruby-plsql could not be loaded
|
3
|
+
ActiveRecord::Base.class_eval do
|
4
|
+
class_inheritable_accessor :custom_create_method, :custom_update_method, :custom_delete_method
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'active_support'
|
8
|
+
|
9
|
+
module ActiveRecord #:nodoc:
|
10
|
+
module ConnectionAdapters #:nodoc:
|
11
|
+
module OracleEnhancedProcedures #:nodoc:
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
# Specify custom create method which should be used instead of Rails generated INSERT statement.
|
15
|
+
# Provided block should return ID of new record.
|
16
|
+
# Example:
|
17
|
+
# set_create_method do
|
18
|
+
# plsql.employees_pkg.create_employee(
|
19
|
+
# :p_first_name => first_name,
|
20
|
+
# :p_last_name => last_name,
|
21
|
+
# :p_employee_id => nil
|
22
|
+
# )[:p_employee_id]
|
23
|
+
# end
|
24
|
+
def set_create_method(&block)
|
25
|
+
include_with_custom_methods
|
26
|
+
self.custom_create_method = block
|
27
|
+
end
|
28
|
+
|
29
|
+
# Specify custom update method which should be used instead of Rails generated UPDATE statement.
|
30
|
+
# Example:
|
31
|
+
# set_update_method do
|
32
|
+
# plsql.employees_pkg.update_employee(
|
33
|
+
# :p_employee_id => id,
|
34
|
+
# :p_first_name => first_name,
|
35
|
+
# :p_last_name => last_name
|
36
|
+
# )
|
37
|
+
# end
|
38
|
+
def set_update_method(&block)
|
39
|
+
include_with_custom_methods
|
40
|
+
self.custom_update_method = block
|
41
|
+
end
|
42
|
+
|
43
|
+
# Specify custom delete method which should be used instead of Rails generated DELETE statement.
|
44
|
+
# Example:
|
45
|
+
# set_delete_method do
|
46
|
+
# plsql.employees_pkg.delete_employee(
|
47
|
+
# :p_employee_id => id
|
48
|
+
# )
|
49
|
+
# end
|
50
|
+
def set_delete_method(&block)
|
51
|
+
include_with_custom_methods
|
52
|
+
self.custom_delete_method = block
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_method_name_before_custom_methods #:nodoc:
|
56
|
+
if private_method_defined?(:create_without_timestamps) && defined?(ActiveRecord::VERSION) && ActiveRecord::VERSION::STRING.to_f >= 2.3
|
57
|
+
:create_without_timestamps
|
58
|
+
elsif private_method_defined?(:create_without_callbacks)
|
59
|
+
:create_without_callbacks
|
60
|
+
else
|
61
|
+
:create
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def update_method_name_before_custom_methods #:nodoc:
|
66
|
+
if private_method_defined?(:update_without_dirty)
|
67
|
+
:update_without_dirty
|
68
|
+
elsif private_method_defined?(:update_without_timestamps) && defined?(ActiveRecord::VERSION) && ActiveRecord::VERSION::STRING.to_f >= 2.3
|
69
|
+
:update_without_timestamps
|
70
|
+
elsif private_method_defined?(:update_without_callbacks)
|
71
|
+
:update_without_callbacks
|
72
|
+
else
|
73
|
+
:update
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def destroy_method_name_before_custom_methods #:nodoc:
|
78
|
+
if public_method_defined?(:destroy_without_callbacks)
|
79
|
+
:destroy_without_callbacks
|
80
|
+
else
|
81
|
+
:destroy
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def include_with_custom_methods
|
87
|
+
unless included_modules.include? InstanceMethods
|
88
|
+
include InstanceMethods
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
module InstanceMethods #:nodoc:
|
94
|
+
def self.included(base)
|
95
|
+
base.instance_eval do
|
96
|
+
alias_method :create_without_custom_method, create_method_name_before_custom_methods
|
97
|
+
alias_method create_method_name_before_custom_methods, :create_with_custom_method
|
98
|
+
alias_method :update_without_custom_method, update_method_name_before_custom_methods
|
99
|
+
alias_method update_method_name_before_custom_methods, :update_with_custom_method
|
100
|
+
alias_method :destroy_without_custom_method, destroy_method_name_before_custom_methods
|
101
|
+
alias_method destroy_method_name_before_custom_methods, :destroy_with_custom_method
|
102
|
+
private :create, :update
|
103
|
+
public :destroy
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
# Creates a record with custom create method
|
110
|
+
# and returns its id.
|
111
|
+
def create_with_custom_method
|
112
|
+
# check if class has custom create method
|
113
|
+
return create_without_custom_method unless self.class.custom_create_method
|
114
|
+
self.class.connection.log_custom_method("custom create method", "#{self.class.name} Create") do
|
115
|
+
self.id = self.class.custom_create_method.bind(self).call
|
116
|
+
end
|
117
|
+
@new_record = false
|
118
|
+
id
|
119
|
+
end
|
120
|
+
|
121
|
+
# Updates the associated record with custom update method
|
122
|
+
# Returns the number of affected rows.
|
123
|
+
def update_with_custom_method(attribute_names = @attributes.keys)
|
124
|
+
# check if class has custom create method
|
125
|
+
return update_without_custom_method unless self.class.custom_update_method
|
126
|
+
return 0 if attribute_names.empty?
|
127
|
+
self.class.connection.log_custom_method("custom update method with #{self.class.primary_key}=#{self.id}", "#{self.class.name} Update") do
|
128
|
+
self.class.custom_update_method.bind(self).call
|
129
|
+
end
|
130
|
+
1
|
131
|
+
end
|
132
|
+
|
133
|
+
# Deletes the record in the database with custom delete method
|
134
|
+
# and freezes this instance to reflect that no changes should
|
135
|
+
# be made (since they can't be persisted).
|
136
|
+
def destroy_with_custom_method
|
137
|
+
# check if class has custom create method
|
138
|
+
return destroy_without_custom_method unless self.class.custom_delete_method
|
139
|
+
unless new_record?
|
140
|
+
self.class.connection.log_custom_method("custom delete method with #{self.class.primary_key}=#{self.id}", "#{self.class.name} Destroy") do
|
141
|
+
self.class.custom_delete_method.bind(self).call
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
@destroyed = true
|
146
|
+
freeze
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
ActiveRecord::Base.class_eval do
|
156
|
+
extend ActiveRecord::ConnectionAdapters::OracleEnhancedProcedures::ClassMethods
|
157
|
+
end
|
158
|
+
|
159
|
+
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
|
160
|
+
# public alias to log method which could be used from other objects
|
161
|
+
alias_method :log_custom_method, :log
|
162
|
+
public :log_custom_method
|
163
|
+
end
|