activerecord 3.0.0.beta4 → 3.0.0.rc

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 (69) hide show
  1. data/CHANGELOG +267 -254
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +9 -9
  4. data/lib/active_record/aggregations.rb +3 -4
  5. data/lib/active_record/association_preload.rb +15 -10
  6. data/lib/active_record/associations.rb +54 -37
  7. data/lib/active_record/associations/association_collection.rb +43 -17
  8. data/lib/active_record/associations/association_proxy.rb +2 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +1 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +22 -7
  12. data/lib/active_record/associations/has_many_association.rb +6 -1
  13. data/lib/active_record/associations/has_many_through_association.rb +1 -0
  14. data/lib/active_record/associations/has_one_association.rb +1 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +1 -0
  16. data/lib/active_record/associations/through_association_scope.rb +3 -2
  17. data/lib/active_record/attribute_methods.rb +1 -0
  18. data/lib/active_record/autosave_association.rb +4 -6
  19. data/lib/active_record/base.rb +106 -240
  20. data/lib/active_record/callbacks.rb +4 -25
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +22 -29
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +2 -8
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +10 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +56 -7
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +10 -18
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +2 -2
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +65 -69
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +19 -6
  30. data/lib/active_record/connection_adapters/sqlite_adapter.rb +20 -46
  31. data/lib/active_record/counter_cache.rb +14 -4
  32. data/lib/active_record/dynamic_finder_match.rb +9 -0
  33. data/lib/active_record/dynamic_scope_match.rb +7 -0
  34. data/lib/active_record/errors.rb +3 -0
  35. data/lib/active_record/fixtures.rb +5 -6
  36. data/lib/active_record/locale/en.yml +1 -1
  37. data/lib/active_record/locking/optimistic.rb +1 -0
  38. data/lib/active_record/log_subscriber.rb +48 -0
  39. data/lib/active_record/migration.rb +64 -37
  40. data/lib/active_record/named_scope.rb +33 -19
  41. data/lib/active_record/nested_attributes.rb +17 -13
  42. data/lib/active_record/observer.rb +13 -6
  43. data/lib/active_record/persistence.rb +55 -22
  44. data/lib/active_record/query_cache.rb +1 -0
  45. data/lib/active_record/railtie.rb +14 -8
  46. data/lib/active_record/railties/controller_runtime.rb +2 -2
  47. data/lib/active_record/railties/databases.rake +63 -33
  48. data/lib/active_record/reflection.rb +46 -28
  49. data/lib/active_record/relation.rb +38 -24
  50. data/lib/active_record/relation/finder_methods.rb +5 -5
  51. data/lib/active_record/relation/predicate_builder.rb +2 -4
  52. data/lib/active_record/relation/query_methods.rb +134 -115
  53. data/lib/active_record/relation/spawn_methods.rb +1 -1
  54. data/lib/active_record/schema.rb +2 -0
  55. data/lib/active_record/schema_dumper.rb +15 -12
  56. data/lib/active_record/serialization.rb +2 -0
  57. data/lib/active_record/session_store.rb +93 -79
  58. data/lib/active_record/test_case.rb +3 -0
  59. data/lib/active_record/timestamp.rb +49 -29
  60. data/lib/active_record/transactions.rb +5 -2
  61. data/lib/active_record/validations.rb +5 -2
  62. data/lib/active_record/validations/associated.rb +1 -1
  63. data/lib/active_record/validations/uniqueness.rb +1 -1
  64. data/lib/active_record/version.rb +1 -1
  65. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -6
  66. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  67. metadata +27 -14
  68. data/README +0 -351
  69. data/lib/active_record/railties/log_subscriber.rb +0 -32
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  merged_relation = clone
7
7
  return merged_relation unless r
8
8
 
9
- (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).reject {|m| [:joins, :where].include?(m)}.each do |method|
9
+ ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - [:joins, :where]).each do |method|
10
10
  value = r.send(:"#{method}_values")
11
11
  merged_relation.send(:"#{method}_values=", value) if value.present?
