activerecord 4.2.0.beta4 → 4.2.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +107 -34
  3. data/lib/active_record/aggregations.rb +2 -2
  4. data/lib/active_record/associations.rb +1 -1
  5. data/lib/active_record/associations/alias_tracker.rb +3 -12
  6. data/lib/active_record/associations/association_scope.rb +1 -2
  7. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  8. data/lib/active_record/associations/collection_association.rb +29 -6
  9. data/lib/active_record/associations/has_many_association.rb +1 -1
  10. data/lib/active_record/associations/has_many_through_association.rb +2 -2
  11. data/lib/active_record/associations/join_dependency.rb +1 -1
  12. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  13. data/lib/active_record/associations/preloader.rb +1 -0
  14. data/lib/active_record/associations/preloader/association.rb +3 -8
  15. data/lib/active_record/associations/preloader/through_association.rb +1 -0
  16. data/lib/active_record/associations/singular_association.rb +2 -1
  17. data/lib/active_record/attribute_methods.rb +2 -2
  18. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  19. data/lib/active_record/attribute_methods/primary_key.rb +2 -1
  20. data/lib/active_record/attribute_methods/read.rb +13 -5
  21. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  22. data/lib/active_record/attribute_set.rb +7 -11
  23. data/lib/active_record/attribute_set/builder.rb +66 -17
  24. data/lib/active_record/attributes.rb +20 -3
  25. data/lib/active_record/connection_adapters/abstract/database_statements.rb +0 -4
  26. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +1 -3
  27. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +25 -24
  28. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -3
  29. data/lib/active_record/connection_adapters/abstract_adapter.rb +9 -7
  30. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +25 -13
  31. data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
  32. data/lib/active_record/connection_adapters/mysql2_adapter.rb +6 -0
  33. data/lib/active_record/connection_adapters/mysql_adapter.rb +6 -2
  34. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +0 -4
  35. data/lib/active_record/connection_adapters/postgresql/quoting.rb +6 -16
  36. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +29 -7
  37. data/lib/active_record/connection_adapters/postgresql/utils.rb +15 -4
  38. data/lib/active_record/connection_adapters/postgresql_adapter.rb +15 -6
  39. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +5 -7
  40. data/lib/active_record/connection_handling.rb +1 -1
  41. data/lib/active_record/core.rb +5 -3
  42. data/lib/active_record/enum.rb +1 -1
  43. data/lib/active_record/errors.rb +21 -0
  44. data/lib/active_record/fixtures.rb +4 -2
  45. data/lib/active_record/gem_version.rb +1 -1
  46. data/lib/active_record/locking/optimistic.rb +1 -1
  47. data/lib/active_record/migration.rb +15 -4
  48. data/lib/active_record/model_schema.rb +8 -4
  49. data/lib/active_record/persistence.rb +5 -5
  50. data/lib/active_record/railtie.rb +0 -2
  51. data/lib/active_record/railties/databases.rake +7 -6
  52. data/lib/active_record/reflection.rb +2 -2
  53. data/lib/active_record/relation.rb +21 -13
  54. data/lib/active_record/relation/calculations.rb +1 -0
  55. data/lib/active_record/relation/finder_methods.rb +8 -5
  56. data/lib/active_record/relation/merger.rb +0 -12
  57. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  58. data/lib/active_record/relation/query_methods.rb +30 -18
  59. data/lib/active_record/sanitization.rb +4 -1
  60. data/lib/active_record/schema_dumper.rb +1 -6
  61. data/lib/active_record/scoping/named.rb +1 -1
  62. data/lib/active_record/statement_cache.rb +21 -10
  63. data/lib/active_record/tasks/database_tasks.rb +17 -2
  64. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -0
  65. data/lib/active_record/type.rb +1 -0
  66. data/lib/active_record/type/big_integer.rb +13 -0
  67. data/lib/active_record/type/decimal_without_scale.rb +2 -2
  68. data/lib/active_record/type/hash_lookup_type_map.rb +5 -7
  69. data/lib/active_record/type/integer.rb +29 -1
  70. data/lib/active_record/type/serialized.rb +1 -1
  71. data/lib/active_record/type/string.rb +4 -4
  72. data/lib/active_record/type/type_map.rb +23 -7
  73. data/lib/active_record/validations.rb +4 -3
  74. data/lib/active_record/validations/uniqueness.rb +1 -1
  75. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +3 -0
  76. data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
  77. metadata +15 -20
