enumerate_by 0.4.0 → 0.4.1

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.rdoc CHANGED
@@ -1,5 +1,9 @@
1
1
  == master
2
2
 
3
+ == 0.4.1 / 2009-05-01
4
+
5
+ * Improve #fast_bootstrap speed by 50% by using the connection directly
6
+
3
7
  == 0.4.0 / 2009-04-30
4
8
 
5
9
  * Allow cache to be cleared on a per-enumeration basis
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require 'rake/contrib/sshpublisher'
5
5
 
6
6
  spec = Gem::Specification.new do |s|
7
7
  s.name = 'enumerate_by'
8
- s.version = '0.4.0'
8
+ s.version = '0.4.1'
9
9
  s.platform = Gem::Platform::RUBY
10
10
  s.summary = 'Adds support for declaring an ActiveRecord class as an enumeration'
11
11
 
data/lib/enumerate_by.rb CHANGED
@@ -261,16 +261,17 @@ module EnumerateBy
261
261
  defaults = attributes.delete(:defaults)
262
262
 
263
263
  # Update with new attributes
264
- record = !existing.include?(attributes[:id]) ? new(attributes) : begin
265
- record = existing[attributes[:id]]
266
- record.attributes = attributes
267
- record
268
- end
264
+ record =
265
+ if record = existing[attributes[:id]]
266
+ attributes.merge!(defaults.delete_if {|attribute, value| record.send("#{attribute}?")}) if defaults
267
+ record.attributes = attributes
268
+ record
269
+ else
270
+ attributes.merge!(defaults) if defaults
271
+ new(attributes)
272
+ end
269
273
  record.id = attributes[:id]
270
274
 
271
- # Only update defaults if they aren't already specified
272
- defaults.each {|attribute, value| record[attribute] = value unless record.send("#{attribute}?")} if defaults
273
-
274
275
  # Force failed saves to stop execution
275
276
  record.save!
276
277
  record
@@ -281,39 +282,63 @@ module EnumerateBy
281
282
  end
282
283
 
283
284
  # Quickly synchronizes the given records with the existing ones. This
284
- # disables certain features of ActiveRecord in order to provide a speed
285
- # boost, including:
285
+ # skips ActiveRecord altogether, interacting directly with the connection
286
+ # instead. As a result, certain features are not available when being
287
+ # bootstrapped, including:
286
288
  # * Callbacks
287
289
  # * Validations
290
+ # * Transactions
288
291
  # * Timestamps
289
292
  # * Dirty attributes
290
293
  #
291
- # This produces a noticeable performance increase when bootstrapping more
294
+ # Also note that records are created directly without creating instances
295
+ # of the model. As a result, all of the attributes for the record must
296
+ # be specified.
297
+ #
298
+ # This produces a significant performance increase when bootstrapping more
292
299
  # than several hundred records.
293
300
  #
294
301
  # See EnumerateBy::Bootstrapped#bootstrap for information about usage.
295
302
  def fast_bootstrap(*records)
296
- features = {:callbacks => %w(create create_or_update valid?), :dirty => %w(write_attribute), :validation => %w(save save!)}
297
- features.each do |feature, methods|
298
- methods.each do |method|
299
- method, punctuation = method.sub(/([?!=])$/, ''), $1
300
- alias_method "#{method}_without_bootstrap#{punctuation}", "#{method}#{punctuation}"
301
- alias_method "#{method}#{punctuation}", "#{method}_without_#{feature}#{punctuation}"
302
- end
303
- end
304
- original_record_timestamps = self.record_timestamps
305
- self.record_timestamps = false
303
+ # Remove records that are no longer being used
304
+ records.flatten!
305
+ delete_all(['id NOT IN (?)', records.map {|record| record[:id]}])
306
306
 
307
- bootstrap(*records)
308
- ensure
309
- features.each do |feature, methods|
310
- methods.each do |method|
311
- method, punctuation = method.sub(/([?!=])$/, ''), $1
312
- alias_method "#{method}_without_#{feature}#{punctuation}", "#{method}#{punctuation}"
313
- alias_method "#{method}#{punctuation}", "#{method}_without_bootstrap#{punctuation}"
307
+ # Find remaining existing records (to be updated)
308
+ quoted_table_name = self.quoted_table_name
309
+ existing = connection.select_all("SELECT * FROM #{quoted_table_name}").inject({}) {|existing, record| existing[record['id'].to_i] = record; existing}
310
+
311
+ records.each do |attributes|
312
+ attributes.stringify_keys!
313
+ if defaults = attributes.delete('defaults')
314
+ defaults.stringify_keys!
315
+ end
316
+
317
+ id = attributes['id']
318
+ if existing_attributes = existing[id]
319
+ # Record exists: Update attributes
320
+ attributes.delete('id')
321
+ attributes.merge!(defaults.delete_if {|attribute, value| !existing_attributes[attribute].nil?}) if defaults
322
+ update_all(attributes, :id => id)
323
+ else
324
+ # Record doesn't exist: create new one
325
+ attributes.merge!(defaults) if defaults
326
+ column_names = []
327
+ values = []
328
+
329
+ attributes.each do |column_name, value|
330
+ column_names << connection.quote_column_name(column_name)
331
+ values << connection.quote(value, columns_hash[column_name])
332
+ end
333
+
334
+ connection.insert(
335
+ "INSERT INTO #{quoted_table_name} (#{column_names * ', '}) VALUES(#{values * ', '})",
336
+ "#{name} Create", primary_key, id, sequence_name
337
+ )
314
338
  end