12
12
  end
@@ -1,6 +1,8 @@
1
1
  require 'active_support/core_ext/object/blank'
2
2
 
3
3
  module ActiveRecord
4
+ # = Active Record Schema
5
+ #
4
6
  # Allows programmers to programmatically define a schema in a portable
5
7
  # DSL. This means you can define tables, indexes, etc. without using SQL
6
8
  # directly, so your applications can more easily support multiple
@@ -2,6 +2,8 @@ require 'stringio'
2
2
  require 'active_support/core_ext/big_decimal'
3
3
 
4
4
  module ActiveRecord
5
+ # = Active Record Schema Dumper
6
+ #
5
7
  # This class is used to dump the database schema for some connection to some
6
8
  # output format (i.e., ActiveRecord::Schema).
7
9
  class SchemaDumper #:nodoc:
@@ -39,13 +41,14 @@ module ActiveRecord
39
41
  define_params = @version ? ":version => #{@version}" : ""
40
42
 
41
43
  stream.puts <<HEADER
42
- # This file is auto-generated from the current state of the database. Instead of editing this file,
43
- # please use the migrations feature of Active Record to incrementally modify your database, and
44
- # then regenerate this schema definition.
44
+ # This file is auto-generated from the current state of the database. Instead
45
+ # of editing this file, please use the migrations feature of Active Record to
46
+ # incrementally modify your database, and then regenerate this schema definition.
45
47
  #
46
- # Note that this schema.rb definition is the authoritative source for your database schema. If you need
47
- # to create the application database on another system, you should be using db:schema:load, not running
48
- # all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
48
+ # Note that this schema.rb definition is the authoritative source for your
49
+ # database schema. If you need to create the application database on another
50
+ # system, you should be using db:schema:load, not running all the migrations
51
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
49
52
  # you'll amass, the slower it'll run and the greater likelihood for issues).
50
53
  #
51
54
  # It's strongly recommended to check this file into your version control system.
@@ -173,15 +176,15 @@ HEADER
173
176
  def indexes(table, stream)
174
177
  if (indexes = @connection.indexes(table)).any?
175
178
  add_index_statements = indexes.map do |index|
176
- statment_parts = [ ('add_index ' + index.table.inspect) ]
177
- statment_parts << index.columns.inspect
178
- statment_parts << (':name => ' + index.name.inspect)
179
- statment_parts << ':unique => true' if index.unique
179
+ statement_parts = [ ('add_index ' + index.table.inspect) ]
180
+ statement_parts << index.columns.inspect
181
+ statement_parts << (':name => ' + index.name.inspect)
182
+ statement_parts << ':unique => true' if index.unique
180
183
 
181
184
  index_lengths = index.lengths.compact if index.lengths.is_a?(Array)
182
- statment_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present?
185
+ statement_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present?
183
186
 
184
- ' ' + statment_parts.join(', ')
187
+ ' ' + statement_parts.join(', ')
185
188
  end
186
189
 
187
190
  stream.puts add_index_statements.sort.join("\n")
@@ -1,4 +1,5 @@
1
1
  module ActiveRecord #:nodoc:
2
+ # = Active Record Serialization
2
3
  module Serialization
3
4
  extend ActiveSupport::Concern
4
5
  include ActiveModel::Serializers::JSON
@@ -22,6 +23,7 @@ module ActiveRecord #:nodoc:
22
23
 
23
24
  private
24
25
  # Add associations specified via the <tt>:includes</tt> option.
26
+ #
25
27
  # Expects a block that takes as arguments:
26
28
  # +association+ - name of the association
27
29
  # +records+ - the association record(s) to be serialized
@@ -1,4 +1,6 @@
1
1
  module ActiveRecord
2
+ # = Active Record Session Store
3
+ #
2
4
  # A session store backed by an Active Record class. A default class is
3
5
  # provided, but any object duck-typing to an Active Record Session class
4
6
  # with text +session_id+ and +data+ attributes is sufficient.