@@ -254,6 +254,7 @@ module ActiveRecord
254
254
  select_value = operation_over_aggregate_column(column, operation, distinct)
255
255
 
256
256
  column_alias = select_value.alias
257
+ column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
257
258
  relation.select_values = [select_value]
258
259
 
259
260
  query_builder = relation.arel
@@ -82,12 +82,16 @@ module ActiveRecord
82
82
  # Post.find_by "published_at < ?", 2.weeks.ago
83
83
  def find_by(*args)
84
84
  where(*args).take
85
+ rescue RangeError
86
+ nil
85
87
  end
86
88
 
87
89
  # Like <tt>find_by</tt>, except that if no record is found, raises
88
90
  # an <tt>ActiveRecord::RecordNotFound</tt> error.
89
91
  def find_by!(*args)
90
92
  where(*args).take!
93
+ rescue RangeError
94
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range value"
91
95
  end
92
96
 
93
97
  # Gives a record (or N records if a parameter is supplied) without any implied
@@ -433,6 +437,8 @@ module ActiveRecord
433
437
  else
434
438
  find_some(ids)
435
439
  end
440
+ rescue RangeError
441
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
436
442
  end
437
443
 
438
444
  def find_one(id)
@@ -444,10 +450,7 @@ module ActiveRecord
444
450
  MSG
445
451
  end
446
452
 
447
- column = columns_hash[primary_key]
448
- substitute = connection.substitute_at(column, bind_values.length)
449
- relation = where(table[primary_key].eq(substitute))
450
- relation.bind_values += [[column, id]]
453
+ relation = where(primary_key => id)
451
454
  record = relation.take
452
455
 
453
456
  raise_record_not_found_exception!(id, 0, 1) unless record
@@ -456,7 +459,7 @@ module ActiveRecord
456
459
  end
457
460
 
458
461
  def find_some(ids)
459
- result = where(table[primary_key].in(ids)).to_a
462
+ result = where(primary_key => ids).to_a
460
463
 
461
464
  expected_size =
462
465
  if limit_value && ids.size > limit_value
@@ -118,18 +118,6 @@ module ActiveRecord
118
118
  where_values = kept + rhs_wheres
119
119
  bind_values = filter_binds(lhs_binds, removed) + rhs_binds
120
120
 
121
- conn = relation.klass.connection
122
- bv_index = 0
123
- where_values.map! do |node|
124
- if Arel::Nodes::Equality === node && Arel::Nodes::BindParam === node.right
125
- substitute = conn.substitute_at(bind_values[bv_index].first, bv_index)
126
- bv_index += 1
127
- Arel::Nodes::Equality.new(node.left, substitute)
128
- else
129
- node
130
- end
131
- end
132
-
133
121
  relation.where_values = where_values
134
122
  relation.bind_values = bind_values
135
123
 
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  value = value.select(value.klass.arel_table[value.klass.primary_key])
7
7
  end
8
8
 
9
- attribute.in(value.arel.ast)
9
+ attribute.in(value.arel)
10
10
  end
11
11
  end
12
12
  end
@@ -427,19 +427,17 @@ module ActiveRecord
427
427
  # => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
428
428
  def joins(*args)
429
429
  check_if_method_has_arguments!(:joins, args)
430
-
431
- args.compact!
432
- args.flatten!
433
-
434
430
  spawn.joins!(*args)
435
431
  end
436
432
 
437
433
  def joins!(*args) # :nodoc:
434
+ args.compact!
435
+ args.flatten!
438
436
  self.joins_values += args
439
437
  self
440
438
  end
441
439
 
442
- def bind(value)
440
+ def bind(value) # :nodoc:
443
441
  spawn.bind!(value)
444
442
  end
445
443
 
@@ -881,13 +879,6 @@ module ActiveRecord
881
879
  arel.from(build_from) if from_value
882
880
  arel.lock(lock_value) if lock_value
883
881
 
