ar-extensions 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,3 +1,11 @@
1
+ 2007-07-20 zdennis <zdennis@dhcp-22.atomicobject.localnet>
2
+
3
+ * Added patch from Michael Flester to fix bug with created_at/updated_at fields to use proper timezone.
4
+
5
+ 2007-07-18 zdennis <zdennis@dhcp-22.atomicobject.localnet>
6
+
7
+ * Added patch for Oracle support from Michael Flester.
8
+
1
9
  2007-05-05 zdennis <zdennis@elijah.local>
2
10
 
3
11
  * Added ActiveRecord::Base::AbstractAdapter#synchronize method which allows a mass re-synchronization of ActiveRecord instances. This is similar to ActiveRecord::Base#reload except that it works on multiple instances at a time rather then one. This sends one query for all instances rather then 1 per instance reloaded.
data/README CHANGED
@@ -9,6 +9,15 @@ Email: zach.dennis@gmail.com
9
9
  - For How-To information see http://www.continuousthinking.com/tags/arext
10
10
  - For project information and feedback please consult http://rubyforge.org/projects/arext/
11
11
  - For release information please see below
12
+
13
+ AciveRecord::Extensions 0.7.0
14
+ -----------------------------
15
+ July 20th, 2007
16
+ - Fixes timezone issue (thanks Michael Flester)
17
+ - Adds better finder and import support for Oracle (thanks Michael Flester)
18
+ - Committed patch to fix MySQL query padding, thanks to Gabe da Silveira
19
+ - Added functionality for MySQL to work with created_on, created_at, updated_on or updated_at fields
20
+ - Added more test coverage for import functionality
12
21
 
13
22
  ActiveRecord::Extensions 0.6.0
14
23
  ------------------------------
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ task :boot do
12
12
  require File.expand_path( File.join( DIR, 'db/migrate/version' ) )
13
13
  end
14
14
 
15
- ADAPTERS = %w( mysql postgresql sqlite sqlite3 )
15
+ ADAPTERS = %w( mysql postgresql sqlite sqlite3 oracle )
16
16
 
17
17
  namespace :db do
18
18
 
@@ -49,6 +49,9 @@ ActiveRecord::Schema.define do
49
49
  t.column :publisher, :string, :null=>false
50
50
  t.column :author_name, :string, :null=>false
51
51
  t.column :created_at, :time
52
+ t.column :created_on, :datetime
53
+ t.column :updated_at, :time
54
+ t.column :updated_on, :datetime
52
55
  t.column :topic_id, :integer
53
56
  end
54
57
 
@@ -20,6 +20,9 @@ ActiveRecord::Schema.define do
20
20
  t.column :publisher, :string, :null=>false
21
21
  t.column :author_name, :string, :null=>false
22
22
  t.column :created_at, :datetime
23
+ t.column :created_on, :datetime
24
+ t.column :updated_at, :datetime
25
+ t.column :updated_on, :datetime
23
26
  t.column :topic_id, :integer
24
27
  end
25
28
  execute "ALTER TABLE books ADD FULLTEXT( `title`, `publisher`, `author_name` )"
@@ -1,4 +1,4 @@
1
1
  class SchemaInfo < ActiveRecord::Base
2
2
  set_table_name 'schema_info'
3
- VERSION = 6
3
+ VERSION = 8
4
4
  end
@@ -2,7 +2,8 @@ module ActiveRecord # :nodoc:
2
2
  module ConnectionAdapters # :nodoc:
3
3
  class AbstractAdapter # :nodoc:
4
4
  NO_MAX_PACKET = 0
5
-
5
+ QUERY_OVERHEAD = 8 #This was shown to be true for MySQL, but it's not clear where the overhead is from.
6
+
6
7
  # +sql+ can be a single string or an array. If it is an array all
7
8
  # elements that are in position >= 1 will be appended to the final SQL.
8
9
  def insert_many( sql, values, *args ) # :nodoc:
@@ -15,7 +16,7 @@ module ActiveRecord # :nodoc:
15
16
  [ sql.shift, sql.join( ' ' ) ]
16
17
  end
17
18
 
18
- sql_size = base_sql.size + post_sql.size
19
+ sql_size = QUERY_OVERHEAD + base_sql.size + post_sql.size
19
20
 
20
21
  # the number of bytes the requested insert statement values will take up
21
22
  values_in_bytes = self.class.sum_sizes( *values )
@@ -41,7 +42,7 @@ module ActiveRecord # :nodoc:
41
42
  insert( sql2insert, *args )
42
43
  end
43
44
  end
44
-
45
+
45
46
  number_of_inserts
46
47
  end
47
48
 
