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
@@ -37,6 +37,8 @@ module ActiveRecord
37
37
  def not(opts, *rest)
38
38
  where_value = @scope.send(:build_where, opts, rest).map do |rel|
39
39
  case rel
40
+ when NilClass
41
+ raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
40
42
  when Arel::Nodes::In
41
43
  Arel::Nodes::NotIn.new(rel.left, rel.right)
42
44
  when Arel::Nodes::Equality
@@ -118,6 +120,9 @@ module ActiveRecord
118
120
  # Will throw an error, but this will work:
119
121
  #
120
122
  # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
123
+ #
124
+ # Note that +includes+ works with association names while +references+ needs
125
+ # the actual table name.
121
126
  def includes(*args)
122
127
  check_if_method_has_arguments!(:includes, args)
123
128
  spawn.includes!(*args)
@@ -161,24 +166,26 @@ module ActiveRecord
161
166
  self
162
167
  end
163
168
 
164
- # Used to indicate that an association is referenced by an SQL string, and should
165
- # therefore be JOINed in any query rather than loaded separately.
169
+ # Use to indicate that the given +table_names+ are referenced by an SQL string,
170
+ # and should therefore be JOINed in any query rather than loaded separately.
171
+ # This method only works in conjuction with +includes+.
172
+ # See #includes for more details.
166
173
  #
167
174
  # User.includes(:posts).where("posts.name = 'foo'")
168
175
  # # => Doesn't JOIN the posts table, resulting in an error.
169
176
  #
170
177
  # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
171
178
  # # => Query now knows the string references posts, so adds a JOIN
172
- def references(*args)
173
- check_if_method_has_arguments!(:references, args)
174
- spawn.references!(*args)
179
+ def references(*table_names)
180
+ check_if_method_has_arguments!(:references, table_names)
181
+ spawn.references!(*table_names)
175
182
  end
176
183
 
177
- def references!(*args) # :nodoc:
178
- args.flatten!
179
- args.map!(&:to_s)
184
+ def references!(*table_names) # :nodoc:
185
+ table_names.flatten!
186
+ table_names.map!(&:to_s)
180
187
 
181
- self.references_values |= args
188
+ self.references_values |= table_names
182
189
  self
183
190
  end
184
191
 
@@ -232,7 +239,9 @@ module ActiveRecord
232
239
 
233
240
  def select!(*fields) # :nodoc:
234
241
  fields.flatten!
235
-
242
+ fields.map! do |field|
243
+ klass.attribute_alias?(field) ? klass.attribute_alias(field) : field
244
+ end
236
245
  self.select_values += fields
237
246
  self
238
247
  end
@@ -631,12 +640,11 @@ module ActiveRecord
631
640
  self
632
641
  end
633
642
 
634
- # Returns a chainable relation with zero records, specifically an
635
- # instance of the <tt>ActiveRecord::NullRelation</tt> class.
643
+ # Returns a chainable relation with zero records.
636
644
  #
637
- # The returned <tt>ActiveRecord::NullRelation</tt> inherits from Relation and implements the
638
- # Null Object pattern. It is an object with defined null behavior and always returns an empty
639
- # array of records without querying the database.
645
+ # The returned relation implements the Null Object pattern. It is an
646
+ # object with defined null behavior and always returns an empty array of
647
+ # records without querying the database.
640
648
  #
641
649
  # Any subsequent condition chained to the returned relation will continue
642
650
  # generating an empty relation and will not fire any query to the database.
@@ -816,11 +824,12 @@ module ActiveRecord
816
824
  end
817
825
 
818
826
  # Returns the Arel object associated with the relation.
819
- def arel
827
+ def arel # :nodoc:
820
828
  @arel ||= build_arel
821
829
  end
822
830
 
823
- # Like #arel, but ignores the default scope of the model.
831
+ private
832
+
824
833
  def build_arel
825
834
  arel = Arel::SelectManager.new(table.engine, table)
826
835
 
@@ -846,19 +855,17 @@ module ActiveRecord
846
855
  arel
847
856
  end
848
857
 
849
- private
850
-
851
858
  def symbol_unscoping(scope)
852
859
  if !VALID_UNSCOPING_VALUES.include?(scope)
853
860
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
854
861
  end
855
862
 
856
863
  single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