884
- # Reorder bind indexes if joins produced bind values
885
- bvs = arel.bind_values + bind_values
886
- arel.ast.grep(Arel::Nodes::BindParam).each_with_index do |bp, i|
887
- column = bvs[i].first
888
- bp.replace connection.substitute_at(column, i)
889
- end
890
-
891
882
  arel
892
883
  end
893
884
 
@@ -958,8 +949,7 @@ module ActiveRecord
958
949
  when Hash
959
950
  opts = PredicateBuilder.resolve_column_aliases(klass, opts)
960
951
 
961
- bv_len = bind_values.length
962
- tmp_opts, bind_values = create_binds(opts, bv_len)
952
+ tmp_opts, bind_values = create_binds(opts)
963
953
  self.bind_values += bind_values
964
954
 
965
955
  attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
@@ -971,7 +961,7 @@ module ActiveRecord
971
961
  end
972
962
  end
973
963
 
974
- def create_binds(opts, idx)
964
+ def create_binds(opts)
975
965
  bindable, non_binds = opts.partition do |column, value|
976
966
  case value
977
967
  when String, Integer, ActiveRecord::StatementCache::Substitute
@@ -981,12 +971,23 @@ module ActiveRecord
981
971
  end
982
972
  end
983
973
 
974
+ association_binds, non_binds = non_binds.partition do |column, value|
975
+ value.is_a?(Hash) && association_for_table(column)
976
+ end
977
+
984
978
  new_opts = {}
985
979
  binds = []
986
980
 
987
- bindable.each_with_index do |(column,value), index|
981
+ bindable.each do |(column,value)|
988
982
  binds.push [@klass.columns_hash[column.to_s], value]
989
- new_opts[column] = connection.substitute_at(column, index + idx)
983
+ new_opts[column] = connection.substitute_at(column)
984
+ end
985
+
986
+ association_binds.each do |(column, value)|
987
+ association_relation = association_for_table(column).klass.send(:relation)
988
+ association_new_opts, association_bind = association_relation.send(:create_binds, value)
989
+ new_opts[column] = association_new_opts
990
+ binds += association_bind
990
991
  end
991
992
 
992
993
  non_binds.each { |column,value| new_opts[column] = value }
@@ -994,6 +995,12 @@ module ActiveRecord
994
995
  [new_opts, binds]
995
996
  end
996
997
 
998
+ def association_for_table(table_name)
999
+ table_name = table_name.to_s
1000
+ @klass._reflect_on_association(table_name) ||
1001
+ @klass._reflect_on_association(table_name.singularize)
1002
+ end
1003
+
997
1004
  def build_from
998
1005
  opts, name = from_value
999
1006
  case opts
@@ -1050,8 +1057,13 @@ module ActiveRecord
1050
1057
  def build_select(arel, selects)
1051
1058
  if !selects.empty?
1052
1059
  expanded_select = selects.map do |field|
1053
- columns_hash.key?(field.to_s) ? arel_table[field] : field
1060
+ if (Symbol === field || String === field) && columns_hash.key?(field.to_s)
1061
+ arel_table[field]
1062
+ else
1063
+ field
1064
+ end
1054
1065
  end
1066
+
1055
1067
  arel.project(*expanded_select)
1056
1068
  else
1057
1069
  arel.project(@klass.arel_table[Arel.star])
@@ -87,6 +87,9 @@ module ActiveRecord
87
87
  # { address: Address.new("123 abc st.", "chicago") }
88
88
  # # => "address_street='123 abc st.' and address_city='chicago'"
89
89
  def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
90
+ ActiveSupport::Deprecation.warn(<<-EOWARN)
91
+ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
92
+ EOWARN
90
93
  attrs = PredicateBuilder.resolve_column_aliases self, attrs
91
94
  attrs = expand_hash_conditions_for_aggregates(attrs)
92
95
 
@@ -134,7 +137,7 @@ module ActiveRecord
134
137
  raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
135
138
  bound = values.dup
136
139
  c = connection
137
- statement.gsub('?') do
140
+ statement.gsub(/\?/) do
138
141
  replace_bind_variable(bound.shift, c)
139
142
  end
140
143
  end
