activerecord 3.0.0.rc → 3.0.0.rc2

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 +6 -1
  2. data/README.rdoc +9 -9
  3. data/lib/active_record/aggregations.rb +64 -51
  4. data/lib/active_record/association_preload.rb +11 -9
  5. data/lib/active_record/associations.rb +300 -204
  6. data/lib/active_record/associations/association_collection.rb +7 -2
  7. data/lib/active_record/associations/belongs_to_association.rb +9 -5
  8. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +7 -6
  9. data/lib/active_record/associations/has_many_association.rb +6 -6
  10. data/lib/active_record/associations/has_many_through_association.rb +4 -3
  11. data/lib/active_record/associations/has_one_association.rb +7 -7
  12. data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -1
  13. data/lib/active_record/attribute_methods/write.rb +2 -2
  14. data/lib/active_record/autosave_association.rb +54 -72
  15. data/lib/active_record/base.rb +167 -108
  16. data/lib/active_record/callbacks.rb +43 -35
  17. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +8 -11
  18. data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -1
  19. data/lib/active_record/connection_adapters/abstract/query_cache.rb +0 -8
  20. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +8 -6
  22. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +5 -3
  23. data/lib/active_record/connection_adapters/mysql_adapter.rb +5 -1
  24. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -5
  25. data/lib/active_record/connection_adapters/sqlite_adapter.rb +5 -5
  26. data/lib/active_record/dynamic_finder_match.rb +3 -3
  27. data/lib/active_record/dynamic_scope_match.rb +1 -1
  28. data/lib/active_record/errors.rb +9 -5
  29. data/lib/active_record/fixtures.rb +36 -22
  30. data/lib/active_record/locale/en.yml +2 -2
  31. data/lib/active_record/migration.rb +36 -36
  32. data/lib/active_record/named_scope.rb +23 -11
  33. data/lib/active_record/nested_attributes.rb +3 -3
  34. data/lib/active_record/observer.rb +3 -3
  35. data/lib/active_record/persistence.rb +44 -29
  36. data/lib/active_record/railtie.rb +5 -8
  37. data/lib/active_record/railties/databases.rake +1 -1
  38. data/lib/active_record/reflection.rb +52 -52
  39. data/lib/active_record/relation.rb +26 -19
  40. data/lib/active_record/relation/batches.rb +4 -4
  41. data/lib/active_record/relation/calculations.rb +58 -34
  42. data/lib/active_record/relation/finder_methods.rb +21 -12
  43. data/lib/active_record/relation/query_methods.rb +26 -31
  44. data/lib/active_record/relation/spawn_methods.rb +17 -5
  45. data/lib/active_record/schema.rb +1 -1
  46. data/lib/active_record/schema_dumper.rb +12 -12
  47. data/lib/active_record/serialization.rb +1 -1
  48. data/lib/active_record/serializers/xml_serializer.rb +1 -1
  49. data/lib/active_record/session_store.rb +9 -9
  50. data/lib/active_record/test_case.rb +2 -2
  51. data/lib/active_record/timestamp.rb +31 -32
  52. data/lib/active_record/validations/associated.rb +4 -3
  53. data/lib/active_record/validations/uniqueness.rb +15 -11
  54. data/lib/active_record/version.rb +1 -1
  55. metadata +17 -16
@@ -21,23 +21,28 @@ module ActiveRecord
21
21
  #
22
22
  # ==== Parameters
23
23
  #
24
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>, or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
24
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>,
25
+ # or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
25
26
  # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
26
27
  # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
27
- # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
28
+ # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a
29
+ # <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
28
30
  # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
29
- # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
31
+ # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5,
32
+ # it would skip rows 0 through 4.
30
33
  # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
31
- # named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s),
34
+ # named associations in the same form used for the <tt>:include</tt> option, which will perform an
35
+ # <tt>INNER JOIN</tt> on the associated table(s),
32
36
  # or an array containing a mixture of both strings and named associations.
33
- # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
37
+ # If the value is a string, then the records will be returned read-only since they will
38
+ # have attributes that do not correspond to the table's columns.
34
39
  # Pass <tt>:readonly => false</tt> to override.
35
40
  # * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
36
41
  # to already defined associations. See eager loading under Associations.
