square-activerecord 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/CHANGELOG +6140 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +179 -0
  5. data/examples/simple.rb +14 -0
  6. data/lib/active_record.rb +124 -0
  7. data/lib/active_record/aggregations.rb +277 -0
  8. data/lib/active_record/association_preload.rb +430 -0
  9. data/lib/active_record/associations.rb +2307 -0
  10. data/lib/active_record/associations/association_collection.rb +572 -0
  11. data/lib/active_record/associations/association_proxy.rb +299 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +115 -0
  17. data/lib/active_record/associations/has_one_association.rb +143 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  19. data/lib/active_record/associations/through_association_scope.rb +154 -0
  20. data/lib/active_record/attribute_methods.rb +60 -0
  21. data/lib/active_record/attribute_methods/before_type_cast.rb +30 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +56 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +145 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
  27. data/lib/active_record/attribute_methods/write.rb +43 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1904 -0
  30. data/lib/active_record/callbacks.rb +284 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -0
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  33. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +333 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -0
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +539 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -0
  43. data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
  44. data/lib/active_record/counter_cache.rb +115 -0
  45. data/lib/active_record/dynamic_finder_match.rb +56 -0
  46. data/lib/active_record/dynamic_scope_match.rb +23 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1006 -0
  49. data/lib/active_record/locale/en.yml +40 -0
  50. data/lib/active_record/locking/optimistic.rb +172 -0
  51. data/lib/active_record/locking/pessimistic.rb +55 -0
  52. data/lib/active_record/log_subscriber.rb +48 -0
  53. data/lib/active_record/migration.rb +617 -0
  54. data/lib/active_record/named_scope.rb +138 -0
  55. data/lib/active_record/nested_attributes.rb +419 -0
  56. data/lib/active_record/observer.rb +125 -0
  57. data/lib/active_record/persistence.rb +290 -0
  58. data/lib/active_record/query_cache.rb +36 -0
  59. data/lib/active_record/railtie.rb +91 -0
  60. data/lib/active_record/railties/controller_runtime.rb +38 -0
  61. data/lib/active_record/railties/databases.rake +512 -0
  62. data/lib/active_record/reflection.rb +411 -0
  63. data/lib/active_record/relation.rb +394 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +295 -0
  66. data/lib/active_record/relation/finder_methods.rb +363 -0
  67. data/lib/active_record/relation/predicate_builder.rb +48 -0
  68. data/lib/active_record/relation/query_methods.rb +303 -0
  69. data/lib/active_record/relation/spawn_methods.rb +132 -0
  70. data/lib/active_record/schema.rb +59 -0
  71. data/lib/active_record/schema_dumper.rb +195 -0
  72. data/lib/active_record/serialization.rb +60 -0
  73. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  74. data/lib/active_record/session_store.rb +340 -0
  75. data/lib/active_record/test_case.rb +67 -0
  76. data/lib/active_record/timestamp.rb +88 -0
  77. data/lib/active_record/transactions.rb +359 -0
  78. data/lib/active_record/validations.rb +84 -0
  79. data/lib/active_record/validations/associated.rb +48 -0
  80. data/lib/active_record/validations/uniqueness.rb +190 -0
  81. data/lib/active_record/version.rb +10 -0
  82. data/lib/rails/generators/active_record.rb +19 -0
  83. data/lib/rails/generators/active_record/migration.rb +15 -0
  84. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  85. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  86. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  87. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  88. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  89. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  90. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  91. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  92. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  93. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  94. 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
+