857
- unscope_code = :"#{scope}_value#{'s' unless single_val_method}="
864
+ unscope_code = "#{scope}_value#{'s' unless single_val_method}="
858
865
 
859
866
  case scope
860
867
  when :order
861
- self.send(:reverse_order_value=, false)
868
+ self.reverse_order_value = false
862
869
  result = []
863
870
  else
864
871
  result = [] unless single_val_method
@@ -868,17 +875,17 @@ module ActiveRecord
868
875
  end
869
876
 
870
877
  def where_unscoping(target_value)
871
- target_value_sym = target_value.to_sym
878
+ target_value = target_value.to_s
872
879
 
873
880
  where_values.reject! do |rel|
874
881
  case rel
875
882
  when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual
876
883
  subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
877
- subrelation.name.to_sym == target_value_sym
878
- else
879
- raise "unscope(where: #{target_value.inspect}) failed: unscoping #{rel.class} is unimplemented."
884
+ subrelation.name == target_value
880
885
  end
881
886
  end
887
+
888
+ bind_values.reject! { |col,_| col.name == target_value }
882
889
  end
883
890
 
884
891
  def custom_join_ast(table, joins)
@@ -983,8 +990,11 @@ module ActiveRecord
983
990
  end
984
991
 
985
992
  def build_select(arel, selects)
986
- unless selects.empty?
987
- arel.project(*selects)
993
+ if !selects.empty?
994
+ expanded_select = selects.map do |field|
995
+ columns_hash.key?(field.to_s) ? arel_table[field] : field
996
+ end
997
+ arel.project(*expanded_select)
988
998
  else
989
999
  arel.project(@klass.arel_table[Arel.star])
990
1000
  end
@@ -1040,9 +1050,11 @@ module ActiveRecord
1040
1050
  order_args.map! do |arg|
1041
1051
  case arg
1042
1052
  when Symbol
1053
+ arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
1043
1054
  table[arg].asc
1044
1055
  when Hash
1045
1056
  arg.map { |field, dir|
1057
+ field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1046
1058
  table[field].send(dir)
1047
1059
  }
1048
1060
  else
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  if block_given?
55
55
  hash_rows.each { |row| yield row }
56
56
  else
57
- hash_rows.to_enum
57
+ hash_rows.to_enum { @rows.size }
58
58
  end
59
59
  end
60
60
 
@@ -29,6 +29,7 @@ module ActiveRecord
29
29
  end
30
30
  end
31
31
  alias_method :sanitize_sql, :sanitize_sql_for_conditions
32
+ alias_method :sanitize_conditions, :sanitize_sql
32
33
 
33
34
  # Accepts an array, hash, or string of SQL conditions and sanitizes
34
35
  # them into a valid SQL fragment for a SET clause.
@@ -100,8 +101,9 @@ module ActiveRecord
100
101
  # { status: nil, group_id: 1 }
101
102
  # # => "status = NULL , group_id = 1"
102
103
  def sanitize_sql_hash_for_assignment(attrs, table)
104
+ c = connection
103
105
  attrs.map do |attr, value|
104
- "#{connection.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value)}"
106
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
105
107
  end.join(', ')
106
108
  end
107
109
 
@@ -121,8 +123,6 @@ module ActiveRecord
121
123
  end
122
124
  end
123
125
 
124
- alias_method :sanitize_conditions, :sanitize_sql
125
-
126
126
  def replace_bind_variables(statement, values) #:nodoc:
127
127
  raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
128
128
  bound = values.dup
@@ -152,8 +152,10 @@ module ActiveRecord
152
152
  end
153
153
  end
154
154
 
155
- def quote_bound_value(value, c = connection) #:nodoc:
156
- if value.respond_to?(:map) && !value.acts_like?(:string)
155
+ def quote_bound_value(value, c = connection, column = nil) #:nodoc:
156
+ if column
157
+ c.quote(value, column)
158
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
157
159
  if value.respond_to?(:empty?) && value.empty?
158
160
  c.quote(nil)
159
161
  else
@@ -27,6 +27,11 @@ module ActiveRecord
27
27
  end
28
28
  end
29
29
 
30
+ def initialize_internals_callback
31
+ super
32
+ populate_with_current_scope_attributes
33
+ end
34
+
30
35
  # This class stores the +:current_scope+ and +:ignore_default_scope+ values