37
- # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not
38
- # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
39
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
40
- # of a database view).
42
+ # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you,
43
+ # for example, want to do a join but not include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
44
+ # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed
45
+ # to an alternate table name (or even the name of a database view).
41
46
  # * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
42
47
  # * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
43
48
  # <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
@@ -164,6 +169,8 @@ module ActiveRecord
164
169
  # Person.exists?(['name LIKE ?', "%#{query}%"])
165
170
  # Person.exists?
166
171
  def exists?(id = nil)
172
+ id = id.id if ActiveRecord::Base === id
173
+
167
174
  case id
168
175
  when Array, Hash
169
176
  where(id).exists?
@@ -279,10 +286,12 @@ module ActiveRecord
279
286
  end
280
287
 
281
288
  def find_one(id)
289
+ id = id.id if ActiveRecord::Base === id
290
+
282
291
  record = where(primary_key.eq(id)).first
283
292
 
284
293
  unless record
285
- conditions = arel.send(:where_clauses).join(', ')
294
+ conditions = arel.wheres.map { |x| x.value }.join(', ')
286
295
  conditions = " [WHERE #{conditions}]" if conditions.present?
287
296
  raise RecordNotFound, "Couldn't find #{@klass.name} with ID=#{id}#{conditions}"
288
297
  end
@@ -308,7 +317,7 @@ module ActiveRecord
308
317
  if result.size == expected_size
309
318
  result
310
319
  else
311
- conditions = arel.send(:where_clauses).join(', ')
320
+ conditions = arel.wheres.map { |x| x.value }.join(', ')
312
321
  conditions = " [WHERE #{conditions}]" if conditions.present?
313
322
 
314
323
  error = "Couldn't find all #{@klass.name.pluralize} with IDs "
@@ -339,7 +348,7 @@ module ActiveRecord
339
348
  end
340
349
 
341
350
  def using_limitable_reflections?(reflections)
342
- reflections.none?(&:collection?)
351
+ reflections.none? { |r| r.collection? }
343
352
  end
344
353
 
345
354
  end
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
 
12
12
  def includes(*args)
13
13
  args.reject! { |a| a.blank? }
14
- clone.tap {|r| r.includes_values += args if args.present? }
14
+ clone.tap {|r| r.includes_values = (r.includes_values + args).flatten.uniq if args.present? }
15
15
  end
16
16
 
17
17
  def eager_load(*args)
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
  end
32
32
 
33
33
  def group(*args)
34
- clone.tap {|r| r.group_values += args if args.present? }
34
+ clone.tap {|r| r.group_values += args.flatten if args.present? }
35
35
  end
36
36
 
37
37
  def order(*args)
@@ -47,9 +47,11 @@ module ActiveRecord
47
47
  clone.tap {|r| r.joins_values += args if args.present? }
48
48
  end
49
49
 
50
- def where(*args)
51
- value = build_where(*args)
52
- clone.tap {|r| r.where_values += Array.wrap(value) if value.present? }
50
+ def where(opts, *rest)
51
+ value = build_where(opts, rest)
52
+ copy = clone
53
+ copy.where_values += Array.wrap(value) if value
54
+ copy
53
55
  end
54
56
 
55
57
  def having(*args)
@@ -58,7 +60,9 @@ module ActiveRecord
58
60
  end
59
61
 
60
62
  def limit(value = true)
61
- clone.tap {|r| r.limit_value = value }
63
+ copy = clone
64
+ copy.limit_value = value
65
+ copy
62
66
  end
63
67
 
64
68
  def offset(value = true)
@@ -117,8 +121,10 @@ module ActiveRecord
117
121
  when Hash, Array, Symbol
118
122
  if array_of_strings?(join)
119
123
  join_string = join.join(' ')
120
- arel = arel.join(join_string)
124
+ arel = arel.join(Arel::SqlLiteral.new(join_string))
121
125
  end
126
+ when String
127
+ arel = arel.join(Arel::SqlLiteral.new(join))
122
128
  else
123
129
  arel = arel.join(join)
124
130
  end
@@ -129,11 +135,9 @@ module ActiveRecord
129
135
  def build_arel
130
136
  arel = table
131
137
 
