activerecord-oracle_enhanced-adapter 1.2.4 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -1
- data/History.txt +20 -0
- data/README.rdoc +7 -3
- data/Rakefile +1 -2
- data/VERSION +1 -1
- data/activerecord-oracle_enhanced-adapter.gemspec +96 -0
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +11 -8
- data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +37 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +317 -180
- data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +282 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +3 -2
- data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +1 -1
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +3 -3
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +6 -1
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +143 -52
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +2 -1
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +39 -20
- data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +2 -1
- data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +70 -11
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +27 -20
- data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +334 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +28 -22
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +24 -28
- data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +13 -11
- data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +1 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +72 -69
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +112 -6
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +49 -1
- data/spec/spec_helper.rb +97 -19
- metadata +33 -22
- data/Manifest.txt +0 -32
- data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +0 -126
@@ -0,0 +1,282 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module OracleEnhancedContextIndex
|
4
|
+
|
5
|
+
# Define full text index with Oracle specific CONTEXT index type
|
6
|
+
#
|
7
|
+
# Oracle CONTEXT index by default supports full text indexing of one column.
|
8
|
+
# This method allows full text index creation also on several columns
|
9
|
+
# as well as indexing related table columns by generating stored procedure
|
10
|
+
# that concatenates all columns for indexing as well as generating trigger
|
11
|
+
# that will update main index column to trigger reindexing of record.
|
12
|
+
#
|
13
|
+
# Use +contains+ ActiveRecord model instance method to add CONTAINS where condition
|
14
|
+
# and order by score of matched results.
|
15
|
+
#
|
16
|
+
# ===== Examples
|
17
|
+
#
|
18
|
+
# ====== Creating single column index
|
19
|
+
# add_context_index :posts, :title
|
20
|
+
# search with
|
21
|
+
# Post.contains(:title, 'word')
|
22
|
+
#
|
23
|
+
# ====== Creating index on several columns
|
24
|
+
# add_context_index :posts, [:title, :body]
|
25
|
+
# search with (use first column as argument for contains method but it will search in all index columns)
|
26
|
+
# Post.contains(:title, 'word')
|
27
|
+
#
|
28
|
+
# ====== Creating index on several columns with dummy index column and commit option
|
29
|
+
# add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT'
|
30
|
+
# search with
|
31
|
+
# Post.contains(:all_text, 'word')
|
32
|
+
#
|
33
|
+
# ====== Creating index with trigger option (will reindex when specified columns are updated)
|
34
|
+
# add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT',
|
35
|
+
# :index_column_trigger_on => [:created_at, :updated_at]
|
36
|
+
# search with
|
37
|
+
# Post.contains(:all_text, 'word')
|
38
|
+
#
|
39
|
+
# ====== Creating index on multiple tables
|
40
|
+
# add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT',
|
41
|
+
# :index_column_trigger_on => [:created_at, :updated_at]
|
42
|
+
# add_context_index :posts,
|
43
|
+
# [:title, :body,
|
44
|
+
# # specify aliases always with AS keyword
|
45
|
+
# "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
|
46
|
+
# ],
|
47
|
+
# :name => 'post_and_comments_index',
|
48
|
+
# :index_column => :all_text, :index_column_trigger_on => [:updated_at, :comments_count],
|
49
|
+
# :sync => 'ON COMMIT'
|
50
|
+
# search in any table columns
|
51
|
+
# Post.contains(:all_text, 'word')
|
52
|
+
# search in specified column
|
53
|
+
# Post.contains(:all_text, "aaa within title")
|
54
|
+
# Post.contains(:all_text, "bbb within comment_author")
|
55
|
+
#
|
56
|
+
def add_context_index(table_name, column_name, options = {})
|
57
|
+
self.all_schema_indexes = nil
|
58
|
+
column_names = Array(column_name)
|
59
|
+
index_name = options[:name] || index_name(table_name, :column => options[:index_column] || column_names,
|
60
|
+
# CONEXT index name max length is 25
|
61
|
+
:identifier_max_length => 25)
|
62
|
+
|
63
|
+
quoted_column_name = quote_column_name(options[:index_column] || column_names.first)
|
64
|
+
if options[:index_column_trigger_on]
|
65
|
+
raise ArgumentError, "Option :index_column should be specified together with :index_column_cource option" \
|
66
|
+
unless options[:index_column]
|
67
|
+
create_index_column_trigger(table_name, index_name, options[:index_column], options[:index_column_trigger_on])
|
68
|
+
end
|
69
|
+
|
70
|
+
sql = "CREATE INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
|
71
|
+
sql << " (#{quoted_column_name})"
|
72
|
+
sql << " INDEXTYPE IS CTXSYS.CONTEXT"
|
73
|
+
parameters = []
|
74
|
+
if column_names.size > 1
|
75
|
+
procedure_name = default_datastore_procedure(index_name)
|
76
|
+
datastore_name = default_datastore_name(index_name)
|
77
|
+
create_datastore_procedure(table_name, procedure_name, column_names, options)
|
78
|
+
create_datastore_preference(datastore_name, procedure_name)
|
79
|
+
parameters << "DATASTORE #{datastore_name} SECTION GROUP CTXSYS.AUTO_SECTION_GROUP"
|
80
|
+
end
|
81
|
+
if options[:tablespace]
|
82
|
+
storage_name = default_storage_name(index_name)
|
83
|
+
create_storage_preference(storage_name, options[:tablespace])
|
84
|
+
parameters << "STORAGE #{storage_name}"
|
85
|
+
end
|
86
|
+
if options[:sync]
|
87
|
+
parameters << "SYNC(#{options[:sync]})"
|
88
|
+
end
|
89
|
+
unless parameters.empty?
|
90
|
+
sql << " PARAMETERS ('#{parameters.join(' ')}')"
|
91
|
+
end
|
92
|
+
execute sql
|
93
|
+
end
|
94
|
+
|
95
|
+
# Drop full text index with Oracle specific CONTEXT index type
|
96
|
+
def remove_context_index(table_name, options = {})
|
97
|
+
self.all_schema_indexes = nil
|
98
|
+
unless Hash === options # if column names passed as argument
|
99
|
+
options = {:column => Array(options)}
|
100
|
+
end
|
101
|
+
index_name = options[:name] || index_name(table_name,
|
102
|
+
:column => options[:index_column] || options[:column], :identifier_max_length => 25)
|
103
|
+
execute "DROP INDEX #{index_name}"
|
104
|
+
drop_ctx_preference(default_datastore_name(index_name))
|
105
|
+
drop_ctx_preference(default_storage_name(index_name))
|
106
|
+
procedure_name = default_datastore_procedure(index_name)
|
107
|
+
execute "DROP PROCEDURE #{quote_table_name(procedure_name)}" rescue nil
|
108
|
+
drop_index_column_trigger(index_name)
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def create_datastore_procedure(table_name, procedure_name, column_names, options)
|
114
|
+
quoted_table_name = quote_table_name(table_name)
|
115
|
+
select_queries = column_names.select{|c| c.to_s =~ /^SELECT /i}
|
116
|
+
column_names = column_names - select_queries
|
117
|
+
keys, selected_columns = parse_select_queries(select_queries)
|
118
|
+
quoted_column_names = (column_names+keys).map{|col| quote_column_name(col)}
|
119
|
+
execute compress_lines(<<-SQL)
|
120
|
+
CREATE OR REPLACE PROCEDURE #{quote_table_name(procedure_name)}
|
121
|
+
(p_rowid IN ROWID,
|
122
|
+
p_clob IN OUT NOCOPY CLOB) IS
|
123
|
+
-- add_context_index_parameters #{(column_names+select_queries).inspect}#{!options.empty? ? ', ' << options.inspect[1..-2] : ''}
|
124
|
+
#{
|
125
|
+
selected_columns.map do |cols|
|
126
|
+
cols.map do |col|
|
127
|
+
raise ArgumentError, "Alias #{col} too large, should be 28 or less characters long" unless col.length <= 28
|
128
|
+
"l_#{col} VARCHAR2(32767);\n"
|
129
|
+
end.join
|
130
|
+
end.join
|
131
|
+
} BEGIN
|
132
|
+
FOR r1 IN (
|
133
|
+
SELECT #{quoted_column_names.join(', ')}
|
134
|
+
FROM #{quoted_table_name}
|
135
|
+
WHERE #{quoted_table_name}.ROWID = p_rowid
|
136
|
+
) LOOP
|
137
|
+
#{
|
138
|
+
(column_names.map do |col|
|
139
|
+
col = col.to_s
|
140
|
+
"DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+2}, '<#{col}>');\n" <<
|
141
|
+
"DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(r1.#{col}), r1.#{col});\n" <<
|
142
|
+
"DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+3}, '</#{col}>');\n"
|
143
|
+
end.join) <<
|
144
|
+
(selected_columns.zip(select_queries).map do |cols, query|
|
145
|
+
(cols.map do |col|
|
146
|
+
"l_#{col} := '';\n"
|
147
|
+
end.join) <<
|
148
|
+
"FOR r2 IN (\n" <<
|
149
|
+
query.gsub(/:(\w+)/,"r1.\\1") << "\n) LOOP\n" <<
|
150
|
+
(cols.map do |col|
|
151
|
+
"l_#{col} := l_#{col} || r2.#{col} || CHR(10);\n"
|
152
|
+
end.join) <<
|
153
|
+
"END LOOP;\n" <<
|
154
|
+
(cols.map do |col|
|
155
|
+
col = col.to_s
|
156
|
+
"DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+2}, '<#{col}>');\n" <<
|
157
|
+
"IF LENGTH(l_#{col}) > 0 THEN\n" <<
|
158
|
+
"DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(l_#{col}), l_#{col});\n" <<
|
159
|
+
"END IF;\n" <<
|
160
|
+
"DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+3}, '</#{col}>');\n"
|
161
|
+
end.join)
|
162
|
+
end.join)
|
163
|
+
}
|
164
|
+
END LOOP;
|
165
|
+
END;
|
166
|
+
SQL
|
167
|
+
end
|
168
|
+
|
169
|
+
def parse_select_queries(select_queries)
|
170
|
+
keys = []
|
171
|
+
selected_columns = []
|
172
|
+
select_queries.each do |query|
|
173
|
+
# get primary or foreign keys like :id or :something_id
|
174
|
+
keys << (query.scan(/:\w+/).map{|k| k[1..-1].downcase.to_sym})
|
175
|
+
select_part = query.scan(/^select\s.*\sfrom/i).first
|
176
|
+
selected_columns << select_part.scan(/\sas\s+(\w+)/i).map{|c| c.first}
|
177
|
+
end
|
178
|
+
[keys.flatten.uniq, selected_columns]
|
179
|
+
end
|
180
|
+
|
181
|
+
def create_datastore_preference(datastore_name, procedure_name)
|
182
|
+
drop_ctx_preference(datastore_name)
|
183
|
+
execute <<-SQL
|
184
|
+
BEGIN
|
185
|
+
CTX_DDL.CREATE_PREFERENCE('#{datastore_name}', 'USER_DATASTORE');
|
186
|
+
CTX_DDL.SET_ATTRIBUTE('#{datastore_name}', 'PROCEDURE', '#{procedure_name}');
|
187
|
+
END;
|
188
|
+
SQL
|
189
|
+
end
|
190
|
+
|
191
|
+
def create_storage_preference(storage_name, tablespace)
|
192
|
+
drop_ctx_preference(storage_name)
|
193
|
+
sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{storage_name}', 'BASIC_STORAGE');\n"
|
194
|
+
['I_TABLE_CLAUSE', 'K_TABLE_CLAUSE', 'R_TABLE_CLAUSE',
|
195
|
+
'N_TABLE_CLAUSE', 'I_INDEX_CLAUSE', 'P_TABLE_CLAUSE'].each do |clause|
|
196
|
+
default_clause = case clause
|
197
|
+
when 'R_TABLE_CLAUSE'; 'LOB(DATA) STORE AS (CACHE) '
|
198
|
+
when 'I_INDEX_CLAUSE'; 'COMPRESS 2 '
|
199
|
+
else ''
|
200
|
+
end
|
201
|
+
sql << "CTX_DDL.SET_ATTRIBUTE('#{storage_name}', '#{clause}', '#{default_clause}TABLESPACE #{tablespace}');\n"
|
202
|
+
end
|
203
|
+
sql << "END;\n"
|
204
|
+
execute sql
|
205
|
+
end
|
206
|
+
|
207
|
+
def drop_ctx_preference(preference_name)
|
208
|
+
execute "BEGIN CTX_DDL.DROP_PREFERENCE('#{preference_name}'); END;" rescue nil
|
209
|
+
end
|
210
|
+
|
211
|
+
def create_index_column_trigger(table_name, index_name, index_column, index_column_source)
|
212
|
+
trigger_name = default_index_column_trigger_name(index_name)
|
213
|
+
columns = Array(index_column_source)
|
214
|
+
quoted_column_names = columns.map{|col| quote_column_name(col)}.join(', ')
|
215
|
+
execute compress_lines(<<-SQL)
|
216
|
+
CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
|
217
|
+
BEFORE UPDATE OF #{quoted_column_names} ON #{quote_table_name(table_name)} FOR EACH ROW
|
218
|
+
BEGIN
|
219
|
+
:new.#{quote_column_name(index_column)} := '1';
|
220
|
+
END;
|
221
|
+
SQL
|
222
|
+
end
|
223
|
+
|
224
|
+
def drop_index_column_trigger(index_name)
|
225
|
+
trigger_name = default_index_column_trigger_name(index_name)
|
226
|
+
execute "DROP TRIGGER #{quote_table_name(trigger_name)}" rescue nil
|
227
|
+
end
|
228
|
+
|
229
|
+
def default_datastore_procedure(index_name)
|
230
|
+
"#{index_name}_prc"
|
231
|
+
end
|
232
|
+
|
233
|
+
def default_datastore_name(index_name)
|
234
|
+
"#{index_name}_dst"
|
235
|
+
end
|
236
|
+
|
237
|
+
def default_storage_name(index_name)
|
238
|
+
"#{index_name}_sto"
|
239
|
+
end
|
240
|
+
|
241
|
+
def default_index_column_trigger_name(index_name)
|
242
|
+
"#{index_name}_trg"
|
243
|
+
end
|
244
|
+
|
245
|
+
module BaseClassMethods
|
246
|
+
# Declare that model table has context index defined.
|
247
|
+
# As a result <tt>contains</tt> class scope method is defined.
|
248
|
+
def has_context_index
|
249
|
+
extend ContextIndexClassMethods
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
module ContextIndexClassMethods
|
254
|
+
# Add context index condition.
|
255
|
+
case ::ActiveRecord::VERSION::MAJOR
|
256
|
+
when 3
|
257
|
+
def contains(column, query, options ={})
|
258
|
+
score_label = options[:label].to_i || 1
|
259
|
+
where("CONTAINS(#{connection.quote_column_name(column)}, ?, #{score_label}) > 0", query).
|
260
|
+
order("SCORE(#{score_label}) DESC")
|
261
|
+
end
|
262
|
+
when 2
|
263
|
+
def contains(column, query, options ={})
|
264
|
+
score_label = options[:label].to_i || 1
|
265
|
+
scoped(:conditions => ["CONTAINS(#{connection.quote_column_name(column)}, ?, #{score_label}) > 0", query],
|
266
|
+
:order => "SCORE(#{score_label}) DESC")
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
|
277
|
+
include ActiveRecord::ConnectionAdapters::OracleEnhancedContextIndex
|
278
|
+
end
|
279
|
+
|
280
|
+
ActiveRecord::Base.class_eval do
|
281
|
+
extend ActiveRecord::ConnectionAdapters::OracleEnhancedContextIndex::BaseClassMethods
|
282
|
+
end
|
@@ -26,7 +26,6 @@ end
|
|
26
26
|
# Add Unicode aware String#upcase and String#downcase methods when mb_chars method is called
|
27
27
|
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' && RUBY_VERSION >= '1.9'
|
28
28
|
begin
|
29
|
-
gem "unicode_utils", ">=1.0.0"
|
30
29
|
require "unicode_utils/upcase"
|
31
30
|
require "unicode_utils/downcase"
|
32
31
|
|
@@ -53,7 +52,9 @@ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' && RUBY_VERSION >= '1.9'
|
|
53
52
|
|
54
53
|
rescue LoadError
|
55
54
|
warning_message = "WARNING: Please install unicode_utils gem to support Unicode aware upcase and downcase for String#mb_chars"
|
56
|
-
if defined?(
|
55
|
+
if defined?(Rails.logger) && Rails.logger
|
56
|
+
Rails.logger.warn warning_message
|
57
|
+
elsif defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER
|
57
58
|
RAILS_DEFAULT_LOGGER.warn warning_message
|
58
59
|
else
|
59
60
|
STDERR.puts warning_message
|
@@ -32,7 +32,7 @@ module ActiveRecord #:nodoc:
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
if ActiveRecord::Base.
|
35
|
+
if ActiveRecord::Base.method_defined?(:changed?)
|
36
36
|
ActiveRecord::Base.class_eval do
|
37
37
|
include ActiveRecord::ConnectionAdapters::OracleEnhancedDirty::InstanceMethods
|
38
38
|
end
|
@@ -154,7 +154,7 @@ module ActiveRecord
|
|
154
154
|
@active = true
|
155
155
|
rescue NativeException => e
|
156
156
|
@active = false
|
157
|
-
if e.message =~ /^java\.sql\.
|
157
|
+
if e.message =~ /^java\.sql\.SQL(Recoverable)?Exception/
|
158
158
|
raise OracleEnhancedConnectionException, e.message
|
159
159
|
else
|
160
160
|
raise
|
@@ -169,7 +169,7 @@ module ActiveRecord
|
|
169
169
|
@active = true
|
170
170
|
rescue NativeException => e
|
171
171
|
@active = false
|
172
|
-
if e.message =~ /^java\.sql\.
|
172
|
+
if e.message =~ /^java\.sql\.SQL(Recoverable)?Exception/
|
173
173
|
raise OracleEnhancedConnectionException, e.message
|
174
174
|
else
|
175
175
|
raise
|
@@ -183,7 +183,7 @@ module ActiveRecord
|
|
183
183
|
begin
|
184
184
|
yield if block_given?
|
185
185
|
rescue NativeException => e
|
186
|
-
raise unless e.message =~ /^java\.sql\.
|
186
|
+
raise unless e.message =~ /^java\.sql\.SQL(Recoverable)?Exception: (Closed Connection|Io exception:|No more data to read from socket)/
|
187
187
|
@active = false
|
188
188
|
raise unless should_retry
|
189
189
|
should_retry = false
|
@@ -51,68 +51,106 @@ module ActiveRecord #:nodoc:
|
|
51
51
|
include_with_custom_methods
|
52
52
|
self.custom_delete_method = block
|
53
53
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
:create_without_timestamps
|
58
|
-
|
59
|
-
:create_without_callbacks
|
60
|
-
|
61
|
-
|
54
|
+
|
55
|
+
if ActiveRecord::VERSION::MAJOR < 3
|
56
|
+
def create_method_name_before_custom_methods #:nodoc:
|
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
|
62
64
|
end
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
:
|
68
|
-
|
69
|
-
:
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
65
|
+
|
66
|
+
def update_method_name_before_custom_methods #:nodoc:
|
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
|
74
76
|
end
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
77
|
+
|
78
|
+
def destroy_method_name_before_custom_methods #:nodoc:
|
79
|
+
if public_method_defined?(:destroy_without_callbacks)
|
80
|
+
:destroy_without_callbacks
|
81
|
+
else
|
82
|
+
:destroy
|
83
|
+
end
|
82
84
|
end
|
83
85
|
end
|
84
|
-
|
86
|
+
|
85
87
|
private
|
88
|
+
|
86
89
|
def include_with_custom_methods
|
87
90
|
unless included_modules.include? InstanceMethods
|
88
91
|
include InstanceMethods
|
89
92
|
end
|
90
93
|
end
|
91
94
|
end
|
92
|
-
|
95
|
+
|
93
96
|
module InstanceMethods #:nodoc:
|
94
97
|
def self.included(base)
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
98
|
+
# alias methods just for ActiveRecord 2.x
|
99
|
+
# for ActiveRecord 3.0 will just redefine create, update, delete methods which call super
|
100
|
+
if ActiveRecord::VERSION::MAJOR < 3
|
101
|
+
base.instance_eval do
|
102
|
+
alias_method :create_without_custom_method, create_method_name_before_custom_methods
|
103
|
+
alias_method create_method_name_before_custom_methods, :create_with_custom_method
|
104
|
+
alias_method :update_without_custom_method, update_method_name_before_custom_methods
|
105
|
+
alias_method update_method_name_before_custom_methods, :update_with_custom_method
|
106
|
+
alias_method :destroy_without_custom_method, destroy_method_name_before_custom_methods
|
107
|
+
alias_method destroy_method_name_before_custom_methods, :destroy_with_custom_method
|
108
|
+
private :create, :update
|
109
|
+
public :destroy
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
if ActiveRecord::VERSION::MAJOR >= 3
|
115
|
+
def destroy #:nodoc:
|
116
|
+
# check if class has custom delete method
|
117
|
+
if self.class.custom_delete_method
|
118
|
+
# wrap destroy in transaction
|
119
|
+
with_transaction_returning_status do
|
120
|
+
# run before/after callbacks defined in model
|
121
|
+
_run_destroy_callbacks { destroy_using_custom_method }
|
122
|
+
end
|
123
|
+
else
|
124
|
+
super
|
125
|
+
end
|
104
126
|
end
|
105
127
|
end
|
106
|
-
|
128
|
+
|
107
129
|
private
|
108
|
-
|
130
|
+
|
109
131
|
# Creates a record with custom create method
|
110
132
|
# and returns its id.
|
111
|
-
|
112
|
-
|
113
|
-
|
133
|
+
if ActiveRecord::VERSION::MAJOR < 3
|
134
|
+
def create_with_custom_method
|
135
|
+
# check if class has custom create method
|
136
|
+
self.class.custom_create_method ? create_using_custom_method : create_without_custom_method
|
137
|
+
end
|
138
|
+
else # ActiveRecord 3.x
|
139
|
+
def create
|
140
|
+
# check if class has custom create method
|
141
|
+
if self.class.custom_create_method
|
142
|
+
set_timestamps_before_custom_create_method
|
143
|
+
# run before/after callbacks defined in model
|
144
|
+
_run_create_callbacks { create_using_custom_method }
|
145
|
+
else
|
146
|
+
super
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def create_using_custom_method
|
114
152
|
self.class.connection.log_custom_method("custom create method", "#{self.class.name} Create") do
|
115
|
-
self.id = self.class.custom_create_method
|
153
|
+
self.id = instance_eval(&self.class.custom_create_method)
|
116
154
|
end
|
117
155
|
@new_record = false
|
118
156
|
id
|
@@ -120,12 +158,37 @@ module ActiveRecord #:nodoc:
|
|
120
158
|
|
121
159
|
# Updates the associated record with custom update method
|
122
160
|
# Returns the number of affected rows.
|
123
|
-
|
124
|
-
|
125
|
-
|
161
|
+
if ActiveRecord::VERSION::MAJOR < 3
|
162
|
+
def update_with_custom_method(attribute_names = @attributes.keys)
|
163
|
+
# check if class has custom create method
|
164
|
+
self.class.custom_update_method ? update_using_custom_method(attribute_names) : update_without_custom_method(attribute_names)
|
165
|
+
end
|
166
|
+
else # ActiveRecord 3.x
|
167
|
+
def update(attribute_names = @attributes.keys)
|
168
|
+
# check if class has custom update method
|
169
|
+
if self.class.custom_update_method
|
170
|
+
set_timestamps_before_custom_update_method
|
171
|
+
# run before/after callbacks defined in model
|
172
|
+
_run_update_callbacks do
|
173
|
+
# update just dirty attributes
|
174
|
+
if partial_updates?
|
175
|
+
# Serialized attributes should always be written in case they've been
|
176
|
+
# changed in place.
|
177
|
+
update_using_custom_method(changed | (attributes.keys & self.class.serialized_attributes.keys))
|
178
|
+
else
|
179
|
+
update_using_custom_method(attribute_names)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
else
|
183
|
+
super
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def update_using_custom_method(attribute_names)
|
126
189
|
return 0 if attribute_names.empty?
|
127
190
|
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
|
191
|
+
instance_eval(&self.class.custom_update_method)
|
129
192
|
end
|
130
193
|
1
|
131
194
|
end
|
@@ -133,12 +196,17 @@ module ActiveRecord #:nodoc:
|
|
133
196
|
# Deletes the record in the database with custom delete method
|
134
197
|
# and freezes this instance to reflect that no changes should
|
135
198
|
# be made (since they can't be persisted).
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
199
|
+
if ActiveRecord::VERSION::MAJOR < 3
|
200
|
+
def destroy_with_custom_method
|
201
|
+
# check if class has custom delete method
|
202
|
+
self.class.custom_delete_method ? destroy_using_custom_method : destroy_without_custom_method
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def destroy_using_custom_method
|
207
|
+
unless new_record? || @destroyed
|
140
208
|
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
|
209
|
+
instance_eval(&self.class.custom_delete_method)
|
142
210
|
end
|
143
211
|
end
|
144
212
|
|
@@ -146,6 +214,29 @@ module ActiveRecord #:nodoc:
|
|
146
214
|
freeze
|
147
215
|
end
|
148
216
|
|
217
|
+
if ActiveRecord::VERSION::MAJOR >= 3
|
218
|
+
def set_timestamps_before_custom_create_method
|
219
|
+
if record_timestamps
|
220
|
+
current_time = current_time_from_proper_timezone
|
221
|
+
|
222
|
+
write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
|
223
|
+
write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
|
224
|
+
|
225
|
+
write_attribute('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil?
|
226
|
+
write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_on.nil?
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def set_timestamps_before_custom_update_method
|
231
|
+
if record_timestamps && (!partial_updates? || changed?)
|
232
|
+
current_time = current_time_from_proper_timezone
|
233
|
+
|
234
|
+
write_attribute('updated_at', current_time) if respond_to?(:updated_at)
|
235
|
+
write_attribute('updated_on', current_time) if respond_to?(:updated_on)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
149
240
|
end
|
150
241
|
|
151
242
|
end
|