unclebilly-activerecord-oracle_enhanced-adapter 1.2.4

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