ar-extensions 0.6.0 → 0.7.0

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/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