@@ -244,12 +244,7 @@ HEADER
244
244
 
245
245
  def ignored?(table_name)
246
246
  ['schema_migrations', ignore_tables].flatten.any? do |ignored|
247
- case ignored
248
- when String; remove_prefix_and_suffix(table_name) == ignored
249
- when Regexp; remove_prefix_and_suffix(table_name) =~ ignored
250
- else
251
- raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
252
- end
247
+ ignored === remove_prefix_and_suffix(table_name)
253
248
  end
254
249
  end
255
250
  end
@@ -139,7 +139,7 @@ module ActiveRecord
139
139
  # Article.published.featured.latest_article
140
140
  # Article.featured.titles
141
141
  def scope(name, body, &block)
142
- unless body.respond_to?:call
142
+ unless body.respond_to?(:call)
143
143
  raise ArgumentError, 'The scope body needs to be callable.'
144
144
  end
145
145
 
@@ -1,22 +1,33 @@
1
1
  module ActiveRecord
2
2
 
3
3
  # Statement cache is used to cache a single statement in order to avoid creating the AST again.
4
- # Initializing the cache is done by passing the statement in the initialization block:
4
+ # Initializing the cache is done by passing the statement in the create block:
5
5
  #
6
- # cache = ActiveRecord::StatementCache.new do
7
- # Book.where(name: "my book").limit(100)
6
+ # cache = StatementCache.create(Book.connection) do |params|
7
+ # Book.where(name: "my book").where("author_id > 3")
8
8
  # end
9
9
  #
10
10
  # The cached statement is executed by using the +execute+ method:
11
11
  #
12
- # cache.execute
12
+ # cache.execute([], Book, Book.connection)
13
13
  #
14
14
  # The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped.
15
15
  # Database is queried when +to_a+ is called on the relation.
16
- class StatementCache
17
- class Substitute; end
16
+ #
17
+ # If you want to cache the statement without the values you can use the +bind+ method of the
18
+ # block parameter.
19
+ #
20
+ # cache = StatementCache.create(Book.connection) do |params|
21
+ # Book.where(name: params.bind)
22
+ # end
23
+ #
24
+ # And pass the bind values as the first argument of +execute+ call.
25
+ #
26
+ # cache.execute(["my book"], Book, Book.connection)
27
+ class StatementCache # :nodoc:
28
+ class Substitute; end # :nodoc:
18
29
 
19
- class Query
30
+ class Query # :nodoc:
20
31
  def initialize(sql)
21
32
  @sql = sql
22
33
  end
@@ -26,7 +37,7 @@ module ActiveRecord
26
37
  end
27
38
  end
28
39
 
29
- class PartialQuery < Query
40
+ class PartialQuery < Query # :nodoc:
30
41
  def initialize values
31
42
  @values = values