132
- arel = build_joins(arel, @joins_values) if @joins_values.present?
133
-
134
- @where_values.uniq.each do |where|
135
- next if where.blank?
138
+ arel = build_joins(arel, @joins_values) unless @joins_values.empty?
136
139
 
140
+ (@where_values - ['']).uniq.each do |where|
137
141
  case where
138
142
  when Arel::SqlLiteral
139
143
  arel = arel.where(where)
@@ -143,36 +147,27 @@ module ActiveRecord
143
147
  end
144
148
  end
145
149
 
146
- arel = arel.having(*@having_values.uniq.select{|h| h.present?}) if @having_values.present?
150
+ arel = arel.having(*@having_values.uniq.select{|h| h.present?}) unless @having_values.empty?
147
151
 
148
- arel = arel.take(@limit_value) if @limit_value.present?
149
- arel = arel.skip(@offset_value) if @offset_value.present?
152
+ arel = arel.take(@limit_value) if @limit_value
153
+ arel = arel.skip(@offset_value) if @offset_value
150
154
 
151
- arel = arel.group(*@group_values.uniq.select{|g| g.present?}) if @group_values.present?
155
+ arel = arel.group(*@group_values.uniq.select{|g| g.present?}) unless @group_values.empty?
152
156
 
153
- arel = arel.order(*@order_values.uniq.select{|o| o.present?}) if @order_values.present?
157
+ arel = arel.order(*@order_values.uniq.select{|o| o.present?}) unless @order_values.empty?
154
158
 
155
159
  arel = build_select(arel, @select_values.uniq)
156
160
 
157
- arel = arel.from(@from_value) if @from_value.present?
158
-
159
- case @lock_value
160
- when TrueClass
161
- arel = arel.lock
162
- when String
163
- arel = arel.lock(@lock_value)
164
- end if @lock_value.present?
161
+ arel = arel.from(@from_value) if @from_value
162
+ arel = arel.lock(@lock_value) if @lock_value
165
163
 
166
164
  arel
167
165
  end
168
166
 
169
- def build_where(*args)
170
- return if args.blank?
171
-
172
- opts = args.first
167
+ def build_where(opts, other = [])
173
168
  case opts
174
169
  when String, Array
175
- @klass.send(:sanitize_sql, args.size > 1 ? args : opts)
170
+ @klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))
176
171
  when Hash
177
172
  attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
178
173
  PredicateBuilder.new(table.engine).build_from_hash(attributes, table)
@@ -226,7 +221,7 @@ module ActiveRecord
226
221
  end
227
222
 
228
223
  def build_select(arel, selects)
229
- if selects.present?
224
+ unless selects.empty?
230
225
  @implicit_readonly = false
231
226
  # TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array.
232
227
  # Before this change we were passing to ARel the last element only, and ARel is capable of handling an array
@@ -236,7 +231,7 @@ module ActiveRecord
236
231
  arel.project(selects.last)
237
232
  end
238
233
  else
239
- arel.project(@klass.quoted_table_name + '.*')
234
+ arel.project(Arel::SqlLiteral.new(@klass.quoted_table_name + '.*'))
240
235
  end
241
236
  end
242
237
 
@@ -8,7 +8,13 @@ module ActiveRecord
8
8
 
9
9
  ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - [:joins, :where]).each do |method|
10
10
  value = r.send(:"#{method}_values")
11
- merged_relation.send(:"#{method}_values=", value) if value.present?
11
+ if value.present?
12
+ if method == :includes
13
+ merged_relation = merged_relation.includes(value)
14
+ else
15
+ merged_relation.send(:"#{method}_values=", value)
16
+ end
17
+ end
12
18
  end
13
19
 
14
20
  merged_relation = merged_relation.joins(r.joins_values)
@@ -16,8 +22,10 @@ module ActiveRecord
16
22
  merged_wheres = @where_values
17
23
 
18
24
  r.where_values.each do |w|
19
- if w.is_a?(Arel::Predicates::Equality)
20
- merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name }
25
+ if w.respond_to?(:operator) && w.operator == :==
26
+ merged_wheres = merged_wheres.reject { |p|
27
+ p.respond_to?(:operator) && p.operator == :== && p.operand1.name == w.operand1.name
28
+ }
21
29
  end
22
30
 
23
31
  merged_wheres += [w]
@@ -80,10 +88,14 @@ module ActiveRecord
80
88
 