@@ -7,16 +9,19 @@ module ActiveRecord
7
9
  # +id+ (numeric primary key),
8
10
  # +session_id+ (text, or longtext if your session data exceeds 65K), and
9
11
  # +data+ (text or longtext; careful if your session data exceeds 65KB).
12
+ #
10
13
  # The +session_id+ column should always be indexed for speedy lookups.
11
14
  # Session data is marshaled to the +data+ column in Base64 format.
12
15
  # If the data you write is larger than the column's size limit,
13
16
  # ActionController::SessionOverflowError will be raised.
14
17
  #
15
18
  # You may configure the table name, primary key, and data column.
16
- # For example, at the end of <tt>config/environment.rb</tt>:
19
+ # For example, at the end of <tt>config/application.rb</tt>:
20
+ #
17
21
  # ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
18
22
  # ActiveRecord::SessionStore::Session.primary_key = 'session_id'
19
23
  # ActiveRecord::SessionStore::Session.data_column_name = 'legacy_session_data'
24
+ #
20
25
  # Note that setting the primary key to the +session_id+ frees you from
21
26
  # having a separate +id+ column if you don't want it. However, you must
22
27
  # set <tt>session.model.id = session.session_id</tt> by hand! A before filter
@@ -29,8 +34,11 @@ module ActiveRecord
29
34
  # You may provide your own session class implementation, whether a
30
35
  # feature-packed Active Record or a bare-metal high-performance SQL
31
36
  # store, by setting
37
+ #
32
38
  # ActiveRecord::SessionStore.session_class = MySessionClass
39
+ #
33
40
  # You must implement these methods:
41
+ #
34
42
  # self.find_by_session_id(session_id)
35
43
  # initialize(hash_of_session_id_and_data)
36
44
  # attr_reader :session_id
@@ -41,8 +49,34 @@ module ActiveRecord
41
49
  # The example SqlBypass class is a generic SQL session store. You may
42
50
  # use it as a basis for high-performance database-specific stores.
43
51
  class SessionStore < ActionDispatch::Session::AbstractStore
52
+ module ClassMethods # :nodoc:
53
+ def marshal(data)
54
+ ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
55
+ end
56
+
57
+ def unmarshal(data)
58
+ Marshal.load(ActiveSupport::Base64.decode64(data)) if data
59
+ end
60
+
61
+ def drop_table!
62
+ connection.execute "DROP TABLE #{table_name}"
63
+ end
64
+
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
73
+ end
74
+ end
75
+
44
76
  # The default Active Record class.
45
77
  class Session < ActiveRecord::Base
78
+ extend ClassMethods
79
+
46
80
  ##
47
81
  # :singleton-method:
48
82
  # Customizable data column name. Defaults to 'data'.
@@ -54,7 +88,7 @@ module ActiveRecord
54
88
 
55
89
  class << self
56
90
  def data_column_size_limit
57
- @data_column_size_limit ||= columns_hash[@@data_column_name].limit
91
+ @data_column_size_limit ||= columns_hash[data_column_name].limit
58
92
  end
59
93
 
60
94
  # Hook to set up sessid compatibility.
@@ -63,29 +97,11 @@ module ActiveRecord
63
97
  find_by_session_id(session_id)
64
98
  end
65
99
 
66
- def marshal(data)
67
- ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
68
- end
69
-
70
- def unmarshal(data)
71
- Marshal.load(ActiveSupport::Base64.decode64(data)) if data
72
- end
73
-
74
- def create_table!
75
- connection.execute <<-end_sql
76
- CREATE TABLE #{table_name} (
77
- id INTEGER PRIMARY KEY,
78
- #{connection.quote_column_name('session_id')} TEXT UNIQUE,
79
- #{connection.quote_column_name(@@data_column_name)} TEXT(255)
80
- )
81
- end_sql
82
- end
83
-
84
- def drop_table!
85
- connection.execute "DROP TABLE #{table_name}"
86
- end
87
-
88
100
  private