32
43
  @indexes = values.each_with_index.find_all { |thing,i|
@@ -51,11 +62,11 @@ module ActiveRecord
51
62
  PartialQuery.new collected
52
63
  end
53
64
 
54
- class Params
65
+ class Params # :nodoc:
55
66
  def bind; Substitute.new; end
56
67
  end
57
68
 
58
- class BindMap
69
+ class BindMap # :nodoc:
59
70
  def initialize(bind_values)
60
71
  @indexes = []
61
72
  @bind_values = bind_values
@@ -197,18 +197,27 @@ module ActiveRecord
197
197
  load_schema_current(format, file)
198
198
  end
199
199
 
200
+ def schema_file(format = ActiveSupport::Base.schema_format)
201
+ case format
202
+ when :ruby
203
+ File.join(db_dir, "schema.rb")
204
+ when :sql
205
+ File.join(db_dir, "structure.sql")
206
+ end
207
+ end
208
+
200
209
  # This method is the successor of +load_schema+. We should rename it
201
210
  # after +load_schema+ went through a deprecation cycle. (Rails > 4.2)
202
211
  def load_schema_for(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
212
+ file ||= schema_file(format)
213
+
203
214
  case format
204
215
  when :ruby
205
- file ||= File.join(db_dir, "schema.rb")
206
216
  check_schema_file(file)
207
217
  purge(configuration)
208
218
  ActiveRecord::Base.establish_connection(configuration)
209
219
  load(file)
210
220
  when :sql
211
- file ||= File.join(db_dir, "structure.sql")
212
221
  check_schema_file(file)
213
222
  purge(configuration)
214
223
  structure_load(configuration, file)
@@ -217,6 +226,12 @@ module ActiveRecord
217
226
  end
218
227
  end
219
228
 
229
+ def load_schema_current_if_exists(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
230
+ if File.exist?(file || schema_file(format))
231
+ load_schema_current(format, file, environment)
232
+ end
233
+ end
234
+
220
235
  def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
221
236
  each_current_configuration(environment) { |configuration|
222
237
  load_schema_for configuration, format, file
@@ -31,6 +31,7 @@ module ActiveRecord
31
31
  end
32
32
  establish_connection configuration
33
33
  else
34
+ $stderr.puts error.inspect
34
35
  $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}"
35
36
  $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['encoding']
36
37
  end
@@ -4,6 +4,7 @@ require 'active_record/type/numeric'
4
4
  require 'active_record/type/time_value'
5
5
  require 'active_record/type/value'
6
6
 
7
+ require 'active_record/type/big_integer'
7
8
  require 'active_record/type/binary'
8
9
  require 'active_record/type/boolean'
9
10
  require 'active_record/type/date'
@@ -0,0 +1,13 @@
1
+ require 'active_record/type/integer'
2
+
3
+ module ActiveRecord
4
+ module Type
5
+ class BigInteger < Integer # :nodoc:
6
+ private
7
+
8
+ def max_value
9
+ ::Float::INFINITY
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,8 +1,8 @@
1
- require 'active_record/type/integer'
1
+ require 'active_record/type/big_integer'
2
2
 
3
3
  module ActiveRecord
4
4
  module Type
5
- class DecimalWithoutScale < Integer # :nodoc:
5
+ class DecimalWithoutScale < BigInteger # :nodoc:
6
6
  def type
7
7
  :decimal
8
8
  end
@@ -3,16 +3,14 @@ module ActiveRecord
3
3
  class HashLookupTypeMap < TypeMap # :nodoc:
4
4
  delegate :key?, to: :@mapping
5
5
 
6
- def lookup(type, *args)
7
- @mapping.fetch(type, proc { default_value }).call(type, *args)
6
+ def alias_type(type, alias_type)
7
+ register_type(type) { |_, *args| lookup(alias_type, *args) }
8
8
  end
9
9
 
10
- def fetch(type, *args, &block)
11
- @mapping.fetch(type, block).call(type, *args)
12
- end
10
+ private
13
11
 
14
- def alias_type(type, alias_type)
15
- register_type(type) { |_, *args| lookup(alias_type, *args) }
12
+ def perform_fetch(type, *args, &block)
13
+ @mapping.fetch(type, block).call(type, *args)
16
14
  end
17
15
  end
18
16
  end
@@ -3,21 +3,49 @@ module ActiveRecord
3
3
  class Integer < Value # :nodoc:
4
4
  include Numeric
5
5
 
6
+ def initialize(*)
7
+ super
8
+ @range = -max_value...max_value
9
+ end
10
+
6
11
  def type
7
12
  :integer
8
13
  end
9
14
 
10
15
  alias type_cast_for_database type_cast
11
16
 
17
+ def type_cast_from_database(value)
18
+ return if value.nil?
19
+ value.to_i
20
+ end
21
+
22
+ protected
23
+
24
+ attr_reader :range
25
+
12
26
  private
13
27
 
14
28
  def cast_value(value)
15
29
  case value
16
30
  when true then 1
17
31
  when false then 0
18
- else value.to_i rescue nil
32
+ else
33
+ result = value.to_i rescue nil
34
+ ensure_in_range(result) if result
35
+ result
19
36
  end
20
37
  end
38
+
39
+ def ensure_in_range(value)
40
+ unless range.cover?(value)
41
+ raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || 4}"
42
+ end
43
+ end
44
+
45
+ def max_value
46
+ limit = self.limit || 4
47
+ 1 << (limit * 8 - 1) # 8 bits per byte with one bit for sign
48
+ end
21
49
  end
22
50
  end
23
51
  end