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
@@ -216,7 +216,13 @@ module ActiveRecord
216
216
  super(connection, logger)
217
217
  @connection_parameters, @config = connection_parameters, config
218
218
 
219
+ # @local_tz is initialized as nil to avoid warnings when connect tries to use it
220
+ @local_tz = nil
221
+ @table_alias_length = nil
222
+ @postgresql_version = nil
223
+
219
224
  connect
225
+ @local_tz = execute('SHOW TIME ZONE').first["TimeZone"]
220
226
  end
221
227
 
222
228
  # Is this connection alive and ready for queries?
@@ -224,7 +230,7 @@ module ActiveRecord
224
230
  if @connection.respond_to?(:status)
225
231
  @connection.status == PGconn::CONNECTION_OK
226
232
  else
227
- # We're asking the driver, not ActiveRecord, so use @connection.query instead of #query
233
+ # We're asking the driver, not Active Record, so use @connection.query instead of #query
228
234
  @connection.query 'SELECT 1'
229
235
  true
230
236
  end
@@ -258,7 +264,7 @@ module ActiveRecord
258
264
  true
259
265
  end
260
266
 
261
- # Does PostgreSQL support finding primary key on non-ActiveRecord tables?
267
+ # Does PostgreSQL support finding primary key on non-Active Record tables?
262
268
  def supports_primary_key? #:nodoc:
263
269
  true
264
270
  end
@@ -305,14 +311,16 @@ module ActiveRecord
305
311
 
306
312
  # Quotes PostgreSQL-specific data types for SQL input.
307
313
  def quote(value, column = nil) #:nodoc:
308
- if value.kind_of?(String) && column && column.type == :binary
314
+ return super unless column
315
+
316
+ if value.kind_of?(String) && column.type == :binary
309
317
  "'#{escape_bytea(value)}'"
310
- elsif value.kind_of?(String) && column && column.sql_type == 'xml'
318
+ elsif value.kind_of?(String) && column.sql_type == 'xml'
311
319
  "xml '#{quote_string(value)}'"
312
- elsif value.kind_of?(Numeric) && column && column.sql_type == 'money'
320
+ elsif value.kind_of?(Numeric) && column.sql_type == 'money'
313
321
  # Not truly string input, so doesn't require (or allow) escape string syntax.
314
- "'#{value.to_s}'"
315
- elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
322
+ "'#{value}'"
323
+ elsif value.kind_of?(String) && column.sql_type =~ /^bit/
316
324
  case value
317
325
  when /^[01]*$/
318
326
  "B'#{value}'" # Bit-string notation
@@ -367,12 +375,12 @@ module ActiveRecord
367
375
 
368
376
  def supports_disable_referential_integrity?() #:nodoc:
369
377
  version = query("SHOW server_version")[0][0].split('.')
370
- (version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false
378
+ version[0].to_i >= 8 && version[1].to_i >= 1
371
379
  rescue
372
380
  return false
373
381
  end
374
382
 
375
- def disable_referential_integrity(&block) #:nodoc:
383
+ def disable_referential_integrity #:nodoc:
376
384
  if supports_disable_referential_integrity?() then
377
385
  execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
378
386
  end
@@ -428,17 +436,37 @@ module ActiveRecord
428
436
  def result_as_array(res) #:nodoc:
429
437
  # check if we have any binary column and if they need escaping
430
438
  unescape_col = []
431
- for j in 0...res.nfields do
432
- # unescape string passed BYTEA field (OID == 17)
433
- unescape_col << ( res.ftype(j)==17 )
439
+ res.nfields.times do |j|
440
+ unescape_col << res.ftype(j)
434
441
  end
435
442
 
436
443
  ary = []
437
- for i in 0...res.ntuples do
444
+ res.ntuples.times do |i|
438
445
  ary << []
439
- for j in 0...res.nfields do
446
+ res.nfields.times do |j|
440
447
  data = res.getvalue(i,j)
441
- data = unescape_bytea(data) if unescape_col[j] and data.is_a?(String)
448
+ case unescape_col[j]
449
+
450
+ # unescape string passed BYTEA field (OID == 17)
451
+ when BYTEA_COLUMN_TYPE_OID
452
+ data = unescape_bytea(data) if String === data
453
+
454
+ # If this is a money type column and there are any currency symbols,
455
+ # then strip them off. Indeed it would be prettier to do this in
456
+ # PostgreSQLColumn.string_to_decimal but would break form input
457
+ # fields that call value_before_type_cast.
458
+ when MONEY_COLUMN_TYPE_OID
459
+ # Because money output is formatted according to the locale, there are two
460
+ # cases to consider (note the decimal separators):
461
+ # (1) $12,345,678.12
462
+ # (2) $12.345.678,12
463
+ case data
464
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
465
+ data.gsub!(/[^-\d\.]/, '')
466
+ when /^-?\D+[\d\.]+,\d{2}$/ # (2)
467
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
468
+ end
469
+ end
442
470
  ary[i] << data
443
471
  end
444
472
  end
@@ -606,27 +634,22 @@ module ActiveRecord
606
634
  SQL
607
635
 
608
636
 
609
- indexes = []
610
-
611
- indexes = result.map do |row|
637
+ result.map do |row|
612
638
  index_name = row[0]
613
639
  unique = row[1] == 't'
614
640
  indkey = row[2].split(" ")
615
641
  oid = row[3]
616
642
 
617
- columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = r[0]; attlist}
618
- SELECT a.attname, a.attnum
643
+ columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
644
+ SELECT a.attnum, a.attname
619
645
  FROM pg_attribute a