81
89
  options.assert_valid_keys(VALID_FIND_OPTIONS)
82
90
 
83
- [:joins, :select, :group, :having, :limit, :offset, :from, :lock, :readonly].each do |finder|
84
- relation = relation.send(finder, options[finder]) if options.has_key?(finder)
91
+ [:joins, :select, :group, :having, :limit, :offset, :from, :lock].each do |finder|
92
+ if value = options[finder]
93
+ relation = relation.send(finder, value)
94
+ end
85
95
  end
86
96
 
97
+ relation = relation.readonly(options[:readonly]) if options.key? :readonly
98
+
87
99
  # Give precedence to newly-applied orders and groups to play nicely with with_scope
88
100
  [:group, :order].each do |finder|
89
101
  relation.send("#{finder}_values=", Array.wrap(options[finder]) + relation.send("#{finder}_values")) if options.has_key?(finder)
@@ -2,7 +2,7 @@ require 'active_support/core_ext/object/blank'
2
2
 
3
3
  module ActiveRecord
4
4
  # = Active Record Schema
5
- #
5
+ #
6
6
  # Allows programmers to programmatically define a schema in a portable
7
7
  # DSL. This means you can define tables, indexes, etc. without using SQL
8
8
  # directly, so your applications can more easily support multiple
@@ -8,13 +8,13 @@ module ActiveRecord
8
8
  # output format (i.e., ActiveRecord::Schema).
9
9
  class SchemaDumper #:nodoc:
10
10
  private_class_method :new
11
-
11
+
12
12
  ##
13
13
  # :singleton-method:
14
- # A list of tables which should not be dumped to the schema.
14
+ # A list of tables which should not be dumped to the schema.
15
15
  # Acceptable values are strings as well as regexp.
16
16
  # This setting is only used if ActiveRecord::Base.schema_format == :ruby
17
- cattr_accessor :ignore_tables
17
+ cattr_accessor :ignore_tables
18
18
  @@ignore_tables = []
19
19
 
20
20
  def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
@@ -41,11 +41,11 @@ module ActiveRecord
41
41
  define_params = @version ? ":version => #{@version}" : ""
42
42
 
43
43
  stream.puts <<HEADER
44
- # This file is auto-generated from the current state of the database. Instead
44
+ # This file is auto-generated from the current state of the database. Instead
45
45
  # of editing this file, please use the migrations feature of Active Record to
46
46
  # incrementally modify your database, and then regenerate this schema definition.
47
47
  #
48
- # Note that this schema.rb definition is the authoritative source for your
48
+ # Note that this schema.rb definition is the authoritative source for your
49
49
  # database schema. If you need to create the application database on another
50
50
  # system, you should be using db:schema:load, not running all the migrations
51
51
  # from scratch. The latter is a flawed and unsustainable approach (the more migrations
@@ -71,7 +71,7 @@ HEADER
71
71
  else
72
72
  raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
73
73
  end
74
- end
74
+ end
75
75
  table(tbl, stream)
76
76
  end
77
77
  end
@@ -87,7 +87,7 @@ HEADER
87
87
  elsif @connection.respond_to?(:primary_key)
88
88
  pk = @connection.primary_key(table)
89
89
  end
90
-
90
+
91
91
  tbl.print " create_table #{table.inspect}"
92
92
  if columns.detect { |c| c.name == pk }
93
93
  if pk != 'id'
@@ -105,7 +105,7 @@ HEADER
105
105
  next if column.name == pk
106
106
  spec = {}
107
107
  spec[:name] = column.name.inspect
108
-
108
+
109
109
  # AR has an optimisation which handles zero-scale decimals as integers. This
110
110
  # code ensures that the dumper still dumps the column as a decimal.
111
111
  spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
@@ -123,7 +123,7 @@ HEADER
123
123
  end.compact
124
124
 
125
125
  # find all migration keys used in this table
126
- keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map(&:keys).flatten
126
+ keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map{ |k| k.keys }.flatten
127
127
 
128
128
  # figure out the lengths for each column based on above keys
129
129
  lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
@@ -148,7 +148,7 @@ HEADER
148
148
 
149
149
  tbl.puts " end"
150
150
  tbl.puts