101
+ def session_id_column
102
+ 'session_id'
103
+ end
104
+
89
105
  # Compatibility with tables using sessid instead of session_id.
90
106
  def setup_sessid_compatibility!
91
107
  # Reset column info since it may be stale.
@@ -98,6 +114,8 @@ module ActiveRecord
98
114
  define_method(:session_id) { sessid }
99
115
  define_method(:session_id=) { |session_id| self.sessid = session_id }
100
116
  else
117
+ class << self; remove_method :find_by_session_id; end
118
+
101
119
  def self.find_by_session_id(session_id)
102
120
  find :first, :conditions => {:session_id=>session_id}
103
121
  end
@@ -105,6 +123,11 @@ module ActiveRecord
105
123
  end
106
124
  end
107
125
 
126
+ def initialize(attributes = nil)
127
+ @data = nil
128
+ super
129
+ end
130
+
108
131
  # Lazy-unmarshal session state.
109
132
  def data
110
133
  @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
@@ -114,22 +137,22 @@ module ActiveRecord
114
137
 
115
138
  # Has the session been loaded yet?
116
139
  def loaded?
117
- !!@data
140
+ @data
118
141
  end
119
142
 
120
143
  private
121
144
  def marshal_data!
122
- return false if !loaded?
123
- write_attribute(@@data_column_name, self.class.marshal(self.data))
145
+ return false unless loaded?
146
+ write_attribute(@@data_column_name, self.class.marshal(data))
124
147
  end
125
148
 
126
149
  # Ensures that the data about to be stored in the database is not
127
150
  # larger than the data storage column. Raises
128
151
  # ActionController::SessionOverflowError.
129
152
  def raise_on_session_data_overflow!
130
- return false if !loaded?
153
+ return false unless loaded?
131
154
  limit = self.class.data_column_size_limit
132
- if loaded? and limit and read_attribute(@@data_column_name).size > limit
155
+ if limit and read_attribute(@@data_column_name).size > limit
133
156
  raise ActionController::SessionOverflowError
134
157
  end
135
158
  end
@@ -154,6 +177,8 @@ module ActiveRecord
154
177
  # binary session data in a +text+ column. For higher performance,
155
178
  # store in a +blob+ column instead and forgo the Base64 encoding.
156
179
  class SqlBypass
180
+ extend ClassMethods
181
+
157
182
  ##
158
183
  # :singleton-method:
159
184
  # Use the ActiveRecord::Base.connection by default.
@@ -178,6 +203,8 @@ module ActiveRecord
178
203
  @@data_column = 'data'
179
204
 
180
205
  class << self
206
+ alias :data_column_name :data_column
207
+
181
208
  def connection
182
209
  @@connection ||= ActiveRecord::Base.connection
183
210
  end
@@ -188,43 +215,21 @@ module ActiveRecord
188
215
  new(:session_id => session_id, :marshaled_data => record['data'])
189
216
  end
190
217
  end
191
-
192
- def marshal(data)
193
- ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
194
- end
195
-
196
- def unmarshal(data)
197
- Marshal.load(ActiveSupport::Base64.decode64(data)) if data
198
- end
199
-
200
- def create_table!
201
- @@connection.execute <<-end_sql
202
- CREATE TABLE #{table_name} (
203
- id INTEGER PRIMARY KEY,
204
- #{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
205
- #{@@connection.quote_column_name(data_column)} TEXT
206
- )
207
- end_sql
208
- end
209
-
210
- def drop_table!
211
- @@connection.execute "DROP TABLE #{table_name}"
212
- end
213
218
  end
214
219
 
215
- attr_reader :session_id
220
+ attr_reader :session_id, :new_record
221
+ alias :new_record? :new_record
222
+
216
223
  attr_writer :data
217
224
 
218
225
  # Look for normal and marshaled data, self.find_by_session_id's way of
219
226
  # telling us to postpone unmarshaling until the data is requested.
220
227
  # We need to handle a normal data attribute in case of a new record.
221
228
  def initialize(attributes)
