activerecord 2.3.2 → 2.3.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (55) hide show
  1. data/CHANGELOG +11 -0
  2. data/Rakefile +10 -10
  3. data/lib/active_record/associations.rb +67 -30
  4. data/lib/active_record/associations/association_collection.rb +9 -5
  5. data/lib/active_record/associations/association_proxy.rb +2 -2
  6. data/lib/active_record/associations/belongs_to_association.rb +22 -4
  7. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -1
  8. data/lib/active_record/associations/has_one_through_association.rb +8 -8
  9. data/lib/active_record/autosave_association.rb +11 -6
  10. data/lib/active_record/base.rb +16 -21
  11. data/lib/active_record/batches.rb +23 -15
  12. data/lib/active_record/calculations.rb +5 -13
  13. data/lib/active_record/connection_adapters/postgresql_adapter.rb +66 -22
  14. data/lib/active_record/connection_adapters/sqlite_adapter.rb +3 -0
  15. data/lib/active_record/fixtures.rb +5 -4
  16. data/lib/active_record/named_scope.rb +2 -2
  17. data/lib/active_record/schema_dumper.rb +5 -1
  18. data/lib/active_record/serialization.rb +3 -2
  19. data/lib/active_record/serializers/json_serializer.rb +8 -18
  20. data/lib/active_record/session_store.rb +9 -1
  21. data/lib/active_record/timestamp.rb +39 -9
  22. data/lib/active_record/validations.rb +1 -1
  23. data/lib/active_record/version.rb +1 -1
  24. data/test/cases/associations/belongs_to_associations_test.rb +102 -4
  25. data/test/cases/associations/eager_test.rb +12 -0
  26. data/test/cases/associations/has_many_associations_test.rb +6 -0
  27. data/test/cases/associations/has_one_through_associations_test.rb +8 -1
  28. data/test/cases/associations/inner_join_association_test.rb +5 -0
  29. data/test/cases/autosave_association_test.rb +22 -0
  30. data/test/cases/base_test.rb +2 -2
  31. data/test/cases/calculations_test.rb +8 -14
  32. data/test/cases/copy_table_test_sqlite.rb +5 -5
  33. data/test/cases/finder_test.rb +6 -0
  34. data/test/cases/fixtures_test.rb +5 -0
  35. data/test/cases/helper.rb +1 -2
  36. data/test/cases/json_serialization_test.rb +57 -57
  37. data/test/cases/method_scoping_test.rb +13 -3
  38. data/test/cases/reflection_test.rb +5 -5
  39. data/test/cases/schema_dumper_test.rb +17 -7
  40. data/test/cases/schema_test_postgresql.rb +76 -0
  41. data/test/cases/timestamp_test.rb +75 -0
  42. data/test/debug.log +415 -0
  43. data/test/fixtures/fixture_database.sqlite3 +0 -0
  44. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  45. data/test/models/author.rb +4 -0
  46. data/test/models/company.rb +7 -7
  47. data/test/models/developer.rb +10 -0
  48. data/test/models/essay.rb +3 -0
  49. data/test/models/pet.rb +1 -1
  50. data/test/models/project.rb +1 -1
  51. data/test/models/reply.rb +2 -1
  52. data/test/models/topic.rb +1 -0
  53. data/test/models/toy.rb +2 -0
  54. data/test/schema/schema.rb +15 -0
  55. metadata +6 -3
@@ -4,10 +4,12 @@ module ActiveRecord
4
4
  base.extend(ClassMethods)
5
5
  end
6
6
 
7
- # When processing large numbers of records, it's often a good idea to do so in batches to prevent memory ballooning.
7
+ # When processing large numbers of records, it's often a good idea to do
8
+ # so in batches to prevent memory ballooning.
8
9
  module ClassMethods