151
-
151
+
152
152
  indexes(table, tbl)
153
153
 
154
154
  tbl.rewind
@@ -158,7 +158,7 @@ HEADER
158
158
  stream.puts "# #{e.message}"
159
159
  stream.puts
160
160
  end
161
-
161
+
162
162
  stream
163
163
  end
164
164
 
@@ -172,7 +172,7 @@ HEADER
172
172
  value.inspect
173
173
  end
174
174
  end
175
-
175
+
176
176
  def indexes(table, stream)
177
177
  if (indexes = @connection.indexes(table)).any?
178
178
  add_index_statements = indexes.map do |index|
@@ -23,7 +23,7 @@ module ActiveRecord #:nodoc:
23
23
 
24
24
  private
25
25
  # Add associations specified via the <tt>:includes</tt> option.
26
- #
26
+ #
27
27
  # Expects a block that takes as arguments:
28
28
  # +association+ - name of the association
29
29
  # +records+ - the association record(s) to be serialized
@@ -80,7 +80,7 @@ module ActiveRecord #:nodoc:
80
80
  # closure created by a Proc, to_xml can be used to add elements that normally fall
81
81
  # outside of the scope of the model -- for example, generating and appending URLs
82
82
  # associated with models.
83
- #
83
+ #
84
84
  # proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
85
85
  # firm.to_xml :procs => [ proc ]
86
86
  #
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  # +id+ (numeric primary key),
10
10
  # +session_id+ (text, or longtext if your session data exceeds 65K), and
11
11
  # +data+ (text or longtext; careful if your session data exceeds 65KB).
12
- #
12
+ #
13
13
  # The +session_id+ column should always be indexed for speedy lookups.
14
14
  # Session data is marshaled to the +data+ column in Base64 format.
15
15
  # If the data you write is larger than the column's size limit,
@@ -59,17 +59,15 @@ module ActiveRecord
59
59
  end
60
60
 
61
61
  def drop_table!
62
- connection.execute "DROP TABLE #{table_name}"
62
+ connection.drop_table table_name
63
63
  end
64
64
 
65
65
  def create_table!
66
- connection.execute <<-end_sql
67
- CREATE TABLE #{table_name} (
68
- id #{connection.type_to_sql(:primary_key)},
69
- #{connection.quote_column_name(session_id_column)} VARCHAR(255) UNIQUE,
70
- #{connection.quote_column_name(data_column_name)} TEXT
71
- )
72
- end_sql
66
+ connection.create_table(table_name) do |t|
67
+ t.string session_id_column, :limit => 255
68
+ t.text data_column_name
69
+ end
70
+ connection.add_index table_name, session_id_column, :unique => true
73
71
  end
74
72
  end
75
73
 
@@ -205,6 +203,7 @@ module ActiveRecord
205
203
  class << self
206
204
  alias :data_column_name :data_column
207
205
 
206
+ remove_method :connection
208
207
  def connection
209
208
  @@connection ||= ActiveRecord::Base.connection
210
209
  end
@@ -293,6 +292,7 @@ module ActiveRecord
293
292
  private
294
293
  def get_session(env, sid)
295
294
  Base.silence do
295
+ sid ||= generate_sid
296
296
  session = find_session(sid)
297
297
  env[SESSION_RECORD_KEY] = session
298
298
  [sid, session.data]
@@ -1,6 +1,6 @@
1
1
  module ActiveRecord
2
2
  # = Active Record Test Case
3
- #
3
+ #
4
4
  # Defines some test assertions to test against SQL queries.
5
5
  class TestCase < ActiveSupport::TestCase #:nodoc:
6
6
  def assert_date_from_db(expected, actual, message = nil)
@@ -21,7 +21,7 @@ module ActiveRecord
21
21
  patterns_to_match.each do |pattern|
22
22
  failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql }
23
23
  end
24
- assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
24
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
25
25
  end
26
26
 
27
27
  def assert_queries(num = 1)
@@ -1,8 +1,8 @@
1
1
  module ActiveRecord
2
2
  # = Active Record Timestamp
3
- #
3
+ #
4
4
  # Active Record automatically timestamps create and update operations if the
5
- # table has fields named <tt>created_at/created_on</tt> or
5
+ # table has fields named <tt>created_at/created_on</tt> or
6
6
  # <tt>updated_at/updated_on</tt>.