222
- @session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
223
- @new_record = @marshaled_data.nil?
224
- end
225
-
226
- def new_record?
227
- @new_record
229
+ @session_id = attributes[:session_id]
230
+ @data = attributes[:data]
231
+ @marshaled_data = attributes[:marshaled_data]
232
+ @new_record = @marshaled_data.nil?
228
233
  end
229
234
 
230
235
  # Lazy-unmarshal session state.
@@ -240,39 +245,41 @@ module ActiveRecord
240
245
  end
241
246
 
242
247
  def loaded?
243
- !!@data
248
+ @data
244
249
  end
245
250
 
246
251
  def save
247
- return false if !loaded?
252
+ return false unless loaded?
248
253
  marshaled_data = self.class.marshal(data)
254
+ connect = connection
249
255
 
250
256
  if @new_record
251
257
  @new_record = false
252
- @@connection.update <<-end_sql, 'Create session'
253
- INSERT INTO #{@@table_name} (
254
- #{@@connection.quote_column_name(@@session_id_column)},
255
- #{@@connection.quote_column_name(@@data_column)} )
258
+ connect.update <<-end_sql, 'Create session'
259
+ INSERT INTO #{table_name} (
260
+ #{connect.quote_column_name(session_id_column)},
261
+ #{connect.quote_column_name(data_column)} )
256
262
  VALUES (
257
- #{@@connection.quote(session_id)},
258
- #{@@connection.quote(marshaled_data)} )
263
+ #{connect.quote(session_id)},
264
+ #{connect.quote(marshaled_data)} )
259
265
  end_sql
260
266
  else
261
- @@connection.update <<-end_sql, 'Update session'
262
- UPDATE #{@@table_name}
263
- SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)}
264
- WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
267
+ connect.update <<-end_sql, 'Update session'
268
+ UPDATE #{table_name}
269
+ SET #{connect.quote_column_name(data_column)}=#{connect.quote(marshaled_data)}
270
+ WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
265
271
  end_sql
266
272
  end
267
273
  end
268
274
 
269
275
  def destroy
270
- unless @new_record
271
- @@connection.delete <<-end_sql, 'Destroy session'
272
- DELETE FROM #{@@table_name}
273
- WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
274
- end_sql
275
- end
276
+ return if @new_record
277
+
278
+ connect = connection
279
+ connect.delete <<-end_sql, 'Destroy session'
280
+ DELETE FROM #{table_name}
281
+ WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
282
+ end_sql
276
283
  end
277
284
  end
278
285
 
@@ -281,12 +288,11 @@ module ActiveRecord
281
288
  cattr_accessor :session_class
282
289
  self.session_class = Session
283
290
 
284
- SESSION_RECORD_KEY = 'rack.session.record'.freeze
291
+ SESSION_RECORD_KEY = 'rack.session.record'
285
292
 
286
293
  private
287
294
  def get_session(env, sid)
288
295
  Base.silence do
289
- sid ||= generate_sid
290
296
  session = find_session(sid)
291
297
  env[SESSION_RECORD_KEY] = session
292
298
  [sid, session.data]
@@ -309,7 +315,15 @@ module ActiveRecord
309
315
 
310
316
  sid
311
317
  end
312
-
318
+
319
+ def destroy(env)
320
+ if sid = current_session_id(env)
321
+ Base.silence do
322
+ get_session_model(env, sid).destroy
323
+ end
324
+ end
325
+ end
326
+
313
327
  def get_session_model(env, sid)
314
328
  if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
315
329
  env[SESSION_RECORD_KEY] = find_session(sid)
@@ -1,4 +1,7 @@
1
1
  module ActiveRecord
2
+ # = Active Record Test Case
3
+ #
4
+ # Defines some test assertions to test against SQL queries.
2
5
  class TestCase < ActiveSupport::TestCase #:nodoc:
3
6
  def assert_date_from_db(expected, actual, message = nil)
4
7
  # SybaseAdapter doesn't have a separate column type just for dates,