9
- # Yields each record that was found by the find +options+. The find is performed by find_in_batches
10
- # with a batch size of 1000 (or as specified by the +batch_size+ option).
10
+ # Yields each record that was found by the find +options+. The find is
11
+ # performed by find_in_batches with a batch size of 1000 (or as
12
+ # specified by the <tt>:batch_size</tt> option).
11
13
  #
12
14
  # Example:
13
15
  #
@@ -15,9 +17,10 @@ module ActiveRecord
15
17
  # person.party_all_night!
16
18
  # end
17
19
  #
18
- # Note: This method is only intended to use for batch processing of large amounts of records that wouldn't fit in
19
- # memory all at once. If you just need to loop over less than 1000 records, it's probably better just to use the
20
- # regular find methods.
20
+ # Note: This method is only intended to use for batch processing of
21
+ # large amounts of records that wouldn't fit in memory all at once. If
22
+ # you just need to loop over less than 1000 records, it's probably
23
+ # better just to use the regular find methods.
21
24
  def find_each(options = {})
22
25
  find_in_batches(options) do |records|
23
26
  records.each { |record| yield record }
@@ -26,17 +29,22 @@ module ActiveRecord
26
29
  self
27
30
  end
28
31
 
29
- # Yields each batch of records that was found by the find +options+ as an array. The size of each batch is
30
- # set by the +batch_size+ option; the default is 1000.
32
+ # Yields each batch of records that was found by the find +options+ as
33
+ # an array. The size of each batch is set by the <tt>:batch_size</tt>
34
+ # option; the default is 1000.
31
35
  #
32
- # You can control the starting point for the batch processing by supplying the +start+ option. This is especially
33
- # useful if you want multiple workers dealing with the same processing queue. You can make worker 1 handle all the
34
- # records between id 0 and 10,000 and worker 2 handle from 10,000 and beyond (by setting the +start+ option on that
35
- # worker).
36
+ # You can control the starting point for the batch processing by
37
+ # supplying the <tt>:start</tt> option. This is especially useful if you
38
+ # want multiple workers dealing with the same processing queue. You can
39
+ # make worker 1 handle all the records between id 0 and 10,000 and
40
+ # worker 2 handle from 10,000 and beyond (by setting the <tt>:start</tt>
41
+ # option on that worker).
36
42
  #
37
- # It's not possible to set the order. That is automatically set to ascending on the primary key ("id ASC")
38
- # to make the batch ordering work. This also mean that this method only works with integer-based primary keys.
39
- # You can't set the limit either, that's used to control the the batch sizes.
43
+ # It's not possible to set the order. That is automatically set to
44
+ # ascending on the primary key ("id ASC") to make the batch ordering
45
+ # work. This also mean that this method only works with integer-based
46
+ # primary keys. You can't set the limit either, that's used to control
47
+ # the the batch sizes.
40
48
  #
41
49
  # Example:
42
50
  #
@@ -141,30 +141,22 @@ module ActiveRecord
141
141
  def construct_count_options_from_args(*args)
142
142
  options = {}
143
143
  column_name = :all
144
-
144
+
145
145
  # We need to handle
146
146
  # count()
147
147
  # count(:column_name=:all)
148
148
  # count(options={})
149
149
  # count(column_name=:all, options={})
150
- # selects specified by scopes
151
150
  case args.size
152
- when 0
153
- column_name = scope(:find)[:select] if scope(:find)
154
151
  when 1
155
- if args[0].is_a?(Hash)
156
- column_name = scope(:find)[:select] if scope(:find)
157
- options = args[0]
158
- else
159
- column_name = args[0]
160
- end
152
+ args[0].is_a?(Hash) ? options = args[0] : column_name = args[0]
161
153
  when 2
162
154
  column_name, options = args
163
155
  else
164
156
  raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}"
165
- end
166
-
167
- [column_name || :all, options]
157
+ end if args.size > 0
158
+
159
+ [column_name, options]
168
160
  end
169
161
 
170
162
  def construct_calculation_sql(operation, column_name, options) #:nodoc:
@@ -287,7 +287,13 @@ module ActiveRecord
287
287
 
288
288
  # Escapes binary strings for bytea input to the database.
289
289
  def escape_bytea(value)
290
- if PGconn.respond_to?(:escape_bytea)
290
+ if @connection.respond_to?(:escape_bytea)
291
+ self.class.instance_eval do
292
+ define_method(:escape_bytea) do |value|
293
+ @connection.escape_bytea(value) if value
294
+ end
295
+ end
296
+ elsif PGconn.respond_to?(:escape_bytea)
291
297
  self.class.instance_eval do
292
298
  define_method(:escape_bytea) do |value|
293
299
  PGconn.escape_bytea(value) if value
@@ -376,7 +382,13 @@ module ActiveRecord
376
382
 
377
383
  # Quotes strings for use in SQL input in the postgres driver for better performance.
378
384
  def quote_string(s) #:nodoc:
379
- if PGconn.respond_to?(:escape)
385
+ if @connection.respond_to?(:escape)
386
+ self.class.instance_eval do
387
+ define_method(:quote_string) do |s|
388
+ @connection.escape(s)
389
+ end
390
+ end
391
+ elsif PGconn.respond_to?(:escape)
380
392
  self.class.instance_eval do
381
393
  define_method(:quote_string) do |s|
382
394
  PGconn.escape(s)
@@ -392,9 +404,28 @@ module ActiveRecord
392
404
  quote_string(s)
393
405
  end
394
406
 
407
+ # Checks the following cases:
408
+ #
409
+ # - table_name
410
+ # - "table.name"
411
+ # - schema_name.table_name
412
+ # - schema_name."table.name"
413
+ # - "schema.name".table_name
414
+ # - "schema.name"."table.name"
415
+ def quote_table_name(name)
416
+ schema, name_part = extract_pg_identifier_from_name(name.to_s)
417
+
418
+ unless name_part
419
+ quote_column_name(schema)
420
+ else
421
+ table_name, name_part = extract_pg_identifier_from_name(name_part)
422
+ "#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
423
+ end
424
+ end
425
+
395
426
  # Quotes column names for use in SQL queries.
396
427
  def quote_column_name(name) #:nodoc:
397
- %("#{name}")
428
+ PGconn.quote_ident(name.to_s)
398
429
  end
399
430
 
400
431
  # Quote date/time values for use in SQL input. Includes microseconds
@@ -621,33 +652,36 @@ module ActiveRecord
621
652
  def indexes(table_name, name = nil)
622
653
  schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
623
654
  result = query(<<-SQL, name)
624
- SELECT distinct i.relname, d.indisunique, a.attname
625
- FROM pg_class t, pg_class i, pg_index d, pg_attribute a
655
+ SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
656
+ FROM pg_class t, pg_class i, pg_index d
626
657
  WHERE i.relkind = 'i'
627
658
  AND d.indexrelid = i.oid
628
659
  AND d.indisprimary = 'f'
629
660
  AND t.oid = d.indrelid
630
661
  AND t.relname = '#{table_name}'
631
662
  AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
632
- AND a.attrelid = t.oid
633
- AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
634
- OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
635
- OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
636
- OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
637
- OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
638
663
  ORDER BY i.relname
639
664
  SQL
640
665
 
641
- current_index = nil
666
+
642
667
  indexes = []
643
668
 