7
7
  #
8
8
  # Timestamping can be turned off by setting:
@@ -12,6 +12,19 @@ module ActiveRecord
12
12
  # Timestamps are in the local timezone by default but you can use UTC by setting:
13
13
  #
14
14
  # <tt>ActiveRecord::Base.default_timezone = :utc</tt>
15
+ #
16
+ # == Time Zone aware attributes
17
+ #
18
+ # By default, ActiveRecord::Base keeps all the datetime columns time zone aware by executing following code.
19
+ #
20
+ # ActiveRecord::Base.time_zone_aware_attributes = true
21
+ #
22
+ # This feature can easily be turned off by assigning value <tt>false</tt> .
23
+ #
24
+ # If your attributes are time zone aware and you desire to skip time zone conversion for certain
25
+ # attributes then you can do following:
26
+ #
27
+ # Topic.skip_time_zone_conversion_for_attributes = [:written_on]
15
28
  module Timestamp
16
29
  extend ActiveSupport::Concern
17
30
 
@@ -19,19 +32,6 @@ module ActiveRecord
19
32
  class_inheritable_accessor :record_timestamps, :instance_writer => false
20
33
  self.record_timestamps = true
21
34
  end
22
-
23
- # Saves the record with the updated_at/on attributes set to the current time.
24
- # Please note that no validation is performed and no callbacks are executed.
25
- # If an attribute name is passed, that attribute is updated along with
26
- # updated_at/on attributes.
27
- #
28
- # Examples:
29
- #
30
- # product.touch # updates updated_at/on
31
- # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
32
- def touch(attribute = nil)
33
- update_attribute(attribute, current_time_from_proper_timezone)
34
- end
35
35
 
36
36
  private
37
37
 
@@ -39,34 +39,33 @@ module ActiveRecord
39
39
  if record_timestamps
40
40
  current_time = current_time_from_proper_timezone
41
41
 
42
- timestamp_attributes_for_create.each do |column|
42
+ all_timestamp_attributes.each do |column|
43
43
  write_attribute(column.to_s, current_time) if respond_to?(column) && self.send(column).nil?
44
44
  end
45
-
46
- timestamp_attributes_for_update_in_model.each do |column|
47
- write_attribute(column.to_s, current_time) if self.send(column).nil?
48
- end
49
45
  end
50
46
 
51
47
  super
52
48
  end
53
49
 
54
50
  def update(*args) #:nodoc:
55
- record_update_timestamps if !partial_updates? || changed?
51
+ if should_record_timestamps?
52
+ current_time = current_time_from_proper_timezone
53
+
54
+ timestamp_attributes_for_update_in_model.each do |column|
55
+ column = column.to_s
56
+ next if attribute_changed?(column)
57
+ write_attribute(column, current_time)
58
+ end
59
+ end
56
60
  super
57
61
  end
58
62
 
59
- def record_update_timestamps #:nodoc:
60
- return unless record_timestamps
61
- current_time = current_time_from_proper_timezone
62
- timestamp_attributes_for_update_in_model.inject({}) do |hash, column|
63
- hash[column.to_s] = write_attribute(column.to_s, current_time)
64
- hash
65
- end
63
+ def should_record_timestamps?
64
+ record_timestamps && !partial_updates? || changed?
66
65
  end
67
66
 
68
- def timestamp_attributes_for_update_in_model #:nodoc:
69
- timestamp_attributes_for_update.select { |elem| respond_to?(elem) }
67
+ def timestamp_attributes_for_update_in_model
68
+ timestamp_attributes_for_update.select { |c| respond_to?(c) }
70
69
  end
71
70
 
72
71
  def timestamp_attributes_for_update #:nodoc:
@@ -78,9 +77,9 @@ module ActiveRecord
78
77
  end
79
78
 
80
79
  def all_timestamp_attributes #:nodoc:
81
- timestamp_attributes_for_update + timestamp_attributes_for_create
80
+ timestamp_attributes_for_create + timestamp_attributes_for_update
82
81
  end
83
-
82
+
84
83
  def current_time_from_proper_timezone #:nodoc:
85
84
  self.class.default_timezone == :utc ? Time.now.utc : Time.now
86
85
  end