activerecord 4.1.0.beta2 → 4.1.0.rc1

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +622 -9
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_record.rb +1 -1
  5. data/lib/active_record/associations.rb +10 -7
  6. data/lib/active_record/associations/alias_tracker.rb +39 -29
  7. data/lib/active_record/associations/association.rb +1 -1
  8. data/lib/active_record/associations/association_scope.rb +56 -31
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -0
  10. data/lib/active_record/associations/builder/association.rb +6 -0
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -1
  12. data/lib/active_record/associations/collection_association.rb +33 -9
  13. data/lib/active_record/associations/collection_proxy.rb +53 -5
  14. data/lib/active_record/associations/has_many_association.rb +1 -1
  15. data/lib/active_record/associations/join_dependency.rb +5 -5
  16. data/lib/active_record/associations/join_dependency/join_association.rb +8 -8
  17. data/lib/active_record/associations/preloader.rb +1 -1
  18. data/lib/active_record/associations/singular_association.rb +1 -1
  19. data/lib/active_record/attribute_methods.rb +28 -5
  20. data/lib/active_record/attribute_methods/dirty.rb +27 -4
  21. data/lib/active_record/attribute_methods/read.rb +1 -1
  22. data/lib/active_record/attribute_methods/serialization.rb +18 -0
  23. data/lib/active_record/autosave_association.rb +1 -1
  24. data/lib/active_record/base.rb +1 -1
  25. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -2
  26. data/lib/active_record/connection_adapters/abstract/database_statements.rb +16 -9
  27. data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -1
  28. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -8
  29. data/lib/active_record/connection_adapters/abstract/transaction.rb +4 -0
  30. data/lib/active_record/connection_adapters/abstract_adapter.rb +15 -5
  31. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +2 -6
  32. data/lib/active_record/connection_adapters/connection_specification.rb +200 -43
  33. data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -1
  34. data/lib/active_record/connection_adapters/mysql_adapter.rb +8 -2
  35. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +3 -2
  36. data/lib/active_record/connection_adapters/postgresql/cast.rb +7 -7
  37. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -2
  38. data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
  39. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +13 -0
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -17
  41. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +25 -3
  42. data/lib/active_record/connection_handling.rb +64 -3
  43. data/lib/active_record/core.rb +28 -24
  44. data/lib/active_record/dynamic_matchers.rb +6 -2
  45. data/lib/active_record/enum.rb +111 -17
  46. data/lib/active_record/errors.rb +12 -0
  47. data/lib/active_record/fixtures.rb +13 -15
  48. data/lib/active_record/inheritance.rb +29 -9
  49. data/lib/active_record/integration.rb +4 -2
  50. data/lib/active_record/migration.rb +20 -7
  51. data/lib/active_record/migration/command_recorder.rb +18 -6
  52. data/lib/active_record/persistence.rb +10 -5
  53. data/lib/active_record/querying.rb +1 -0
  54. data/lib/active_record/railtie.rb +11 -8
  55. data/lib/active_record/railties/databases.rake +24 -38
  56. data/lib/active_record/relation.rb +3 -2
  57. data/lib/active_record/relation/batches.rb +24 -9
  58. data/lib/active_record/relation/finder_methods.rb +100 -11
  59. data/lib/active_record/relation/query_methods.rb +39 -27
  60. data/lib/active_record/result.rb +1 -1
  61. data/lib/active_record/sanitization.rb +7 -5
  62. data/lib/active_record/scoping.rb +5 -0
  63. data/lib/active_record/scoping/named.rb +6 -0
  64. data/lib/active_record/store.rb +1 -1
  65. data/lib/active_record/tasks/database_tasks.rb +45 -23
  66. data/lib/active_record/timestamp.rb +2 -2
  67. data/lib/active_record/transactions.rb +7 -7
  68. data/lib/active_record/validations/presence.rb +1 -1
  69. data/lib/active_record/version.rb +1 -1
  70. metadata +5 -6
  71. data/lib/active_record/associations/join_helper.rb +0 -36
@@ -91,8 +91,9 @@ module ActiveRecord
91
91
  end
92
92
 
93
93
  def add_item_to_array(array, current_item, quoted)
94
- if current_item.length == 0
95
- elsif !quoted && current_item == 'NULL'
94
+ return if !quoted && current_item.length == 0
95
+
96
+ if !quoted && current_item == 'NULL'
96
97
  array.push nil