644
- result.each do |row|
645
- if current_index != row[0]
646
- indexes << IndexDefinition.new(table_name, row[0], row[1] == "t", [])
647
- current_index = row[0]
648
- end
669
+ indexes = result.map do |row|
670
+ index_name = row[0]
671
+ unique = row[1] == 't'
672
+ indkey = row[2].split(" ")
673
+ oid = row[3]
674
+
675
+ columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = r[0]; attlist}
676
+ SELECT a.attname, a.attnum
677
+ FROM pg_attribute a
678
+ WHERE a.attrelid = #{oid}
679
+ AND a.attnum IN (#{indkey.join(",")})
680
+ SQL
681
+
682
+ column_names = indkey.map {|attnum| columns[attnum] }
683
+ IndexDefinition.new(table_name, index_name, unique, column_names)
649
684
 
650
- indexes.last.columns << row[2]
651
685
  end
652
686
 
653
687
  indexes
@@ -745,7 +779,7 @@ module ActiveRecord
745
779
  AND attr.attrelid = cons.conrelid
746
780
  AND attr.attnum = cons.conkey[1]
747
781
  AND cons.contype = 'p'
748
- AND dep.refobjid = '#{table}'::regclass
782
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
749
783
  end_sql
750
784
 
751
785
  if result.nil? or result.empty?
@@ -764,7 +798,7 @@ module ActiveRecord
764
798
  JOIN pg_attribute attr ON (t.oid = attrelid)
765
799
  JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
766
800
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
767
- WHERE t.oid = '#{table}'::regclass
801
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
768
802
  AND cons.contype = 'p'
769
803
  AND def.adsrc ~* 'nextval'
770
804
  end_sql
@@ -839,7 +873,7 @@ module ActiveRecord
839
873
 
840
874
  # Drops an index from a table.
841
875
  def remove_index(table_name, options = {})
842
- execute "DROP INDEX #{index_name(table_name, options)}"
876
+ execute "DROP INDEX #{quote_table_name(index_name(table_name, options))}"
843
877
  end
844
878
 
845
879
  # Maps logical Rails types to PostgreSQL-specific data types.
@@ -1040,11 +1074,21 @@ module ActiveRecord
1040
1074
  SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
1041
1075
  FROM pg_attribute a LEFT JOIN pg_attrdef d
1042
1076
  ON a.attrelid = d.adrelid AND a.attnum = d.adnum
1043
- WHERE a.attrelid = '#{table_name}'::regclass
1077
+ WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
1044
1078
  AND a.attnum > 0 AND NOT a.attisdropped
1045
1079
  ORDER BY a.attnum
1046
1080
  end_sql
1047
1081
  end
1082
+
1083
+ def extract_pg_identifier_from_name(name)
1084
+ match_data = name[0,1] == '"' ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
1085
+
1086
+ if match_data
1087
+ rest = name[match_data[0].length..-1]
1088
+ rest = rest[1..-1] if rest[0,1] == "."
1089
+ [match_data[1], (rest.length > 0 ? rest : nil)]
1090
+ end
1091
+ end
1048
1092
  end
1049
1093
  end
1050
1094
  end
@@ -1,3 +1,4 @@
1
+ # encoding: binary
1
2
  require 'active_record/connection_adapters/abstract_adapter'
2
3
 
3
4
  module ActiveRecord
@@ -46,6 +47,7 @@ module ActiveRecord
46
47
  class SQLiteColumn < Column #:nodoc:
47
48
  class << self
48
49
  def string_to_binary(value)
50
+ value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
49
51
  value.gsub(/\0|\%/n) do |b|
50
52
  case b
51
53
  when "\0" then "%00"
@@ -55,6 +57,7 @@ module ActiveRecord
55
57
  end
56
58
 
57
59
  def binary_to_string(value)
60
+ value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
58
61
  value.gsub(/%00|%25/n) do |b|
59
62
  case b
60
63
  when "%00" then "\0"
@@ -1,6 +1,7 @@
1
1
  require 'erb'
2
2
  require 'yaml'
3
3
  require 'csv'
4
+ require 'zlib'
4
5
  require 'active_support/dependencies'
5
6
  require 'active_support/test_case'
6
7
 
@@ -433,6 +434,7 @@ end
433
434
  # Any fixture labeled "DEFAULTS" is safely ignored.
434
435
 
435
436
  class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
437
+ MAX_ID = 2 ** 31 - 1
436
438
  DEFAULT_FILTER_RE = /\.ya?ml$/
437
439
 
438
440
  @@all_cached_fixtures = {}
@@ -524,11 +526,10 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
524
526
  cached_fixtures(connection, table_names)
525
527
  end
526
528
 
527
- # Returns a consistent identifier for +label+. This will always
528
- # be a positive integer, and will always be the same for a given
529
- # label, assuming the same OS, platform, and version of Ruby.
529
+ # Returns a consistent, platform-independent identifier for +label+.
530
+ # Identifiers are positive integers less than 2^32.
530
531
  def self.identify(label)
531
- label.to_s.hash.abs
532
+ Zlib.crc32(label.to_s) % MAX_ID
532
533
  end
533
534
 
534
535
  attr_reader :table_name, :name
@@ -114,7 +114,7 @@ module ActiveRecord
114
114
  end
115
115
  end
116
116
 
117
- delegate :scopes, :with_scope, :to => :proxy_scope
117
+ delegate :scopes, :with_scope, :scoped_methods, :to => :proxy_scope
118
118
 
119
119
  def initialize(proxy_scope, options, &block)
120
120
  options ||= {}
@@ -178,7 +178,7 @@ module ActiveRecord
178
178
  else
179
179
  with_scope({:find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {}}, :reverse_merge) do
180
180
  method = :new if method == :build
181
- if current_scoped_methods_when_defined
181
+ if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined)
182
182
  with_scope current_scoped_methods_when_defined do