315
339
  end
316
- self.record_timestamps = original_record_timestamps
340
+
341
+ true
317
342
  end
318
343
  end
319
344
 
@@ -324,27 +324,81 @@ class EnumerationBootstrappedWithDefaultsTest < ActiveRecord::TestCase
324
324
  end
325
325
 
326
326
  class EnumerationFastBootstrappedTest < ActiveRecord::TestCase
327
- def test_should_not_run_validations
328
- assert_raise(ActiveRecord::StatementInvalid) { Color.fast_bootstrap({:id => 1, :name => nil}) }
327
+ def setup
328
+ @result = Color.fast_bootstrap(
329
+ {:id => 1, :name => 'red'},
330
+ {:id => 2, :name => 'green'}
331
+ )
332
+ end
333
+
334
+ def test_should_not_raise_exception_if_id_not_specified
335
+ assert_nothing_raised { Color.fast_bootstrap({:name => 'red'}, {:name => 'green'}) }
336
+ assert_equal 2, Color.count
329
337
  end
330
338
 
331
- def test_should_still_record_timestamps_after_bootstrap
332
- Color.fast_bootstrap({:id => 1, :name => 'red'})
333
- assert Color.record_timestamps
339
+ def test_should_raise_exception_if_query_fails
340
+ assert_raise(ActiveRecord::StatementInvalid) { Color.fast_bootstrap({:id => 1, :name => nil}, {:id => 2, :name => 'green'}) }
334
341
  end
335
342
 
336
- def test_should_still_run_validations_after_bootstrap
337
- Color.fast_bootstrap({:id => 1, :name => 'red'})
343
+ def test_should_flatten_bootstrap_records
344
+ Color.bootstrap(
345
+ [{:id => 1, :name => 'red'}],
346
+ [{:id => 2, :name => 'green'}]
347
+ )
348
+ assert_equal 2, Color.count
349
+ end
350
+
351
+ def test_should_create_records
352
+ assert @result
353
+ assert_not_nil Color.find_by_name('red')
354
+ assert_not_nil Color.find_by_name('green')
355
+ end
356
+ end
357
+
358
+ class EnumeratioFastBootstrappedWithExistingRecordsTest < ActiveRecord::TestCase
359
+ def setup
360
+ @red = create_color(:name => 'RED')
361
+ @green = create_color(:name => 'GREEN')
362
+
363
+ Color.fast_bootstrap(
364
+ {:id => @red.id, :name => 'red'},
365
+ {:id => @green.id, :name => 'green'}
366
+ )
338
367
 
339
- color = Color.new
340
- assert !color.save
341
- assert_raise(ActiveRecord::RecordInvalid) { color.save! }
368
+ @red.reload
369
+ @green.reload
342
370
  end
343
371
 
344
- def test_should_still_track_changed_attributes_after_bootstrap
345
- Color.fast_bootstrap({:id => 1, :name => 'red'})
372
+ def test_should_synchronize_all_attributes
373
+ assert_equal 'red', @red.name
374
+ assert_equal 'green', @green.name
375
+ end
376
+ end
377
+
378
+ class EnumerationFastBootstrappedWithDefaultsTest < ActiveRecord::TestCase
379
+ def setup
380
+ @red = create_color(:name => 'RED', :html => '#f00')
381
+ @green = create_color(:name => 'GREEN')
382
+
383
+ Color.fast_bootstrap(
384
+ {:id => @red.id, :name => 'red', :defaults => {:html => '#ff0000'}},
385
+ {:id => @green.id, :name => 'green', :defaults => {:html => '#00ff00'}}
386
+ )
346
387
 
347
- color = Color.new(:name => 'red')
348
- assert color.changed?
388
+ @red.reload
389
+ @green.reload
390
+ end
391
+
392
+ def test_should_update_all_non_default_attributes
393
+ assert_equal 'red', @red.name
394
+ assert_equal 'green', @green.name
395
+ end
396
+
397
+ def test_should_not_update_default_attributes_if_defined
398
+ assert_equal '#f00', @red.html
399
+ end
400
+
401
+ def test_should_update_default_attributes_if_not_defined
402
+ assert_equal '#00ff00', @green.html
349
403
  end
350
404
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enumerate_by
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Pfeifer
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-30 00:00:00 -04:00
12
+ date: 2009-05-01 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15