@@ -0,0 +1,9 @@
1
+ module ActiveRecord # :nodoc:
2
+ module ConnectionAdapters # :nodoc:
3
+ class OracleAdapter # :nodoc:
4
+ def supports_import?
5
+ true
6
+ end
7
+ end
8
+ end
9
+ end
@@ -72,7 +72,7 @@ require 'forwardable'
72
72
  #
73
73
  # == Importing Lots of Data
74
74
  #
75
- # ActiveRecord executes a single INSERT statement for every cal to 'create'
75
+ # ActiveRecord executes a single INSERT statement for every call to 'create'
76
76
  # and for every call to 'save' on a new model object. When you have only
77
77
  # a handful of records to create or save this is not a big deal, but when
78
78
  # you have hundreds, thousands or hundreds of thousands of records
@@ -203,8 +203,8 @@ module ActiveRecord::Extensions
203
203
  # Model.find :all, :conditions=>{ 'number_lt'=>100 }
204
204
  # Model.find :all, :conditions=>{ 'number_gte'=>100 }
205
205
  # Model.find :all, :conditions=>{ 'number_lte'=>100 }
206
- class Comparison
207
-
206
+ class Comparison
207
+
208
208
  SUFFIX_MAP = { 'eq'=>'=', 'lt'=>'<', 'lte'=>'<=', 'gt'=>'>', 'gte'=>'>=', 'ne'=>'!=', 'not'=>'!=' }
209
209
 
210
210
  def self.process( key, val, caller )
@@ -394,6 +394,19 @@ module ActiveRecord::Extensions
394
394
  end
395
395
 
396
396
  end
397
+
398
+ # ActiveRecord::Extension for implementing Regexp implementation for Oracle.
399
+ # See documention for RegexpBase.
400
+ #
401
+ class OracleRegexp < RegexpBase
402
+
403
+ def self.process( key, val, caller )
404
+ return nil unless val.is_a?( Regexp )
405
+ r = field_result( key, caller )
406
+ return Result.new( "#{r.negate? ? ' NOT ':''} REGEXP_LIKE(#{caller.table_name}.#{r.fieldname} , ?)", val )
407
+ end
408
+
409
+ end
397
410
 
398
411
 
399
412
  # ActiveRecord::Extension for implementing Regexp implementation for MySQL.
@@ -473,7 +486,8 @@ end
473
486
  register MySQLRegexp, :adapters=>[ :mysql ]
474
487
  register PostgreSQLRegexp, :adapters=>[ :postgresql ]
475
488
  register SqliteRegexp, :adapters =>[ :sqlite ]
476
- register DatetimeSupport, :adapters =>[ :mysql, :sqlite ]
489
+ register OracleRegexp, :adapters =>[ :oracle ]
490
+ register DatetimeSupport, :adapters =>[ :mysql, :sqlite, :oracle ]
477
491
  end
478
492
 
479
493
 
@@ -24,7 +24,7 @@ class ActiveRecord::Base
24
24
  arg = sanitize_sql_by_way_of_duck_typing( arg )
25
25
  elsif arg.is_a?( Hash )
26
26
  arg = sanitize_sql_from_hash( arg )
27
- elsif arg.size == 2 and arg.first.is_a?( String ) and arg.last.is_a?( Hash )
27
+ elsif arg.is_a?( Array ) and arg.size == 2 and arg.first.is_a?( String ) and arg.last.is_a?( Hash )
28
28
  arg = sanitize_sql_from_string_and_hash( arg )
29
29
  end
30
30
  sanitize_sql_orig( arg )
@@ -18,6 +18,16 @@ end
18
18
 
19
19
  class ActiveRecord::Base
20
20
  class << self
21
+
22
+ # use tz as set in ActiveRecord::Base
23
+ tproc = @@default_timezone == :utc ? lambda { Time.now.utc } : lambda { Time.now }
24
+ AREXT_RAILS_COLUMNS = {
25
+ :create => { "created_on" => tproc ,
26
+ "created_at" => tproc },
27
+ :update => { "updated_on" => tproc ,
28
+ "updated_at" => tproc }
29
+ }
30
+ AREXT_RAILS_COLUMN_NAMES = AREXT_RAILS_COLUMNS[:create].keys + AREXT_RAILS_COLUMNS[:update].keys
21
31
 
22
32
  # Returns true if the current database connection adapter
23
33
  # supports import functionality, otherwise returns false.
@@ -26,7 +36,7 @@ class ActiveRecord::Base
26
36
  rescue NoMethodError
27
37
  false
28
38
  end
29
-
39
+
30
40
  # Returns true if the current database connection adapter
31
41
  # supports on duplicate key update functionality, otherwise
32
42
  # returns false.
@@ -132,7 +142,13 @@ class ActiveRecord::Base
132
142
  #
133
143
  # BlogPost.import columns, attributes, :on_duplicate_key_update=>{ :title => :title }