31
36
  # for different classes. The registry is stored as a thread local, which is
32
37
  # accessed through +ScopeRegistry.current+.
@@ -139,6 +139,12 @@ module ActiveRecord
139
139
  # Article.published.featured.latest_article
140
140
  # Article.featured.titles
141
141
  def scope(name, body, &block)
142
+ if dangerous_class_method?(name)
143
+ raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
144
+ "on the model \"#{self.name}\", but Active Record already defined " \
145
+ "a class method with the same name."
146
+ end
147
+
142
148
  extension = Module.new(&block) if block
143
149
 
144
150
  singleton_class.send(:define_method, name) do |*args|
@@ -178,7 +178,7 @@ module ActiveRecord
178
178
  end
179
179
 
180
180
  def load(yaml)
181
- self.class.as_indifferent_hash(@coder.load(yaml))
181
+ self.class.as_indifferent_hash(@coder.load(yaml || ''))
182
182
  end
183
183
 
184
184
  def self.as_indifferent_hash(obj)
@@ -36,9 +36,8 @@ module ActiveRecord
36
36
  module DatabaseTasks
37
37
  extend self
38
38
 
39
- attr_writer :current_config
40
- attr_accessor :database_configuration, :migrations_paths, :seed_loader, :db_dir,
41
- :fixtures_path, :env, :root
39
+ attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader
40
+ attr_accessor :database_configuration
42
41
 
43
42
  LOCAL_HOSTS = ['127.0.0.1', 'localhost']
44
43
 
@@ -51,16 +50,36 @@ module ActiveRecord
51
50
  register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
52
51
  register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
53
52
 
53
+ def db_dir
54
+ @db_dir ||= Rails.application.config.paths["db"].first
55
+ end
56
+
57
+ def migrations_paths
58
+ @migrations_paths ||= Rails.application.paths['db/migrate'].to_a
59
+ end
60
+
61
+ def fixtures_path
62
+ @fixtures_path ||= File.join(root, 'test', 'fixtures')
63
+ end
64
+
65
+ def root
66
+ @root ||= Rails.root
67
+ end
68
+
69
+ def env
70
+ @env ||= Rails.env
71
+ end
72
+
73
+ def seed_loader
74
+ @seed_loader ||= Rails.application
75
+ end
76
+
54
77
  def current_config(options = {})
55
78
  options.reverse_merge! :env => env
56
79
  if options.has_key?(:config)
57
80
  @current_config = options[:config]
58
81
  else
59
- @current_config ||= if ENV['DATABASE_URL']
60
- database_url_config
61
- else
62
- ActiveRecord::Base.configurations[options[:env]]
63
- end
82
+ @current_config ||= ActiveRecord::Base.configurations[options[:env]]
64
83
  end
65
84
  end
66
85
 
@@ -82,11 +101,7 @@ module ActiveRecord
82
101
  each_current_configuration(environment) { |configuration|
83
102
  create configuration
84
103
  }
85
- ActiveRecord::Base.establish_connection environment
86
- end
87
-
88
- def create_database_url
89
- create database_url_config
104
+ ActiveRecord::Base.establish_connection(environment.to_sym)
90
105
  end
91
106
 
92
107
  def drop(*arguments)
@@ -107,10 +122,6 @@ module ActiveRecord
107
122
  }
108
123
  end
109
124
 
110
- def drop_database_url
111
- drop database_url_config
112
- end
113
-
114
125
  def charset_current(environment = env)
115
126
  charset ActiveRecord::Base.configurations[environment]
116
127
  end
@@ -145,6 +156,21 @@ module ActiveRecord
145
156
  class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
146
157
  end
147
158
 
159
+ def load_schema(format = ActiveRecord::Base.schema_format, file = nil)
160
+ case format
161
+ when :ruby
162
+ file ||= File.join(db_dir, "schema.rb")
163
+ check_schema_file(file)
164
+ load(file)
165
+ when :sql
166
+ file ||= File.join(db_dir, "structure.sql")
167
+ check_schema_file(file)
168
+ structure_load(current_config, file)
169
+ else
170
+ raise ArgumentError, "unknown format #{format.inspect}"
171
+ end
172
+ end
173
+
148
174
  def check_schema_file(filename)
149
175
  unless File.exist?(filename)