620
646
  WHERE a.attrelid = #{oid}
621
647
  AND a.attnum IN (#{indkey.join(",")})
622
648
  SQL
623
649
 
624
- column_names = indkey.map {|attnum| columns[attnum] }
625
- IndexDefinition.new(table_name, index_name, unique, column_names)
626
-
627
- end
628
-
629
- indexes
650
+ column_names = columns.values_at(*indkey).compact
651
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
652
+ end.compact
630
653
  end
631
654
 
632
655
  # Returns the list of all column definitions for a table.
@@ -830,11 +853,12 @@ module ActiveRecord
830
853
  # Maps logical Rails types to PostgreSQL-specific data types.
831
854
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
832
855
  return super unless type.to_s == 'integer'
856
+ return 'integer' unless limit
833
857
 
834
858
  case limit
835
- when 1..2; 'smallint'
836
- when 3..4, nil; 'integer'
837
- when 5..8; 'bigint'
859
+ when 1, 2; 'smallint'
860
+ when 3, 4; 'integer'
861
+ when 5..8; 'bigint'
838
862
  else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
839
863
  end
840
864
  end
@@ -891,6 +915,8 @@ module ActiveRecord
891
915
  private
892
916
  # The internal PostgreSQL identifier of the money data type.
893
917
  MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
918
+ # The internal PostgreSQL identifier of the BYTEA data type.
919
+ BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
894
920
 
895
921
  # Connects to a PostgreSQL server and sets up the adapter depending on the
896
922
  # connected server's characteristics.
@@ -925,9 +951,13 @@ module ActiveRecord
925
951
  # Use standard-conforming strings if available so we don't have to do the E'...' dance.
926
952
  set_standard_conforming_strings
927
953
 
928
- # If using ActiveRecord's time zone support configure the connection to return
954
+ # If using Active Record's time zone support configure the connection to return
929
955
  # TIMESTAMP WITH ZONE types in UTC.
930
- execute("SET time zone 'UTC'") if ActiveRecord::Base.default_timezone == :utc
956
+ if ActiveRecord::Base.default_timezone == :utc
957
+ execute("SET time zone 'UTC'")
958
+ elsif @local_tz
959
+ execute("SET time zone '#{@local_tz}'")
960
+ end
931
961
  end
932
962
 
933
963
  # Returns the current ID of a table's sequence.
@@ -939,51 +969,17 @@ module ActiveRecord
939
969
  # conversions that are required to be performed here instead of in PostgreSQLColumn.
940
970
  def select(sql, name = nil)
941
971
  fields, rows = select_raw(sql, name)
942
- result = []
943
- for row in rows
944
- row_hash = {}
945
- fields.each_with_index do |f, i|
946
- row_hash[f] = row[i]
947
- end
948
- result << row_hash
972
+ rows.map do |row|
973
+ Hash[*fields.zip(row).flatten]
949
974
  end
950
- result
951
975
  end
952
976
 
953
977
  def select_raw(sql, name = nil)
954
978
  res = execute(sql, name)
955
979
  results = result_as_array(res)
956
- fields = []
957
- rows = []
958
- if res.ntuples > 0
959
- fields = res.fields
960
- results.each do |row|
961
- hashed_row = {}
962
- row.each_index do |cell_index|
963
- # If this is a money type column and there are any currency symbols,
964
- # then strip them off. Indeed it would be prettier to do this in
965
- # PostgreSQLColumn.string_to_decimal but would break form input
966
- # fields that call value_before_type_cast.
967
- if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
968
- # Because money output is formatted according to the locale, there are two
969
- # cases to consider (note the decimal separators):
970
- # (1) $12,345,678.12
971
- # (2) $12.345.678,12
972
- case column = row[cell_index]
973
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
974
- row[cell_index] = column.gsub(/[^-\d\.]/, '')
975
- when /^-?\D+[\d\.]+,\d{2}$/ # (2)
976
- row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.')
977
- end
978
- end
979
-
980
- hashed_row[fields[cell_index]] = column
981
- end
982
- rows << row
983
- end
984
- end
980
+ fields = res.fields
985
981
  res.clear
986
- return fields, rows
982
+ return fields, results
987
983
  end
988
984
 
989
985
  # Returns the list of a table's column names, data types, and default values.
@@ -4,7 +4,21 @@ module ActiveRecord
4
4
  class Base
5
5
  # sqlite3 adapter reuses sqlite_connection.
6
6
  def self.sqlite3_connection(config) # :nodoc:
7
- parse_sqlite_config!(config)
7
+ # Require database.
8
+ unless config[:database]
9
+ raise ArgumentError, "No database file specified. Missing argument: database"
10
+ end
11
+
12
+ # Allow database path relative to Rails.root, but only if
13
+ # the database path is not the special path that tells
14
+ # Sqlite to build a database only in memory.
15
+ if defined?(Rails.root) && ':memory:' != config[:database]
16
+ config[:database] = File.expand_path(config[:database], Rails.root)
17
+ end
18
+
19
+ unless 'sqlite3' == config[:adapter]
20
+ raise ArgumentError, 'adapter name should be "sqlite3"'
21
+ end
8
22
 
9
23
  unless self.class.const_defined?(:SQLite3)
10
24
  require_library_or_gem(config[:adapter])
@@ -12,8 +26,7 @@ module ActiveRecord
12
26
 
13
27
  db = SQLite3::Database.new(
14
28
  config[:database],
15
- :results_as_hash => true,
16
- :type_translation => false
29
+ :results_as_hash => true
17
30
  )
18
31
 
19
32
  db.busy_timeout(config[:timeout]) unless config[:timeout].nil?
@@ -24,13 +37,13 @@ module ActiveRecord
24
37
 
25
38
  module ConnectionAdapters #:nodoc:
26
39
  class SQLite3Adapter < SQLiteAdapter # :nodoc:
27
-
40
+
28
41
  # Returns the current database encoding format as a string, eg: 'UTF-8'
29
42
  def encoding
30
43
  if @connection.respond_to?(:encoding)
31
- @connection.encoding[0]['encoding']
44
+ @connection.encoding.to_s
32
45
  else
33
- encoding = @connection.send(:get_query_pragma, 'encoding')
46
+ encoding = @connection.execute('PRAGMA encoding')
34
47
  encoding[0]['encoding']
35
48
  end
36
49
  end
@@ -2,25 +2,6 @@ require 'active_record/connection_adapters/abstract_adapter'
2
2
  require 'active_support/core_ext/kernel/requires'
3
3
 
4
4
  module ActiveRecord
5
- class Base
6
- class << self
7
- private
8
- def parse_sqlite_config!(config)
9
- # Require database.
10
- unless config[:database]
11
- raise ArgumentError, "No database file specified. Missing argument: database"
12
- end
13
-
14
- # Allow database path relative to Rails.root, but only if
15
- # the database path is not the special path that tells
16
- # Sqlite to build a database only in memory.
17
- if defined?(Rails.root) && ':memory:' != config[:database]
18
- config[:database] = File.expand_path(config[:database], Rails.root)
19
- end
20
- end
21
- end
22
- end
23
-
24
5
  module ConnectionAdapters #:nodoc:
25
6
  class SQLiteColumn < Column #:nodoc:
26
7
  class << self
@@ -151,7 +132,7 @@ module ActiveRecord
151
132
  # DATABASE STATEMENTS ======================================
152
133
 
153
134
  def execute(sql, name = nil) #:nodoc:
154
- catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
135
+ log(sql, name) { @connection.execute(sql) }
155
136
  end
156
137
 
157
138
  def update_sql(sql, name = nil) #:nodoc:
@@ -176,15 +157,15 @@ module ActiveRecord
176
157
  end
177
158
 
178
159
  def begin_db_transaction #:nodoc:
179
- catch_schema_changes { @connection.transaction }
160
+ @connection.transaction
180
161
  end
181
162
 
182
163
  def commit_db_transaction #:nodoc:
183
- catch_schema_changes { @connection.commit }
164
+ @connection.commit
184
165
  end
185
166
 
186
167
  def rollback_db_transaction #:nodoc:
187
- catch_schema_changes { @connection.rollback }
168
+ @connection.rollback
188
169
  end
189
170
 
190
171
  # SCHEMA STATEMENTS ========================================
@@ -209,16 +190,21 @@ module ActiveRecord
209
190
 
210
191
  def indexes(table_name, name = nil) #:nodoc:
211
192
  execute("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
212
- index = IndexDefinition.new(table_name, row['name'])
213
- index.unique = row['unique'].to_i != 0
214
- index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
215
- index
193
+ IndexDefinition.new(
194
+ table_name,
195
+ row['name'],
196
+ row['unique'].to_i != 0,
197
+ execute("PRAGMA index_info('#{row['name']}')").map { |col|
198
+ col['name']
199
+ })
216
200
  end
217
201
  end
218
202
 
219
203
  def primary_key(table_name) #:nodoc:
220
- column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
221
- column ? column['name'] : nil
204
+ column = table_structure(table_name).find { |field|
205
+ field['pk'].to_i == 1
206
+ }
207
+ column && column['name']
222
208
  end
223
209
 
224
210
  def remove_index!(table_name, index_name) #:nodoc:
@@ -246,6 +232,7 @@ module ActiveRecord
246
232
  end
247
233
 
248
234
  def remove_column(table_name, *column_names) #:nodoc:
235
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
249
236
  column_names.flatten.each do |column_name|
250
237
  alter_table(table_name) do |definition|
251
238
  definition.columns.delete(definition[column_name])
@@ -296,10 +283,8 @@ module ActiveRecord
296
283
  def select(sql, name = nil) #:nodoc:
297
284
  execute(sql, name).map do |row|
298
285
  record = {}
299
- row.each_key do |key|
300
- if key.is_a?(String)
301
- record[key.sub(/^"?\w+"?\./, '')] = row[key]
302
- end
286
+ row.each do |key, value|
287
+ record[key.sub(/^"?\w+"?\./, '')] = value if key.is_a?(String)
303
288
  end
304
289
  record
305
290
  end
@@ -390,26 +375,15 @@ module ActiveRecord
390
375
  end
391
376
  end
392
377
 
393
- def catch_schema_changes
394
- return yield
395
- rescue ActiveRecord::StatementInvalid => exception
396
- if exception.message =~ /database schema has changed/
397
- reconnect!
398
- retry
399
- else
400
- raise
401
- end
402
- end
403
-
404
378
  def sqlite_version
405
379
  @sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)'))
406
380
  end
407
381
 
408
382
  def default_primary_key_type
409
383
  if supports_autoincrement?
410
- 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'.freeze
384
+ 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
411
385
  else
412
- 'INTEGER PRIMARY KEY NOT NULL'.freeze
386
+ 'INTEGER PRIMARY KEY NOT NULL'
413
387
  end
414
388
  end
415
389
 
@@ -1,4 +1,5 @@
1
1
  module ActiveRecord
2
+ # = Active Record Counter Cache
2
3
  module CounterCache
3
4
  # Resets one or more counter caches to their correct value using an SQL
4
5
  # count query. This is useful when adding new counter caches, or if the
@@ -16,9 +17,18 @@ module ActiveRecord
16
17
  def reset_counters(id, *counters)
17
18
  object = find(id)
18
19
  counters.each do |association|
19
- child_class = reflect_on_association(association.to_sym).klass
20
- belongs_name = self.name.demodulize.underscore.to_sym
21
- counter_name = child_class.reflect_on_association(belongs_name).counter_cache_column
20
+ has_many_association = reflect_on_association(association.to_sym)
21
+
22
+ expected_name = if has_many_association.options[:as]
23
+ has_many_association.options[:as].to_s.classify
24
+ else
25
+ self.name
26
+ end
27
+
28
+ child_class = has_many_association.klass
29
+ belongs_to = child_class.reflect_on_all_associations(:belongs_to)
30
+ reflection = belongs_to.find { |e| e.class_name == expected_name }
31
+ counter_name = reflection.counter_cache_column
22
32
 
23
33
  self.unscoped.where(arel_table[self.primary_key].eq(object.id)).arel.update({
24
34
  arel_table[counter_name] => object.send(association).count
@@ -102,4 +112,4 @@ module ActiveRecord
102
112
  update_counters(id, counter_name => -1)
103
113
  end
104
114
  end
105
- end
115
+ end