134
144
  #
145
+ # = Returns
146
+ # This returns an object which responds to +failed_instances+ and +num_inserts+.
147
+ # * failed_instances - an array of objects that fails validation and were not committed to the database. An empty array if no validation is performed.
148
+ # * num_inserts - the number of insert statements it took to import the data
135
149
  def import( *args )
150
+ @logger = Logger.new(STDOUT)
151
+ @logger.level = Logger::DEBUG
136
152
  options = { :validate=>true }
137
153
  options.merge!( args.pop ) if args.last.is_a? Hash
138
154
 
@@ -144,15 +160,18 @@ class ActiveRecord::Base
144
160
  else
145
161
  models = args.first
146
162
  column_names = self.column_names.dup
147
- column_names.delete( self.primary_key ) unless options[ :on_duplicate_key_update ]
148
163
  end
149
164
 
150
- array_of_attributes = models.inject( [] ) do |arr,model|
151
- attributes = []
152
- column_names.each do |name|
153
- attributes << model.send( "#{name}_before_type_cast" )
154
- end
155
- arr << attributes
165
+ array_of_attributes = []
166
+ models.each do |model|
167
+ # this next line breaks sqlite.so with a segmentation fault
168
+ # if model.new_record? || options[:on_duplicate_key_update]
169
+ attributes = []
170
+ column_names.each do |name|
171
+ attributes << model.send( "#{name}_before_type_cast" )
172
+ end
173
+ array_of_attributes << attributes
174
+ # end
156
175
  end
157
176
  # supports 2-element array and array
158
177
  elsif args.size == 2 and args.first.is_a?( Array ) and args.last.is_a?( Array )
@@ -160,22 +179,37 @@ class ActiveRecord::Base
160
179
  else
161
180
  raise ArgumentError.new( "Invalid arguments!" )
162
181
  end
163
-
182
+
183
+ # Force the primary key col into the insert if it's not
184
+ # on the list and we are using a sequence and stuff a nil
185
+ # value for it into each row so the sequencer will fire later
186
+ if !column_names.include?(primary_key) && sequence_name && connection.prefetch_primary_key?
187
+ column_names << primary_key
188
+ array_of_attributes.each { |a| a << nil }
189
+ end
190
+
164
191
  is_validating = options.delete( :validate )
165
-
192
+
166
193
  # dup the passed in array so we don't modify it unintentionally
167
194
  array_of_attributes = array_of_attributes.dup
168
- number_of_inserts = if is_validating
195
+
196
+ # record timestamps unless disabled in ActiveRecord::Base
197
+ if record_timestamps
198
+ add_special_rails_stamps column_names, array_of_attributes, options
199
+ end
200
+
201
+ return_obj = if is_validating
169
202
  import_with_validations( column_names, array_of_attributes, options )
170
203
  else
171
- import_without_validations_or_callbacks( column_names, array_of_attributes, options )
204
+ num_inserts = import_without_validations_or_callbacks( column_names, array_of_attributes, options )
205
+ OpenStruct.new :failed_instances=>[], :num_inserts=>num_inserts
172
206
  end
173
-
174
207
  if options[:synchronize]
175
208
  synchronize( options[:synchronize] )
176
209
  end
177
-
178
- number_of_inserts
210
+
211
+ return_obj.num_inserts = 0 if return_obj.num_inserts.nil?
212
+ return_obj
179
213
  end
180
214
 
181
215
  # TODO import_from_table needs to be implemented.
@@ -184,7 +218,9 @@ class ActiveRecord::Base
184
218
 
185
219
  # Imports the passed in +column_names+ and +array_of_attributes+
186
220
  # given the passed in +options+ Hash with validations. Returns an
187
- # array of instances that failed validations. See
221
+ # object with the methods +failed_instances+ and +num_inserts+.
222
+ # +failed_instances+ is an array of instances that failed validations.
223
+ # +num_inserts+ is the number of inserts it took to import the data. See
188
224
  # ActiveRecord::Base.import for more information on
189
225
  # +column_names+, +array_of_attributes+ and +options+.
190
226
  def import_with_validations( column_names, array_of_attributes, options={} )
@@ -192,10 +228,10 @@ class ActiveRecord::Base
192
228
 
193
229
  # create instances for each of our column/value sets
194
230
  arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )
195
-
231
+
196
232
  # keep track of the instance and the position it is currently at. if this fails
197
233
  # validation we'll use the index to remove it from the array_of_attributes
198
- arr.each_with_index do |hsh,i|
234
+ arr.each_with_index do |hsh,i|
199
235
  instance = new( hsh )
200
236
  if not instance.valid?
201
237
  array_of_attributes[ i ] = nil
@@ -204,10 +240,8 @@ class ActiveRecord::Base
204
240
  end
205
241
  array_of_attributes.compact!
206
242
 
207
- if not array_of_attributes.empty?
208
- import_without_validations_or_callbacks( column_names, array_of_attributes, options )
209
- end
210
- failed_instances
243
+ num_inserts = array_of_attributes.empty? ? 0 : import_without_validations_or_callbacks( column_names, array_of_attributes, options )
244
+ OpenStruct.new :failed_instances=>failed_instances, :num_inserts => num_inserts
211
245
  end
212
246
 
213
247
  # Imports the passed in +column_names+ and +array_of_attributes+
@@ -224,15 +258,20 @@ class ActiveRecord::Base
224
258
  if not supports_import?
225
259
  columns_sql = "(" + escaped_column_names.join( ',' ) + ")"
226
260
  insert_statements, values = [], []
261
+ number_inserted = 0
227
262
  array_of_attributes.each do |arr|
228
263
  my_values = []
229
264
  arr.each_with_index do |val,j|
230
- my_values << connection.quote( val, columns[j] )
265
+ if sequence_name && column_names[j] == primary_key && val.nil?
266
+ my_values << "#{sequence_name}.nextval"
267
+ else
268
+ my_values << connection.quote( val, columns[j] )
269
+ end
231
270
  end
232
271
  insert_statements << "INSERT INTO #{self.table_name} #{columns_sql} VALUES(" + my_values.join( ',' ) + ")"
233
- connection.execute( insert_statements.last )
272
+ number_inserted = number_inserted + connection.execute( insert_statements.last )
234
273
  end
235
- return
274
+ return number_inserted
236
275
  else
237
276
 
238
277
  # generate the sql
@@ -255,6 +294,42 @@ class ActiveRecord::Base
255
294
 
256
295
 
257
296
  private
297
+
298
+
299
+ def add_special_rails_stamps( column_names, array_of_attributes, options )
300
+ AREXT_RAILS_COLUMNS[:create].each_pair do |key, blk|
301
+ if self.column_names.include?(key)
302
+ value = blk.call
303
+ if index=column_names.index(key)
304
+ # replace every instance of the array of attributes with our value
305
+ array_of_attributes.each{ |arr| arr[index] = value }
306
+ else
307
+ column_names << key
308
+ array_of_attributes.each { |arr| arr << value }
309
+ end
310
+ end
311
+ end
312
+
313
+ AREXT_RAILS_COLUMNS[:update].each_pair do |key, blk|
314
+ if self.column_names.include?(key)
315
+ value = blk.call
316
+ if index=column_names.index(key)
317
+ # replace every instance of the array of attributes with our value
318
+ array_of_attributes.each{ |arr| arr[index] = value }
319
+ else
320
+ column_names << key
321
+ array_of_attributes.each { |arr| arr << value }
322
+ end
323
+
324
+ if options[:on_duplicate_key_update]
325
+ options[:on_duplicate_key_update] << key.to_sym if options[:on_duplicate_key_update].is_a?(Array)
326
+ options[:on_duplicate_key_update][key.to_sym] = key.to_sym if options[:on_duplicate_key_update].is_a?(Hash)
327
+ else
328
+ options[:on_duplicate_key_update] = [ key.to_sym ]
329
+ end
330
+ end
331
+ end
332
+ end
258
333
 
259
334
  # Returns an Array of Hashes for the passed in +column_names+ and +array_of_attributes+.
260
335
  def validations_array_for_column_names_and_attributes( column_names, array_of_attributes ) # :nodoc:
@@ -2,7 +2,7 @@
2
2
  module ActiveRecord # :nodoc:
3
3
  module Extensions # :nodoc:
4
4
  module VERSION
5
- MAJOR, MINOR, REVISION = %W( 0 6 0 )
5
+ MAJOR, MINOR, REVISION = %W( 0 7 0 )
6
6
  STRING = [ MAJOR, MINOR, REVISION ].join( '.' )
7
7
  end
8
8
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: ar-extensions
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.6.0
7
- date: 2007-05-05 00:00:00 -04:00
6
+ version: 0.7.0
7
+ date: 2007-07-21 00:00:00 -04:00
8
8
  summary: Extends ActiveRecord functionality.
9
9
  require_paths:
10
10
  - lib
@@ -44,6 +44,7 @@ files:
44
44
  - config/postgresql.schema
45
45
  - lib/ar-extensions/adapters/abstract_adapter.rb
46
46
  - lib/ar-extensions/adapters/mysql_adapter.rb
47
+ - lib/ar-extensions/adapters/oracle.rb
47
48
  - lib/ar-extensions/adapters/postgresql.rb
48
49
  - lib/ar-extensions/csv.rb
49
50
  - lib/ar-extensions/extensions.rb