@@ -1,11 +1,16 @@
1
1
  module ActiveRecord
2
- # Active Record automatically timestamps create and update operations if the table has fields
3
- # named created_at/created_on or updated_at/updated_on.
2
+ # = Active Record Timestamp
3
+ #
4
+ # Active Record automatically timestamps create and update operations if the
5
+ # table has fields named <tt>created_at/created_on</tt> or
6
+ # <tt>updated_at/updated_on</tt>.
7
+ #
8
+ # Timestamping can be turned off by setting:
4
9
  #
5
- # Timestamping can be turned off by setting
6
10
  # <tt>ActiveRecord::Base.record_timestamps = false</tt>
7
11
  #
8
- # Timestamps are in the local timezone by default but you can use UTC by setting
12
+ # Timestamps are in the local timezone by default but you can use UTC by setting:
13
+ #
9
14
  # <tt>ActiveRecord::Base.default_timezone = :utc</tt>
10
15
  module Timestamp
11
16
  extend ActiveSupport::Concern
@@ -16,54 +21,69 @@ module ActiveRecord
16
21
  end
17
22
 
18
23
  # Saves the record with the updated_at/on attributes set to the current time.
19
- # If the save fails because of validation errors, an ActiveRecord::RecordInvalid exception is raised.
20
- # If an attribute name is passed, that attribute is used for the touch instead of the updated_at/on attributes.
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.
21
27
  #
22
28
  # Examples:
23
29
  #
24
- # product.touch # updates updated_at
25
- # product.touch(:designed_at) # updates the designed_at attribute
30
+ # product.touch # updates updated_at/on
31
+ # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
26
32
  def touch(attribute = nil)
27
- current_time = current_time_from_proper_timezone
28
-
29
- if attribute
30
- write_attribute(attribute, current_time)
31
- else
32
- write_attribute('updated_at', current_time) if respond_to?(:updated_at)
33
- write_attribute('updated_on', current_time) if respond_to?(:updated_on)
34
- end
35
-
36
- save!
33
+ update_attribute(attribute, current_time_from_proper_timezone)
37
34
  end
38
35
 
39
36
  private
37
+
40
38
  def create #:nodoc:
41
39
  if record_timestamps
42
40
  current_time = current_time_from_proper_timezone
43
41
 
44
- write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
45
- write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
42
+ timestamp_attributes_for_create.each do |column|
43
+ write_attribute(column.to_s, current_time) if respond_to?(column) && self.send(column).nil?
44
+ end
46
45
 
47
- write_attribute('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil?
48
- write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_on.nil?
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
49
  end
50
50
 
51
51
  super
52
52
  end
53
53
 
54
54
  def update(*args) #:nodoc:
55
- if record_timestamps && (!partial_updates? || changed?)
56
- current_time = current_time_from_proper_timezone
55
+ record_update_timestamps if !partial_updates? || changed?
56
+ super
57
+ end
57
58
 
58
- write_attribute('updated_at', current_time) if respond_to?(:updated_at)
59
- write_attribute('updated_on', current_time) if respond_to?(:updated_on)
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
60
65
  end
66
+ end
61
67
 
62
- super
68
+ def timestamp_attributes_for_update_in_model #:nodoc:
69
+ timestamp_attributes_for_update.select { |elem| respond_to?(elem) }
70
+ end
71
+
72
+ def timestamp_attributes_for_update #:nodoc:
73
+ [:updated_at, :updated_on]
74
+ end
75
+
76
+ def timestamp_attributes_for_create #:nodoc:
77
+ [:created_at, :created_on]
78
+ end
79
+
80
+ def all_timestamp_attributes #:nodoc:
81
+ timestamp_attributes_for_update + timestamp_attributes_for_create
63
82
  end
64
83
 
65
- def current_time_from_proper_timezone
84
+ def current_time_from_proper_timezone #:nodoc:
66
85
  self.class.default_timezone == :utc ? Time.now.utc : Time.now
67
86
  end
68
87
  end
69
- end
88
+ end
89
+