c3-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 +182 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +32 -0
  4. data/README.rdoc +77 -0
  5. data/Rakefile +49 -0
  6. data/VERSION +1 -0
  7. data/activerecord-oracle_enhanced-adapter.gemspec +79 -0
  8. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced.rake +51 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1664 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +121 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +64 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +393 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +389 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +163 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +168 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +213 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +224 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +11 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  24. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +477 -0
  25. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +267 -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 +218 -0
  35. data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +784 -0
  36. data/spec/spec.opts +6 -0
  37. data/spec/spec_helper.rb +114 -0
  38. metadata +137 -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