183
183
  proxy_scope.send(method, *args, &block)
184
184
  end
@@ -78,11 +78,14 @@ HEADER
78
78
  begin
79
79
  tbl = StringIO.new
80
80
 
81
+ # first dump primary key column
81
82
  if @connection.respond_to?(:pk_and_sequence_for)
82
83
  pk, pk_seq = @connection.pk_and_sequence_for(table)
84
+ elsif @connection.respond_to?(:primary_key)
85
+ pk = @connection.primary_key(table)
83
86
  end
84
87
  pk ||= 'id'
85
-
88
+
86
89
  tbl.print " create_table #{table.inspect}"
87
90
  if columns.detect { |c| c.name == pk }
88
91
  if pk != 'id'
@@ -94,6 +97,7 @@ HEADER
94
97
  tbl.print ", :force => true"
95
98
  tbl.puts " do |t|"
96
99
 
100
+ # then dump all non-primary key columns
97
101
  column_specs = columns.map do |column|
98
102
  raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
99
103
  next if column.name == pk
@@ -5,8 +5,9 @@ module ActiveRecord #:nodoc:
5
5
  class Serializer #:nodoc:
6
6
  attr_reader :options
7
7
 
8
- def initialize(record, options = {})
9
- @record, @options = record, options.dup
8
+ def initialize(record, options = nil)
9
+ @record = record
10
+ @options = options ? options.dup : {}
10
11
  end
11
12
 
12
13
  # To replicate the behavior in ActiveRecord#attributes,
@@ -1,8 +1,10 @@
1
+ require 'active_support/json'
2
+ require 'active_support/core_ext/module/model_naming'
3
+
1
4
  module ActiveRecord #:nodoc:
2
5
  module Serialization
3
6
  def self.included(base)
4
7
  base.cattr_accessor :include_root_in_json, :instance_writer => false
5
- base.extend ClassMethods
6
8
  end
7
9
 
8
10
  # Returns a JSON string representing the model. Some configuration is
@@ -72,28 +74,16 @@ module ActiveRecord #:nodoc:
72
74
  # {"comments": [{"body": "Don't think too hard"}],
73
75
  # "title": "So I was thinking"}]}
74
76
  def to_json(options = {})
75
- if include_root_in_json
76
- "{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
77
- else
78
- JsonSerializer.new(self, options).to_s
79
- end
77
+ hash = Serializer.new(self, options).serializable_record
78
+ hash = { self.class.model_name.element => hash } if include_root_in_json
79
+ ActiveSupport::JSON.encode(hash)
80
80
  end