150
176
  message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.}
@@ -165,11 +191,6 @@ module ActiveRecord
165
191
 
166
192
  private
167
193
 
168
- def database_url_config
169
- @database_url_config ||=
170
- ConnectionAdapters::ConnectionSpecification::Resolver.new(ENV["DATABASE_URL"], {}).spec.config.stringify_keys
171
- end
172
-
173
194
  def class_for_adapter(adapter)
174
195
  key = @tasks.keys.detect { |pattern| adapter[pattern] }
175
196
  unless key
@@ -180,7 +201,8 @@ module ActiveRecord
180
201
 
181
202
  def each_current_configuration(environment)
182
203
  environments = [environment]
183
- environments << 'test' if environment == 'development'
204
+ # add test environment only if no RAILS_ENV was specified.
205
+ environments << 'test' if environment == 'development' && ENV['RAILS_ENV'].nil?
184
206
 
185
207
  configurations = ActiveRecord::Base.configurations.values_at(*environments)
186
208
  configurations.compact.each do |configuration|
@@ -37,8 +37,8 @@ module ActiveRecord
37
37
  end
38
38
 
39
39
  def initialize_dup(other) # :nodoc:
40
- clear_timestamp_attributes
41
40
  super
41
+ clear_timestamp_attributes
42
42
  end
43
43
 
44
44
  private
@@ -71,7 +71,7 @@ module ActiveRecord
71
71
  end
72
72
 
73
73
  def should_record_timestamps?
74
- self.record_timestamps && (!partial_writes? || changed? || (attributes.keys & self.class.serialized_attributes.keys).present?)
74
+ self.record_timestamps && (!partial_writes? || changed?)
75
75
  end
76
76
 
77
77
  def timestamp_attributes_for_create_in_model
@@ -6,9 +6,6 @@ module ActiveRecord
6
6
  extend ActiveSupport::Concern
7
7
  ACTIONS = [:create, :destroy, :update]
8
8
 
9
- class TransactionError < ActiveRecordError # :nodoc:
10
- end
11
-
12
9
  included do
13
10
  define_callbacks :commit, :rollback,
14
11
  terminator: ->(_, result) { result == false },
@@ -243,15 +240,14 @@ module ActiveRecord
243
240
  def set_options_for_callbacks!(args)
244
241
  options = args.last
245
242
  if options.is_a?(Hash) && options[:on]
246
- assert_valid_transaction_action(options[:on])
247
- options[:if] = Array(options[:if])
248
243
  fire_on = Array(options[:on])
244
+ assert_valid_transaction_action(fire_on)
245
+ options[:if] = Array(options[:if])
249
246
  options[:if] << "transaction_include_any_action?(#{fire_on})"
250
247
  end
251
248
  end
252
249
 
253
250
  def assert_valid_transaction_action(actions)
254
- actions = Array(actions)
255
251
  if (actions - ACTIONS).any?
256
252
  raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}"
257
253
  end
@@ -277,6 +273,10 @@ module ActiveRecord
277
273
  with_transaction_returning_status { super }
278
274
  end
279
275
 
276
+ def touch(*) #:nodoc:
277
+ with_transaction_returning_status { super }
278
+ end
279
+
280
280
  # Reset id and @new_record if the transaction rolls back.
281
281
  def rollback_active_record_state!
282
282
  remember_transaction_record_state
@@ -295,7 +295,7 @@ module ActiveRecord
295
295
  def committed! #:nodoc:
296
296
  run_callbacks :commit if destroyed? || persisted?
297
297
  ensure
298
- clear_transaction_record_state
298
+ @_start_transaction_state.clear
299
299
  end
300
300
 
301
301
  # Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  super
6
6
  attributes.each do |attribute|
7
7
  next unless record.class.reflect_on_association(attribute)
8
- associated_records = Array(record.send(attribute))
8
+ associated_records = Array.wrap(record.send(attribute))
9
9
 
10
10
  # Superclass validates presence. Ensure present records aren't about to be destroyed.
11
11
  if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? }
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  # Returns the version of the currently loaded ActiveRecord as a Gem::Version
3
3
  def self.version
4
- Gem::Version.new "4.1.0.beta2"
4
+ Gem::Version.new "4.1.0.rc1"
5
5
  end
6
6
 
7
7
  module VERSION #:nodoc: