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
@@ -6,8 +6,12 @@ module ActiveRecord
6
6
  # then we can remove the indirection.
7
7
 
8
8
  def respond_to?(name, include_private = false)
9
- match = Method.match(self, name)
10
- match && match.valid? || super
9
+ if self == Base
10
+ super
11
+ else
12
+ match = Method.match(self, name)
13
+ match && match.valid? || super
14
+ end
11
15
  end
12
16
 
13
17
  private
@@ -1,5 +1,6 @@
1
1
  module ActiveRecord
2
- # Declare an enum attribute where the values map to integers in the database, but can be queried by name. Example:
2
+ # Declare an enum attribute where the values map to integers in the database,
3
+ # but can be queried by name. Example:
3
4
  #
4
5
  # class Conversation < ActiveRecord::Base
5
6
  # enum status: [ :active, :archived ]
@@ -18,6 +19,15 @@ module ActiveRecord
18
19
  # # conversation.update! status: 1
19
20
  # conversation.status = "archived"
20
21
  #
22
+ # # conversation.update! status: nil
23
+ # conversation.status = nil
24
+ # conversation.status.nil? # => true
25
+ # conversation.status # => nil
26
+ #
27
+ # Scopes based on the allowed values of the enum field will be provided
28
+ # as well. With the above example, it will create an +active+ and +archived+
29
+ # scope.
30
+ #
21
31
  # You can set the default value from the database declaration, like:
22
32
  #
23
33
  # create_table :conversations do |t|
@@ -44,46 +54,76 @@ module ActiveRecord
44
54
  # remove unused values, the explicit +Hash+ syntax should be used.
45
55
  #
46
56
  # In rare circumstances you might need to access the mapping directly.
47
- # The mappings are exposed through a constant with the attributes name:
57
+ # The mappings are exposed through a class method with the pluralized attribute
58
+ # name:
59
+ #
60
+ # Conversation.statuses # => { "active" => 0, "archived" => 1 }
48
61
  #
49
- # Conversation::STATUS # => { "active" => 0, "archived" => 1 }
62
+ # Use that class method when you need to know the ordinal value of an enum:
50
63
  #
51
- # Use that constant when you need to know the ordinal value of an enum:
64
+ # Conversation.where("status <> ?", Conversation.statuses[:archived])
52
65
  #
53
- # Conversation.where("status <> ?", Conversation::STATUS[:archived])
66
+ # Where conditions on an enum attribute must use the ordinal value of an enum.
54
67
  module Enum
68
+ DEFINED_ENUMS = {} # :nodoc:
69
+
70
+ def enum_mapping_for(attr_name) # :nodoc:
71
+ DEFINED_ENUMS[attr_name.to_s]
72
+ end
73
+
55
74
  def enum(definitions)
56
75
  klass = self
57
76
  definitions.each do |name, values|
58
- # DIRECTION = { }
59
- enum_values = _enum_methods_module.const_set name.to_s.upcase, ActiveSupport::HashWithIndifferentAccess.new
77
+ # statuses = { }
78
+ enum_values = ActiveSupport::HashWithIndifferentAccess.new
60
79
  name = name.to_sym
61
80
 
81
+ # def self.statuses statuses end
82
+ detect_enum_conflict!(name, name.to_s.pluralize, true)
83
+ klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values }
84
+
62
85
  _enum_methods_module.module_eval do
63
- # def direction=(value) self[:direction] = DIRECTION[value] end
86
+ # def status=(value) self[:status] = statuses[value] end
87
+ klass.send(:detect_enum_conflict!, name, "#{name}=")
64
88
  define_method("#{name}=") { |value|
65
- unless enum_values.has_key?(value)
89
+ if enum_values.has_key?(value) || value.blank?
90
+ self[name] = enum_values[value]
91
+ elsif enum_values.has_value?(value)
92
+ # Assigning a value directly is not a end-user feature, hence it's not documented.
93
+ # This is used internally to make building objects from the generated scopes work
94
+ # as expected, i.e. +Conversation.archived.build.archived?+ should be true.
95
+ self[name] = value
96
+ else
66
97
  raise ArgumentError, "'#{value}' is not a valid #{name}"
67
98
  end
68
- self[name] = enum_values[value]
69
99
  }
70
100
 
71
- # def direction() DIRECTION.key self[:direction] end
101
+ # def status() statuses.key self[:status] end
102
+ klass.send(:detect_enum_conflict!, name, name)
72
103
  define_method(name) { enum_values.key self[name] }
73
104
 
105
+ # def status_before_type_cast() statuses.key self[:status] end
106
+ klass.send(:detect_enum_conflict!, name, "#{name}_before_type_cast")
107
+ define_method("#{name}_before_type_cast") { enum_values.key self[name] }
108
+
74
109
  pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
75
110
  pairs.each do |value, i|
76
111
  enum_values[value] = i
77
112
 
78
- # scope :incoming, -> { where direction: 0 }
79
- klass.scope value, -> { klass.where name => i }
80
-
81
- # def incoming?() direction == 0 end
113
+ # def active?() status == 0 end
114
+ klass.send(:detect_enum_conflict!, name, "#{value}?")
82
115
  define_method("#{value}?") { self[name] == i }
83
116
 
84
- # def incoming! update! direction: :incoming end
117
+ # def active!() update! status: :active end
118
+ klass.send(:detect_enum_conflict!, name, "#{value}!")
85
119
  define_method("#{value}!") { update! name => value }
120
+
121
+ # scope :active, -> { where status: 0 }
122
+ klass.send(:detect_enum_conflict!, name, value, true)
123
+ klass.scope value, -> { klass.where name => i }
86
124
  end
125
+
126
+ DEFINED_ENUMS[name.to_s] = enum_values
87
127
  end
88
128
  end
89
129
  end
@@ -91,10 +131,64 @@ module ActiveRecord
91
131
  private
92
132
  def _enum_methods_module
93
133
  @_enum_methods_module ||= begin
94
- mod = Module.new
134
+ mod = Module.new do
135
+ private
136
+ def save_changed_attribute(attr_name, value)
137
+ if (mapping = self.class.enum_mapping_for(attr_name))
138
+ if attribute_changed?(attr_name)
139
+ old = changed_attributes[attr_name]
140
+
141
+ if mapping[old] == value
142
+ changed_attributes.delete(attr_name)
143
+ end
144
+ else
145
+ old = clone_attribute_value(:read_attribute, attr_name)
146
+
147
+ if old != value
148
+ changed_attributes[attr_name] = mapping.key old
149
+ end
150
+ end
151
+ else
152
+ super
153
+ end
154
+ end
155
+ end
95
156
  include mod
96
157
  mod
97
158
  end
98
159
  end
160
+
161
+ ENUM_CONFLICT_MESSAGE = \
162
+ "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
163
+ "this will generate a %{type} method \"%{method}\", which is already defined " \
164
+ "by %{source}."
165
+
166
+ def detect_enum_conflict!(enum_name, method_name, klass_method = false)
167
+ if klass_method && dangerous_class_method?(method_name)
168
+ raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
169
+ enum: enum_name,
170
+ klass: self.name,
171
+ type: 'class',
172
+ method: method_name,
173
+ source: 'Active Record'
174
+ }
175
+ elsif !klass_method && dangerous_attribute_method?(method_name)
176
+ raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
177
+ enum: enum_name,
178
+ klass: self.name,
179
+ type: 'instance',
180
+ method: method_name,
181
+ source: 'Active Record'
182
+ }
183
+ elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
184
+ raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
185
+ enum: enum_name,
186
+ klass: self.name,
187
+ type: 'instance',
188
+ method: method_name,
189
+ source: 'another enum'
190
+ }
191
+ end
192
+ end
99
193
  end
100
194
  end
@@ -94,6 +94,18 @@ module ActiveRecord
94
94
  class PreparedStatementInvalid < ActiveRecordError
95
95
  end
96
96
 
97
+ # Raised when a given database does not exist
98
+ class NoDatabaseError < ActiveRecordError
99
+ def initialize(message)
100
+ super extend_message(message)
101
+ end
102
+
103
+ # can be over written to add additional error information.
104
+ def extend_message(message)
105
+ message
106
+ end
107
+ end
108
+
97
109
  # Raised on attempt to save stale record. Record is stale when it's being saved in another query after
98
110
  # instantiation, for example, when two users edit the same wiki page and one starts editing and saves
99
111
  # the page before the other.
@@ -462,7 +462,7 @@ module ActiveRecord
462
462
  @class_names.delete_if { |k,klass|
463
463
  unless klass.is_a? Class
464
464
  klass = klass.safe_constantize
465
- ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name will be removed in Rails 4.2, consider using the class itself instead.")
465
+ ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name to `set_fixture_class` will be removed in Rails 4.2. Use the class itself instead.")
466
466
  end
467
467
  !insert_class(@class_names, k, klass)
468
468
  }
@@ -521,8 +521,16 @@ module ActiveRecord
521
521
  connection.transaction(:requires_new => true) do
522
522
  fixture_sets.each do |fs|
523
523
  conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
524
- fs.fixture_sql(conn).each do |stmt|
525
- conn.execute stmt
524
+ table_rows = fs.table_rows
525
+
526
+ table_rows.keys.each do |table|
527
+ conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
528
+ end
529
+
530
+ table_rows.each do |fixture_set_name, rows|
531
+ rows.each do |row|
532
+ conn.insert_fixture(row, fixture_set_name)
533
+ end
526
534
  end
527
535
  end
528
536
 
@@ -560,7 +568,7 @@ module ActiveRecord
560
568
  @model_class = nil
561
569
 
562
570
  if class_name.is_a?(String)
563
- ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name will be removed in Rails 4.2, consider using the class itself instead.")
571
+ ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name to `FixtureSet.new` will be removed in Rails 4.2. Use the class itself instead.")
564
572
  end
565
573
 
566
574
  if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
@@ -594,17 +602,7 @@ module ActiveRecord
594
602
  fixtures.size
595
603
  end
596
604
 
597
- def fixture_sql(conn)
598
- table_rows = self.table_rows
599
-
600
- table_rows.keys.map { |table|
601
- "DELETE FROM #{conn.quote_table_name(table)}"
602
- }.concat table_rows.flat_map { |fixture_set_name, rows|
603
- rows.map { |row| conn.fixture_sql(row, fixture_set_name) }
604
- }
605
- end
606
-
607
- # Return a hash of rows to be inserted. The key is the table, the value is
605
+ # Returns a hash of rows to be inserted. The key is the table, the value is
608
606
  # a list of rows to insert to that table.
609
607
  def table_rows
610
608
  now = config.default_timezone == :utc ? Time.now.utc : Time.now
@@ -16,15 +16,19 @@ module ActiveRecord
16
16
  # instance of the given subclass instead of the base class.
17
17
  def new(*args, &block)
18
18
  if abstract_class? || self == Base
19
- raise NotImplementedError, "#{self} is an abstract class and can not be instantiated."
19
+ raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
20
20
  end
21
- if (attrs = args.first).is_a?(Hash)
22
- if subclass = subclass_from_attrs(attrs)
23
- return subclass.new(*args, &block)
24
- end
21
+
22
+ attrs = args.first
23
+ if subclass_from_attributes?(attrs)
24
+ subclass = subclass_from_attributes(attrs)
25
+ end
26
+
27
+ if subclass
28
+ subclass.new(*args, &block)
29
+ else
30
+ super
25
31
  end
26
- # Delegate to the original .new
27
- super
28
32
  end
29
33
 
30
34
  # Returns +true+ if this does not need STI type condition. Returns
@@ -45,10 +49,12 @@ module ActiveRecord
45
49
  end
46
50
 
47
51
  def symbolized_base_class
52
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.symbolized_base_class is deprecated and will be removed without replacement.")
48
53
  @symbolized_base_class ||= base_class.to_s.to_sym
49
54
  end
50
55
 
51
56
  def symbolized_sti_name
57
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.symbolized_sti_name is deprecated and will be removed without replacement.")
52
58
  @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
53
59
  end
54
60
 
@@ -124,7 +130,7 @@ module ActiveRecord
124
130
  end
125
131
  end
126
132
 
127
- raise NameError, "uninitialized constant #{candidates.first}"
133
+ raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
128
134
  end
129
135
  end
130
136
 
@@ -170,7 +176,11 @@ module ActiveRecord
170
176
  # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
171
177
  # If this is a StrongParameters hash, and access to inheritance_column is not permitted,
172
178
  # this will ignore the inheritance column and return nil
173
- def subclass_from_attrs(attrs)
179
+ def subclass_from_attributes?(attrs)
180
+ columns_hash.include?(inheritance_column) && attrs.is_a?(Hash)
181
+ end
182
+
183
+ def subclass_from_attributes(attrs)
174
184
  subclass_name = attrs.with_indifferent_access[inheritance_column]
175
185
 
176
186
  if subclass_name.present? && subclass_name != self.name
@@ -185,8 +195,18 @@ module ActiveRecord
185
195
  end
186
196
  end
187
197
 
198
+ def initialize_dup(other)
199
+ super
200
+ ensure_proper_type
201
+ end
202
+
188
203
  private
189
204
 
205
+ def initialize_internals_callback
206
+ super
207
+ ensure_proper_type
208
+ end
209
+
190
210
  # Sets the attribute used for single table inheritance to this class name if this is not the
191
211
  # ActiveRecord::Base descendant.
192
212
  # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
@@ -98,8 +98,10 @@ module ActiveRecord
98
98
  super()
99
99
  else
100
100
  define_method :to_param do
101
- if (default = super()) && (result = send(method_name).to_s).present?
102
- "#{default}-#{result.squish.truncate(20, separator: /\s/, omission: nil).parameterize}"
101
+ if (default = super()) &&
102
+ (result = send(method_name).to_s).present? &&
103
+ (param = result.squish.truncate(20, separator: /\s/, omission: nil).parameterize).present?
104
+ "#{default}-#{param}"
103
105
  else
104
106
  default
105
107
  end
@@ -385,8 +385,21 @@ module ActiveRecord
385
385
  attr_accessor :delegate # :nodoc:
386
386
  attr_accessor :disable_ddl_transaction # :nodoc:
387
387
 
388
- def check_pending!
389
- raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?
388
+ def check_pending!(connection = Base.connection)
389
+ raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection)
390
+ end
391
+
392
+ def load_schema_if_pending!
393
+ if ActiveRecord::Migrator.needs_migration?
394
+ ActiveRecord::Tasks::DatabaseTasks.load_schema
395
+ check_pending!
396
+ end
397
+ end
398
+
399
+ def maintain_test_schema! # :nodoc:
400
+ if ActiveRecord::Base.maintain_test_schema
401
+ suppress_messages { load_schema_if_pending! }
402
+ end
390
403
  end
391
404
 
392
405
  def method_missing(name, *args, &block) # :nodoc:
@@ -746,7 +759,7 @@ module ActiveRecord
746
759
 
747
760
  def load_migration
748
761
  require(File.expand_path(filename))
749
- name.constantize.new
762
+ name.constantize.new(name, version)
750
763
  end
751
764
 
752
765
  end
@@ -817,17 +830,17 @@ module ActiveRecord
817
830
  SchemaMigration.all.map { |x| x.version.to_i }.sort
818
831
  end
819
832
 
820
- def current_version
833
+ def current_version(connection = Base.connection)
821
834
  sm_table = schema_migrations_table_name
822
- if Base.connection.table_exists?(sm_table)
835
+ if connection.table_exists?(sm_table)
823
836
  get_all_versions.max || 0
824
837
  else
825
838
  0
826
839
  end
827
840
  end
828
841
 
829
- def needs_migration?
830
- current_version < last_version
842
+ def needs_migration?(connection = Base.connection)
843
+ current_version(connection) < last_version
831
844
  end
832
845
 
833
846
  def last_version
@@ -74,7 +74,7 @@ module ActiveRecord
74
74
  :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps,
75
75
  :change_column_default, :add_reference, :remove_reference, :transaction,
76
76
  :drop_join_table, :drop_table, :execute_block, :enable_extension,
77
- :change_column, :execute, :remove_columns, # irreversible methods need to be here too
77
+ :change_column, :execute, :remove_columns, :change_column_null # irreversible methods need to be here too
78
78
  ].each do |method|
79
79
  class_eval <<-EOV, __FILE__, __LINE__ + 1
80
80
  def #{method}(*args, &block) # def create_table(*args, &block)
@@ -86,7 +86,7 @@ module ActiveRecord
86
86
  alias :remove_belongs_to :remove_reference
87
87
 
88
88
  def change_table(table_name, options = {})
89
- yield ConnectionAdapters::Table.new(table_name, self)
89
+ yield delegate.update_table_definition(table_name, self)
90
90
  end
91
91
 
92
92
  private
@@ -140,7 +140,12 @@ module ActiveRecord
140
140
 
141
141
  def invert_add_index(args)
142
142
  table, columns, options = *args
143
- [:remove_index, [table, (options || {}).merge(column: columns)]]
143
+ options ||= {}
144
+
145
+ index_name = options[:name]
146
+ options_hash = index_name ? { name: index_name } : { column: columns }
147
+
148
+ [:remove_index, [table, options_hash]]
144
149
  end
145
150
 
146
151
  def invert_remove_index(args)
@@ -157,11 +162,18 @@ module ActiveRecord
157
162
  alias :invert_add_belongs_to :invert_add_reference
158
163
  alias :invert_remove_belongs_to :invert_remove_reference
159
164
 
165
+ def invert_change_column_null(args)
166
+ args[2] = !args[2]
167
+ [:change_column_null, args]
168
+ end
169
+
160
170
  # Forwards any missing method call to the \target.
161
171
  def method_missing(method, *args, &block)
162
- @delegate.send(method, *args, &block)
163
- rescue NoMethodError => e
164
- raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@delegate}")
172
+ if @delegate.respond_to?(method)
173
+ @delegate.send(method, *args, &block)
174
+ else
175
+ super
176
+ end
165
177
  end
166
178
  end
167
179
  end