81
81
 
82
+ def as_json(options = nil) self end #:nodoc:
83
+
82
84
  def from_json(json)
83
85
  self.attributes = ActiveSupport::JSON.decode(json)
84
86
  self
85
87
  end
86
-
87
- class JsonSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
88
- def serialize
89
- serializable_record.to_json
90
- end
91
- end
92
-
93
- module ClassMethods
94
- def json_class_name
95
- @json_class_name ||= name.demodulize.underscore.inspect
96
- end
97
- end
98
88
  end
99
89
  end
@@ -295,7 +295,7 @@ module ActiveRecord
295
295
 
296
296
  def set_session(env, sid, session_data)
297
297
  Base.silence do
298
- record = env[SESSION_RECORD_KEY] ||= find_session(sid)
298
+ record = get_session_model(env, sid)
299
299
  record.data = session_data
300
300
  return false unless record.save
301
301
 
@@ -309,6 +309,14 @@ module ActiveRecord
309
309
 
310
310
  return true
311
311
  end
312
+
313
+ def get_session_model(env, sid)
314
+ if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
315
+ env[SESSION_RECORD_KEY] = find_session(sid)
316
+ else
317
+ env[SESSION_RECORD_KEY] ||= find_session(sid)
318
+ end
319
+ end
312
320
 
313
321
  def find_session(id)
314
322
  @@session_class.find_by_session_id(id) ||
@@ -15,27 +15,57 @@ module ActiveRecord
15
15
  base.class_inheritable_accessor :record_timestamps, :instance_writer => false
16
16
  base.record_timestamps = true
17
17
  end
18
+
19
+ # Saves the record with the updated_at/on attributes set to the current time.
20
+ # If the save fails because of validation errors, an ActiveRecord::RecordInvalid exception is raised.
21
+ # If an attribute name is passed, that attribute is used for the touch instead of the updated_at/on attributes.
22
+ #
23
+ # Examples:
24
+ #
25
+ # product.touch # updates updated_at
26
+ # product.touch(:designed_at) # updates the designed_at attribute
27
+ def touch(attribute = nil)
28
+ current_time = current_time_from_proper_timezone
29
+
30
+ if attribute
31
+ write_attribute(attribute, current_time)
32
+ else
33
+ write_attribute('updated_at', current_time) if respond_to?(:updated_at)
34
+ write_attribute('updated_on', current_time) if respond_to?(:updated_on)
35
+ end
36
+
37
+ save!
38
+ end
39
+
18
40
 
19
41
  private
20
42
  def create_with_timestamps #:nodoc:
21
43
  if record_timestamps
22
- t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
23
- write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
24
- write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
44
+ current_time = current_time_from_proper_timezone
25
45
 
26
- write_attribute('updated_at', t) if respond_to?(:updated_at) && updated_at.nil?
27
- write_attribute('updated_on', t) if respond_to?(:updated_on) && updated_on.nil?
46
+ write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
47
+ write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
48
+
49
+ write_attribute('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil?
50
+ write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_on.nil?
28
51
  end
52
+
29
53
  create_without_timestamps
30
54
  end
31
55
 
32
56
  def update_with_timestamps(*args) #:nodoc:
33
57
  if record_timestamps && (!partial_updates? || changed?)
34
- t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
35
- write_attribute('updated_at', t) if respond_to?(:updated_at)
36
- write_attribute('updated_on', t) if respond_to?(:updated_on)
58
+ current_time = current_time_from_proper_timezone
59
+
60
+ write_attribute('updated_at', current_time) if respond_to?(:updated_at)
61
+ write_attribute('updated_on', current_time) if respond_to?(:updated_on)
37
62
  end
63
+
38
64
  update_without_timestamps(*args)
39
65
  end
66
+
67
+ def current_time_from_proper_timezone
68
+ self.class.default_timezone == :utc ? Time.now.utc : Time.now
69
+ end
40
70
  end
41
- end
71
+ end