97
98
  else
98
99
  array.push current_item
@@ -35,11 +35,11 @@ module ActiveRecord
35
35
  end
36
36
  end
37
37
 
38
- def hstore_to_string(object)
38
+ def hstore_to_string(object, array_member = false)
39
39
  if Hash === object
40
- object.map { |k,v|
41
- "#{escape_hstore(k)}=>#{escape_hstore(v)}"
42
- }.join ','
40
+ string = object.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(',')
41
+ string = escape_hstore(string) if array_member
42
+ string
43
43
  else
44
44
  object
45
45
  end
@@ -49,10 +49,10 @@ module ActiveRecord
49
49
  if string.nil?
50
50
  nil
51
51
  elsif String === string
52
- Hash[string.scan(HstorePair).map { |k,v|
52
+ Hash[string.scan(HstorePair).map { |k, v|
53
53
  v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
54
54
  k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
55
- [k,v]
55
+ [k, v]
56
56
  }]
57
57
  else
58
58
  string
@@ -146,7 +146,7 @@ module ActiveRecord
146
146
 
147
147
  def quote_and_escape(value)
148
148
  case value
149
- when "NULL"
149
+ when "NULL", Numeric
150
150
  value
151
151
  else
152
152
  value = value.gsub(/\\/, ARRAY_ESCAPE)
@@ -46,8 +46,8 @@ module ActiveRecord
46
46
 
47
47
  # Executes a SELECT query and returns an array of rows. Each row is an
48
48
  # array of field values.
49
- def select_rows(sql, name = nil)
50
- select_raw(sql, name).last
49
+ def select_rows(sql, name = nil, binds = [])
50
+ exec_query(sql, name, binds).rows
51
51
  end
52
52
 
53
53
  # Executes an INSERT query and returns the new record's ID
@@ -121,7 +121,7 @@ module ActiveRecord
121
121
  end
122
122
  when Hash
123
123
  case column.sql_type
124
- when 'hstore' then PostgreSQLColumn.hstore_to_string(value)
124
+ when 'hstore' then PostgreSQLColumn.hstore_to_string(value, array_member)
125
125
  when 'json' then PostgreSQLColumn.json_to_string(value)
126
126
  else super(value, column)
127
127
  end
@@ -126,6 +126,19 @@ module ActiveRecord
126
126
  SQL
127
127
  end
128
128
 
129
+ def index_name_exists?(table_name, index_name, default)
130
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
131
+ SELECT COUNT(*)
132
+ FROM pg_class t
133
+ INNER JOIN pg_index d ON t.oid = d.indrelid
134
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
135
+ WHERE i.relkind = 'i'
136
+ AND i.relname = '#{index_name}'
137
+ AND t.relname = '#{table_name}'
138
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
139
+ SQL
140
+ end
141
+
129
142
  # Returns an array of indexes for the given table.
130
143
  def indexes(table_name, name = nil)
131
144
  result = query(<<-SQL, 'SCHEMA')
@@ -46,7 +46,7 @@ module ActiveRecord
46
46
  # PostgreSQL-specific extensions to column definitions in a table.
47
47
  class PostgreSQLColumn < Column #:nodoc:
48
48
  attr_accessor :array
49
- # Instantiates a new PostgreSQL column definition in a table.
49
+
50
50
  def initialize(name, default, oid_type, sql_type = nil, null = true)
51
51
  @oid_type = oid_type
52
52
  default_value = self.class.extract_value_from_default(default)
@@ -62,6 +62,14 @@ module ActiveRecord
62
62
  @default_function = default if has_default_function?(default_value, default)
63
63
  end
64
64
 
65
+ def number?
66
+ !array && super
67
+ end
68
+
69
+ def text?
70
+ !array && super
71
+ end
72
+
65
73
  # :stopdoc:
66
74
  class << self
67
75
  include ConnectionAdapters::PostgreSQLColumn::Cast
@@ -578,11 +586,16 @@ module ActiveRecord
578
586
 
579
587
  # Is this connection alive and ready for queries?
580
588
  def active?
581
- @connection.connect_poll != PG::PGRES_POLLING_FAILED
589
+ @connection.query 'SELECT 1'
590
+ true
582
591
  rescue PGError
583
592
  false
584
593
  end
585
594
 
595
+ def active_threadsafe?
596
+ @connection.connect_poll != PG::PGRES_POLLING_FAILED
597
+ end
598
+
586
599
  # Close then reopen the connection.
587
600
  def reconnect!
588
601
  super
@@ -713,6 +726,10 @@ module ActiveRecord
713
726
  !native_database_types[type].nil?
714
727
  end
715
728
 
729
+ def update_table_definition(table_name, base) #:nodoc:
730
+ Table.new(table_name, base)
731
+ end
732
+
716
733
  protected
717
734
 
718
735
  # Returns the version of the connected PostgreSQL server.
@@ -792,7 +809,7 @@ module ActiveRecord
792
809
  end
793
810
  end
794
811
 
795
- FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
812
+ FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
796
813
 
797
814
  def exec_no_cache(sql, name, binds)
798
815
  log(sql, name, binds) { @connection.async_exec(sql) }
@@ -841,7 +858,11 @@ module ActiveRecord
841
858
  sql_key = sql_key(sql)
842
859
  unless @statements.key? sql_key
843
860
  nextkey = @statements.next_key
844
- @connection.prepare nextkey, sql
861
+ begin
862
+ @connection.prepare nextkey, sql
863
+ rescue => e
864
+ raise translate_exception_class(e, sql)
865
+ end
845
866
  # Clear the queue
846
867
  @connection.get_last_result
847
868
  @statements[sql_key] = nextkey
@@ -865,6 +886,12 @@ module ActiveRecord
865
886
  PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
866
887
 
867
888
  configure_connection
889
+ rescue ::PG::Error => error
890
+ if error.message.include?("does not exist")
891
+ raise ActiveRecord::NoDatabaseError.new(error.message)
892
+ else
893
+ raise error
894
+ end
868
895
  end
869
896
 
870
897
  # Configures the encoding, verbosity, schema search path, and time zone of the connection.
@@ -920,14 +947,6 @@ module ActiveRecord
920
947
  exec_query(sql, name, binds)
921
948
  end
922
949
 
923
- def select_raw(sql, name = nil)
924
- res = execute(sql, name)
925
- results = result_as_array(res)
926
- fields = res.fields
927
- res.clear
928
- return fields, results
929
- end
930
-
931
950
  # Returns the list of a table's column names, data types, and default values.
932
951
  #
933
952
  # The underlying query is roughly:
@@ -969,17 +988,13 @@ module ActiveRecord
969
988
  end
970
989
 
971
990
  def extract_table_ref_from_insert_sql(sql)
972
- sql[/into\s+([^\(]*).*values\s*\(/i]
991
+ sql[/into\s+([^\(]*).*values\s*\(/im]
973
992
  $1.strip if $1
974
993
  end
975
994
 
976
995
  def create_table_definition(name, temporary, options, as = nil)
977
996
  TableDefinition.new native_database_types, name, temporary, options, as
978
997
  end
979
-
980
- def update_table_definition(table_name, base)
981
- Table.new(table_name, base)
982
- end
983
998
  end
984
999
  end
985
1000
  end
@@ -31,6 +31,12 @@ module ActiveRecord
31
31
  db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
32
32
 
33
33
  ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
34
+ rescue Errno::ENOENT => error
35
+ if error.message.include?("No such file or directory")
36
+ raise ActiveRecord::NoDatabaseError.new(error.message)
37
+ else
38
+ raise error
39
+ end
34
40
  end
35
41
  end
36
42
 
@@ -149,6 +155,10 @@ module ActiveRecord
149
155
  true
150
156
  end
151
157
 
158
+ def supports_partial_index?
159
+ sqlite_version >= '3.8.0'
160
+ end
161
+
152
162
  # Returns true, since this connection adapter supports prepared statement
153
163
  # caching.
154
164
  def supports_statement_cache?
@@ -337,8 +347,8 @@ module ActiveRecord
337
347
  end
338
348
  alias :create :insert_sql
339
349
 
340
- def select_rows(sql, name = nil)
341
- exec_query(sql, name).rows
350
+ def select_rows(sql, name = nil, binds = [])
351
+ exec_query(sql, name, binds).rows
342
352
  end
343
353
 
344
354
  def begin_db_transaction #:nodoc:
@@ -391,13 +401,25 @@ module ActiveRecord
391
401
  # Returns an array of indexes for the given table.
392
402
  def indexes(table_name, name = nil) #:nodoc:
393
403
  exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
404
+ sql = <<-SQL
405
+ SELECT sql
406
+ FROM sqlite_master
407
+ WHERE name=#{quote(row['name'])} AND type='index'
408
+ UNION ALL
409
+ SELECT sql
410
+ FROM sqlite_temp_master
411
+ WHERE name=#{quote(row['name'])} AND type='index'
412
+ SQL
413
+ index_sql = exec_query(sql).first['sql']
414
+ match = /\sWHERE\s+(.+)$/i.match(index_sql)
415
+ where = match[1] if match
394
416
  IndexDefinition.new(
395
417
  table_name,
396
418
  row['name'],
397
419
  row['unique'] != 0,
398
420
  exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
399
421
  col['name']
400
- })
422
+ }, nil, nil, where)
401
423
  end
402
424
  end
403
425
 
@@ -1,5 +1,8 @@
1
1
  module ActiveRecord
2
2
  module ConnectionHandling
3
+ RAILS_ENV = -> { Rails.env if defined?(Rails) }
4
+ DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
5
+
3
6
  # Establishes the connection to the database. Accepts a hash as input where
4
7
  # the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
5
8
  # example for regular databases (MySQL, Postgresql, etc):
@@ -32,11 +35,19 @@ module ActiveRecord
32
35
  # "postgres://myuser:mypass@localhost/somedatabase"
33
36
  # )
34
37
  #
38
+ # In case <tt>ActiveRecord::Base.configurations</tt> is set (Rails
39
+ # automatically loads the contents of config/database.yml into it),
40
+ # a symbol can also be given as argument, representing a key in the
41
+ # configuration hash:
42
+ #
43
+ # ActiveRecord::Base.establish_connection(:production)
44
+ #
35
45
  # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
36
46
  # may be returned on an error.
37
- def establish_connection(spec = ENV["DATABASE_URL"])
38
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new spec, configurations
39
- spec = resolver.spec
47
+ def establish_connection(spec = nil)
48
+ spec ||= DEFAULT_ENV.call.to_sym
49
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
50
+ spec = resolver.spec(spec)
40
51
 
41
52
  unless respond_to?(spec.adapter_method)
42
53
  raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
@@ -46,6 +57,56 @@ module ActiveRecord
46
57
  connection_handler.establish_connection self, spec
47
58
  end
48
59
 
60
+ class MergeAndResolveDefaultUrlConfig # :nodoc:
61
+ def initialize(raw_configurations, url = ENV['DATABASE_URL'])
62
+ @raw_config = raw_configurations.dup
63
+ @url = url
64
+ end
65
+
66
+ # Returns fully resolved connection hashes.
67
+ # Merges connection information from `ENV['DATABASE_URL']` if available.
68
+ def resolve
69
+ ConnectionAdapters::ConnectionSpecification::Resolver.new(config).resolve_all
70
+ end
71
+
72
+ private
73
+ def config
74
+ if @url
75
+ raw_merged_into_default
76
+ else
77
+ @raw_config
78
+ end
79
+ end
80
+
81
+ def raw_merged_into_default
82
+ default = default_url_hash
83
+
84
+ @raw_config.each do |env, values|
85
+ default[env] = values || {}
86
+ default[env].merge!("url" => @url) { |h, v1, v2| v1 || v2 } if default[env].is_a?(Hash)
87
+ end
88
+ default
89
+ end
90
+
91
+ # When the raw configuration is not present and ENV['DATABASE_URL']
92
+ # is available we return a hash with the connection information in
93
+ # the connection URL. This hash responds to any string key with
94
+ # resolved connection information.
95
+ def default_url_hash
96
+ if @raw_config.blank?
97
+ Hash.new do |hash, key|
98
+ hash[key] = if key.is_a? String
99
+ ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(@url).to_hash
100
+ else
101
+ nil
102
+ end
103
+ end
104
+ else
105
+ {}
106
+ end
107
+ end
108
+ end
109
+
49
110
  # Returns the connection currently associated with the class. This can
50
111
  # also be used to "borrow" the connection to do database work unrelated
51
112
  # to any of the specific Active Records.
@@ -42,9 +42,16 @@ module ActiveRecord
42
42
  # 'database' => 'db/production.sqlite3'
43
43
  # }
44
44
  # }
45
- mattr_accessor :configurations, instance_writer: false
45
+ def self.configurations=(config)
46
+ @@configurations = ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
47
+ end
46
48
  self.configurations = {}
47
49
 
50
+ # Returns fully resolved configurations hash
51
+ def self.configurations
52
+ @@configurations
53
+ end
54
+
48
55
  ##
49
56
  # :singleton-method:
50
57
  # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
@@ -69,6 +76,18 @@ module ActiveRecord
69
76
  mattr_accessor :timestamped_migrations, instance_writer: false
70
77
  self.timestamped_migrations = true
71
78
 
79
+ ##
80
+ # :singleton-method:
81
+ # Specify whether schema dump should happen at the end of the
82
+ # db:migrate rake task. This is true by default, which is useful for the
83
+ # development environment. This should ideally be false in the production
84
+ # environment where dumping schema is rarely needed.
85
+ mattr_accessor :dump_schema_after_migration, instance_writer: false
86
+ self.dump_schema_after_migration = true
87
+
88
+ # :nodoc:
89
+ mattr_accessor :maintain_test_schema, instance_accessor: false
90
+
72
91
  def self.disable_implicit_join_references=(value)
73
92
  ActiveSupport::Deprecation.warn("Implicit join references were removed with Rails 4.1." \
74
93
  "Make sure to remove this configuration because it does nothing.")
@@ -128,12 +147,12 @@ module ActiveRecord
128
147
  # class Post < ActiveRecord::Base
129
148
  # scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) }
130
149
  # end
131
- def arel_table
150
+ def arel_table # :nodoc:
132
151
  @arel_table ||= Arel::Table.new(table_name, arel_engine)
133
152
  end
134
153
 
135
154
  # Returns the Arel engine.
136
- def arel_engine
155
+ def arel_engine # :nodoc:
137
156
  @arel_engine ||=
138
157
  if Base == self || connection_handler.retrieve_connection_pool(self)
139
158
  self
@@ -172,9 +191,7 @@ module ActiveRecord
172
191
  @column_types = self.class.column_types
173
192
 
174
193
  init_internals
175
- init_changed_attributes
176
- ensure_proper_type
177
- populate_with_current_scope_attributes
194
+ initialize_internals_callback
178
195
 
179
196
  # +options+ argument is only needed to make protected_attributes gem easier to hook.
180
197
  # Remove it when we drop support to this gem.
@@ -245,16 +262,12 @@ module ActiveRecord
245
262
 
246
263
  run_callbacks(:initialize) unless _initialize_callbacks.empty?
247
264
 
248
- @changed_attributes = {}
249
- init_changed_attributes
250
-
251
265
  @aggregation_cache = {}
252
266
  @association_cache = {}
253
267
  @attributes_cache = {}
254
268
 
255
269
  @new_record = true
256
270
 
257
- ensure_proper_type
258
271
  super
259
272
  end
260
273
 
@@ -271,7 +284,7 @@ module ActiveRecord
271
284
  # Post.new.encode_with(coder)
272
285
  # coder # => {"attributes" => {"id" => nil, ... }}
273
286
  def encode_with(coder)
274
- coder['attributes'] = attributes
287
+ coder['attributes'] = attributes_for_coder
275
288
  end
276
289
 
277
290
  # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
@@ -387,13 +400,10 @@ module ActiveRecord
387
400
  end
388
401
 
389
402
  def update_attributes_from_transaction_state(transaction_state, depth)
390
- if transaction_state && !has_transactional_callbacks?
403
+ if transaction_state && transaction_state.finalized? && !has_transactional_callbacks?
391
404
  unless @reflects_state[depth]
392
- if transaction_state.committed?
393
- committed!
394
- elsif transaction_state.rolledback?
395
- rolledback!
396
- end
405
+ restore_transaction_record_state if transaction_state.rolledback?
406
+ clear_transaction_record_state
397
407
  @reflects_state[depth] = true
398
408
  end
399
409
 
@@ -433,13 +443,7 @@ module ActiveRecord
433
443
  @reflects_state = [false]
434
444
  end
435
445
 
436
- def init_changed_attributes
437
- # Intentionally avoid using #column_defaults since overridden defaults (as is done in
438
- # optimistic locking) won't get written unless they get marked as changed
439
- self.class.columns.each do |c|
440
- attr, orig_value = c.name, c.default
441
- changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
442
- end
446
+ def initialize_internals_callback
443
447
  end
444
448
 
445
449
  # This method is needed to make protected_attributes gem easier to hook.