square-activerecord 3.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6140 -0
- data/README.rdoc +222 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +179 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +124 -0
- data/lib/active_record/aggregations.rb +277 -0
- data/lib/active_record/association_preload.rb +430 -0
- data/lib/active_record/associations.rb +2307 -0
- data/lib/active_record/associations/association_collection.rb +572 -0
- data/lib/active_record/associations/association_proxy.rb +299 -0
- data/lib/active_record/associations/belongs_to_association.rb +91 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
- data/lib/active_record/associations/has_many_association.rb +128 -0
- data/lib/active_record/associations/has_many_through_association.rb +115 -0
- data/lib/active_record/associations/has_one_association.rb +143 -0
- data/lib/active_record/associations/has_one_through_association.rb +40 -0
- data/lib/active_record/associations/through_association_scope.rb +154 -0
- data/lib/active_record/attribute_methods.rb +60 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +30 -0
- data/lib/active_record/attribute_methods/dirty.rb +95 -0
- data/lib/active_record/attribute_methods/primary_key.rb +56 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +145 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
- data/lib/active_record/attribute_methods/write.rb +43 -0
- data/lib/active_record/autosave_association.rb +369 -0
- data/lib/active_record/base.rb +1904 -0
- data/lib/active_record/callbacks.rb +284 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +333 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +539 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
- data/lib/active_record/counter_cache.rb +115 -0
- data/lib/active_record/dynamic_finder_match.rb +56 -0
- data/lib/active_record/dynamic_scope_match.rb +23 -0
- data/lib/active_record/errors.rb +172 -0
- data/lib/active_record/fixtures.rb +1006 -0
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +172 -0
- data/lib/active_record/locking/pessimistic.rb +55 -0
- data/lib/active_record/log_subscriber.rb +48 -0
- data/lib/active_record/migration.rb +617 -0
- data/lib/active_record/named_scope.rb +138 -0
- data/lib/active_record/nested_attributes.rb +419 -0
- data/lib/active_record/observer.rb +125 -0
- data/lib/active_record/persistence.rb +290 -0
- data/lib/active_record/query_cache.rb +36 -0
- data/lib/active_record/railtie.rb +91 -0
- data/lib/active_record/railties/controller_runtime.rb +38 -0
- data/lib/active_record/railties/databases.rake +512 -0
- data/lib/active_record/reflection.rb +411 -0
- data/lib/active_record/relation.rb +394 -0
- data/lib/active_record/relation/batches.rb +89 -0
- data/lib/active_record/relation/calculations.rb +295 -0
- data/lib/active_record/relation/finder_methods.rb +363 -0
- data/lib/active_record/relation/predicate_builder.rb +48 -0
- data/lib/active_record/relation/query_methods.rb +303 -0
- data/lib/active_record/relation/spawn_methods.rb +132 -0
- data/lib/active_record/schema.rb +59 -0
- data/lib/active_record/schema_dumper.rb +195 -0
- data/lib/active_record/serialization.rb +60 -0
- data/lib/active_record/serializers/xml_serializer.rb +244 -0
- data/lib/active_record/session_store.rb +340 -0
- data/lib/active_record/test_case.rb +67 -0
- data/lib/active_record/timestamp.rb +88 -0
- data/lib/active_record/transactions.rb +359 -0
- data/lib/active_record/validations.rb +84 -0
- data/lib/active_record/validations/associated.rb +48 -0
- data/lib/active_record/validations/uniqueness.rb +190 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +19 -0
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
- metadata +223 -0
@@ -0,0 +1,340 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# = Active Record Session Store
|
3
|
+
#
|
4
|
+
# A session store backed by an Active Record class. A default class is
|
5
|
+
# provided, but any object duck-typing to an Active Record Session class
|
6
|
+
# with text +session_id+ and +data+ attributes is sufficient.
|
7
|
+
#
|
8
|
+
# The default assumes a +sessions+ tables with columns:
|
9
|
+
# +id+ (numeric primary key),
|
10
|
+
# +session_id+ (text, or longtext if your session data exceeds 65K), and
|
11
|
+
# +data+ (text or longtext; careful if your session data exceeds 65KB).
|
12
|
+
#
|
13
|
+
# The +session_id+ column should always be indexed for speedy lookups.
|
14
|
+
# Session data is marshaled to the +data+ column in Base64 format.
|
15
|
+
# If the data you write is larger than the column's size limit,
|
16
|
+
# ActionController::SessionOverflowError will be raised.
|
17
|
+
#
|
18
|
+
# You may configure the table name, primary key, and data column.
|
19
|
+
# For example, at the end of <tt>config/application.rb</tt>:
|
20
|
+
#
|
21
|
+
# ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
|
22
|
+
# ActiveRecord::SessionStore::Session.primary_key = 'session_id'
|
23
|
+
# ActiveRecord::SessionStore::Session.data_column_name = 'legacy_session_data'
|
24
|
+
#
|
25
|
+
# Note that setting the primary key to the +session_id+ frees you from
|
26
|
+
# having a separate +id+ column if you don't want it. However, you must
|
27
|
+
# set <tt>session.model.id = session.session_id</tt> by hand! A before filter
|
28
|
+
# on ApplicationController is a good place.
|
29
|
+
#
|
30
|
+
# Since the default class is a simple Active Record, you get timestamps
|
31
|
+
# for free if you add +created_at+ and +updated_at+ datetime columns to
|
32
|
+
# the +sessions+ table, making periodic session expiration a snap.
|
33
|
+
#
|
34
|
+
# You may provide your own session class implementation, whether a
|
35
|
+
# feature-packed Active Record or a bare-metal high-performance SQL
|
36
|
+
# store, by setting
|
37
|
+
#
|
38
|
+
# ActiveRecord::SessionStore.session_class = MySessionClass
|
39
|
+
#
|
40
|
+
# You must implement these methods:
|
41
|
+
#
|
42
|
+
# self.find_by_session_id(session_id)
|
43
|
+
# initialize(hash_of_session_id_and_data)
|
44
|
+
# attr_reader :session_id
|
45
|
+
# attr_accessor :data
|
46
|
+
# save
|
47
|
+
# destroy
|
48
|
+
#
|
49
|
+
# The example SqlBypass class is a generic SQL session store. You may
|
50
|
+
# use it as a basis for high-performance database-specific stores.
|
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.drop_table table_name
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_table!
|
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
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# The default Active Record class.
|
75
|
+
class Session < ActiveRecord::Base
|
76
|
+
extend ClassMethods
|
77
|
+
|
78
|
+
##
|
79
|
+
# :singleton-method:
|
80
|
+
# Customizable data column name. Defaults to 'data'.
|
81
|
+
cattr_accessor :data_column_name
|
82
|
+
self.data_column_name = 'data'
|
83
|
+
|
84
|
+
before_save :marshal_data!
|
85
|
+
before_save :raise_on_session_data_overflow!
|
86
|
+
|
87
|
+
class << self
|
88
|
+
def data_column_size_limit
|
89
|
+
@data_column_size_limit ||= columns_hash[data_column_name].limit
|
90
|
+
end
|
91
|
+
|
92
|
+
# Hook to set up sessid compatibility.
|
93
|
+
def find_by_session_id(session_id)
|
94
|
+
setup_sessid_compatibility!
|
95
|
+
find_by_session_id(session_id)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def session_id_column
|
100
|
+
'session_id'
|
101
|
+
end
|
102
|
+
|
103
|
+
# Compatibility with tables using sessid instead of session_id.
|
104
|
+
def setup_sessid_compatibility!
|
105
|
+
# Reset column info since it may be stale.
|
106
|
+
reset_column_information
|
107
|
+
if columns_hash['sessid']
|
108
|
+
def self.find_by_session_id(*args)
|
109
|
+
find_by_sessid(*args)
|
110
|
+
end
|
111
|
+
|
112
|
+
define_method(:session_id) { sessid }
|
113
|
+
define_method(:session_id=) { |session_id| self.sessid = session_id }
|
114
|
+
else
|
115
|
+
class << self; remove_method :find_by_session_id; end
|
116
|
+
|
117
|
+
def self.find_by_session_id(session_id)
|
118
|
+
find :first, :conditions => {:session_id=>session_id}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def initialize(attributes = nil)
|
125
|
+
@data = nil
|
126
|
+
super
|
127
|
+
end
|
128
|
+
|
129
|
+
# Lazy-unmarshal session state.
|
130
|
+
def data
|
131
|
+
@data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
|
132
|
+
end
|
133
|
+
|
134
|
+
attr_writer :data
|
135
|
+
|
136
|
+
# Has the session been loaded yet?
|
137
|
+
def loaded?
|
138
|
+
@data
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
def marshal_data!
|
143
|
+
return false unless loaded?
|
144
|
+
write_attribute(@@data_column_name, self.class.marshal(data))
|
145
|
+
end
|
146
|
+
|
147
|
+
# Ensures that the data about to be stored in the database is not
|
148
|
+
# larger than the data storage column. Raises
|
149
|
+
# ActionController::SessionOverflowError.
|
150
|
+
def raise_on_session_data_overflow!
|
151
|
+
return false unless loaded?
|
152
|
+
limit = self.class.data_column_size_limit
|
153
|
+
if limit and read_attribute(@@data_column_name).size > limit
|
154
|
+
raise ActionController::SessionOverflowError
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# A barebones session store which duck-types with the default session
|
160
|
+
# store but bypasses Active Record and issues SQL directly. This is
|
161
|
+
# an example session model class meant as a basis for your own classes.
|
162
|
+
#
|
163
|
+
# The database connection, table name, and session id and data columns
|
164
|
+
# are configurable class attributes. Marshaling and unmarshaling
|
165
|
+
# are implemented as class methods that you may override. By default,
|
166
|
+
# marshaling data is
|
167
|
+
#
|
168
|
+
# ActiveSupport::Base64.encode64(Marshal.dump(data))
|
169
|
+
#
|
170
|
+
# and unmarshaling data is
|
171
|
+
#
|
172
|
+
# Marshal.load(ActiveSupport::Base64.decode64(data))
|
173
|
+
#
|
174
|
+
# This marshaling behavior is intended to store the widest range of
|
175
|
+
# binary session data in a +text+ column. For higher performance,
|
176
|
+
# store in a +blob+ column instead and forgo the Base64 encoding.
|
177
|
+
class SqlBypass
|
178
|
+
extend ClassMethods
|
179
|
+
|
180
|
+
##
|
181
|
+
# :singleton-method:
|
182
|
+
# Use the ActiveRecord::Base.connection by default.
|
183
|
+
cattr_accessor :connection
|
184
|
+
|
185
|
+
##
|
186
|
+
# :singleton-method:
|
187
|
+
# The table name defaults to 'sessions'.
|
188
|
+
cattr_accessor :table_name
|
189
|
+
@@table_name = 'sessions'
|
190
|
+
|
191
|
+
##
|
192
|
+
# :singleton-method:
|
193
|
+
# The session id field defaults to 'session_id'.
|
194
|
+
cattr_accessor :session_id_column
|
195
|
+
@@session_id_column = 'session_id'
|
196
|
+
|
197
|
+
##
|
198
|
+
# :singleton-method:
|
199
|
+
# The data field defaults to 'data'.
|
200
|
+
cattr_accessor :data_column
|
201
|
+
@@data_column = 'data'
|
202
|
+
|
203
|
+
class << self
|
204
|
+
alias :data_column_name :data_column
|
205
|
+
|
206
|
+
remove_method :connection
|
207
|
+
def connection
|
208
|
+
@@connection ||= ActiveRecord::Base.connection
|
209
|
+
end
|
210
|
+
|
211
|
+
# Look up a session by id and unmarshal its data if found.
|
212
|
+
def find_by_session_id(session_id)
|
213
|
+
if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id)}")
|
214
|
+
new(:session_id => session_id, :marshaled_data => record['data'])
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
attr_reader :session_id, :new_record
|
220
|
+
alias :new_record? :new_record
|
221
|
+
|
222
|
+
attr_writer :data
|
223
|
+
|
224
|
+
# Look for normal and marshaled data, self.find_by_session_id's way of
|
225
|
+
# telling us to postpone unmarshaling until the data is requested.
|
226
|
+
# We need to handle a normal data attribute in case of a new record.
|
227
|
+
def initialize(attributes)
|
228
|
+
@session_id = attributes[:session_id]
|
229
|
+
@data = attributes[:data]
|
230
|
+
@marshaled_data = attributes[:marshaled_data]
|
231
|
+
@new_record = @marshaled_data.nil?
|
232
|
+
end
|
233
|
+
|
234
|
+
# Lazy-unmarshal session state.
|
235
|
+
def data
|
236
|
+
unless @data
|
237
|
+
if @marshaled_data
|
238
|
+
@data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
|
239
|
+
else
|
240
|
+
@data = {}
|
241
|
+
end
|
242
|
+
end
|
243
|
+
@data
|
244
|
+
end
|
245
|
+
|
246
|
+
def loaded?
|
247
|
+
@data
|
248
|
+
end
|
249
|
+
|
250
|
+
def save
|
251
|
+
return false unless loaded?
|
252
|
+
marshaled_data = self.class.marshal(data)
|
253
|
+
connect = connection
|
254
|
+
|
255
|
+
if @new_record
|
256
|
+
@new_record = false
|
257
|
+
connect.update <<-end_sql, 'Create session'
|
258
|
+
INSERT INTO #{table_name} (
|
259
|
+
#{connect.quote_column_name(session_id_column)},
|
260
|
+
#{connect.quote_column_name(data_column)} )
|
261
|
+
VALUES (
|
262
|
+
#{connect.quote(session_id)},
|
263
|
+
#{connect.quote(marshaled_data)} )
|
264
|
+
end_sql
|
265
|
+
else
|
266
|
+
connect.update <<-end_sql, 'Update session'
|
267
|
+
UPDATE #{table_name}
|
268
|
+
SET #{connect.quote_column_name(data_column)}=#{connect.quote(marshaled_data)}
|
269
|
+
WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
|
270
|
+
end_sql
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def destroy
|
275
|
+
return if @new_record
|
276
|
+
|
277
|
+
connect = connection
|
278
|
+
connect.delete <<-end_sql, 'Destroy session'
|
279
|
+
DELETE FROM #{table_name}
|
280
|
+
WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
|
281
|
+
end_sql
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# The class used for session storage. Defaults to
|
286
|
+
# ActiveRecord::SessionStore::Session
|
287
|
+
cattr_accessor :session_class
|
288
|
+
self.session_class = Session
|
289
|
+
|
290
|
+
SESSION_RECORD_KEY = 'rack.session.record'
|
291
|
+
|
292
|
+
private
|
293
|
+
def get_session(env, sid)
|
294
|
+
Base.silence do
|
295
|
+
sid ||= generate_sid
|
296
|
+
session = find_session(sid)
|
297
|
+
env[SESSION_RECORD_KEY] = session
|
298
|
+
[sid, session.data]
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def set_session(env, sid, session_data)
|
303
|
+
Base.silence do
|
304
|
+
record = get_session_model(env, sid)
|
305
|
+
record.data = session_data
|
306
|
+
return false unless record.save
|
307
|
+
|
308
|
+
session_data = record.data
|
309
|
+
if session_data && session_data.respond_to?(:each_value)
|
310
|
+
session_data.each_value do |obj|
|
311
|
+
obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
sid
|
317
|
+
end
|
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
|
+
|
327
|
+
def get_session_model(env, sid)
|
328
|
+
if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
|
329
|
+
env[SESSION_RECORD_KEY] = find_session(sid)
|
330
|
+
else
|
331
|
+
env[SESSION_RECORD_KEY] ||= find_session(sid)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def find_session(id)
|
336
|
+
@@session_class.find_by_session_id(id) ||
|
337
|
+
@@session_class.new(:session_id => id, :data => {})
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# = Active Record Test Case
|
3
|
+
#
|
4
|
+
# Defines some test assertions to test against SQL queries.
|
5
|
+
class TestCase < ActiveSupport::TestCase #:nodoc:
|
6
|
+
def assert_date_from_db(expected, actual, message = nil)
|
7
|
+
# SybaseAdapter doesn't have a separate column type just for dates,
|
8
|
+
# so the time is in the string and incorrectly formatted
|
9
|
+
if current_adapter?(:SybaseAdapter)
|
10
|
+
assert_equal expected.to_s, actual.to_date.to_s, message
|
11
|
+
else
|
12
|
+
assert_equal expected.to_s, actual.to_s, message
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def assert_sql(*patterns_to_match)
|
17
|
+
$queries_executed = []
|
18
|
+
yield
|
19
|
+
ensure
|
20
|
+
failed_patterns = []
|
21
|
+
patterns_to_match.each do |pattern|
|
22
|
+
failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql }
|
23
|
+
end
|
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
|
+
end
|
26
|
+
|
27
|
+
def assert_queries(num = 1)
|
28
|
+
$queries_executed = []
|
29
|
+
yield
|
30
|
+
ensure
|
31
|
+
%w{ BEGIN COMMIT }.each { |x| $queries_executed.delete(x) }
|
32
|
+
assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def assert_no_queries(&block)
|
36
|
+
assert_queries(0, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.use_concurrent_connections
|
40
|
+
setup :connection_allow_concurrency_setup
|
41
|
+
teardown :connection_allow_concurrency_teardown
|
42
|
+
end
|
43
|
+
|
44
|
+
def connection_allow_concurrency_setup
|
45
|
+
@connection = ActiveRecord::Base.remove_connection
|
46
|
+
ActiveRecord::Base.establish_connection(@connection.merge({:allow_concurrency => true}))
|
47
|
+
end
|
48
|
+
|
49
|
+
def connection_allow_concurrency_teardown
|
50
|
+
ActiveRecord::Base.clear_all_connections!
|
51
|
+
ActiveRecord::Base.establish_connection(@connection)
|
52
|
+
end
|
53
|
+
|
54
|
+
def with_kcode(kcode)
|
55
|
+
if RUBY_VERSION < '1.9'
|
56
|
+
orig_kcode, $KCODE = $KCODE, kcode
|
57
|
+
begin
|
58
|
+
yield
|
59
|
+
ensure
|
60
|
+
$KCODE = orig_kcode
|
61
|
+
end
|
62
|
+
else
|
63
|
+
yield
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module ActiveRecord
|
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:
|
9
|
+
#
|
10
|
+
# <tt>ActiveRecord::Base.record_timestamps = false</tt>
|
11
|
+
#
|
12
|
+
# Timestamps are in the local timezone by default but you can use UTC by setting:
|
13
|
+
#
|
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]
|
28
|
+
module Timestamp
|
29
|
+
extend ActiveSupport::Concern
|
30
|
+
|
31
|
+
included do
|
32
|
+
class_inheritable_accessor :record_timestamps, :instance_writer => false
|
33
|
+
self.record_timestamps = true
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def create #:nodoc:
|
39
|
+
if record_timestamps
|
40
|
+
current_time = current_time_from_proper_timezone
|
41
|
+
|
42
|
+
all_timestamp_attributes.each do |column|
|
43
|
+
write_attribute(column.to_s, current_time) if respond_to?(column) && self.send(column).nil?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def update(*args) #:nodoc:
|
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
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
63
|
+
def should_record_timestamps?
|
64
|
+
record_timestamps && (!partial_updates? || changed?)
|
65
|
+
end
|
66
|
+
|
67
|
+
def timestamp_attributes_for_update_in_model
|
68
|
+
timestamp_attributes_for_update.select { |c| self.class.column_names.include?(c.to_s) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def timestamp_attributes_for_update #:nodoc:
|
72
|
+
[:updated_at, :updated_on]
|
73
|
+
end
|
74
|
+
|
75
|
+
def timestamp_attributes_for_create #:nodoc:
|
76
|
+
[:created_at, :created_on]
|
77
|
+
end
|
78
|
+
|
79
|
+
def all_timestamp_attributes #:nodoc:
|
80
|
+
timestamp_attributes_for_create + timestamp_attributes_for_update
|
81
|
+
end
|
82
|
+
|
83
|
+
def current_time_from_proper_timezone #:nodoc:
|
84
|
+
self.class.default_timezone == :utc ? Time.now.utc : Time.now
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|