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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +107 -34
- data/lib/active_record/aggregations.rb +2 -2
- data/lib/active_record/associations.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +3 -12
- data/lib/active_record/associations/association_scope.rb +1 -2
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/collection_association.rb +29 -6
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +2 -2
- data/lib/active_record/associations/join_dependency.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +1 -0
- data/lib/active_record/associations/preloader/association.rb +3 -8
- data/lib/active_record/associations/preloader/through_association.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +2 -1
- data/lib/active_record/attribute_methods.rb +2 -2
- data/lib/active_record/attribute_methods/dirty.rb +1 -1
- data/lib/active_record/attribute_methods/primary_key.rb +2 -1
- data/lib/active_record/attribute_methods/read.rb +13 -5
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
- data/lib/active_record/attribute_set.rb +7 -11
- data/lib/active_record/attribute_set/builder.rb +66 -17
- data/lib/active_record/attributes.rb +20 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +0 -4
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +25 -24
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +9 -7
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +25 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +6 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +6 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +6 -16
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +29 -7
- data/lib/active_record/connection_adapters/postgresql/utils.rb +15 -4
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +15 -6
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +5 -7
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +5 -3
- data/lib/active_record/enum.rb +1 -1
- data/lib/active_record/errors.rb +21 -0
- data/lib/active_record/fixtures.rb +4 -2
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/migration.rb +15 -4
- data/lib/active_record/model_schema.rb +8 -4
- data/lib/active_record/persistence.rb +5 -5
- data/lib/active_record/railtie.rb +0 -2
- data/lib/active_record/railties/databases.rake +7 -6
- data/lib/active_record/reflection.rb +2 -2
- data/lib/active_record/relation.rb +21 -13
- data/lib/active_record/relation/calculations.rb +1 -0
- data/lib/active_record/relation/finder_methods.rb +8 -5
- data/lib/active_record/relation/merger.rb +0 -12
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +30 -18
- data/lib/active_record/sanitization.rb +4 -1
- data/lib/active_record/schema_dumper.rb +1 -6
- data/lib/active_record/scoping/named.rb +1 -1
- data/lib/active_record/statement_cache.rb +21 -10
- data/lib/active_record/tasks/database_tasks.rb +17 -2
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -0
- data/lib/active_record/type.rb +1 -0
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/decimal_without_scale.rb +2 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +5 -7
- data/lib/active_record/type/integer.rb +29 -1
- data/lib/active_record/type/serialized.rb +1 -1
- data/lib/active_record/type/string.rb +4 -4
- data/lib/active_record/type/type_map.rb +23 -7
- data/lib/active_record/validations.rb +4 -3
- data/lib/active_record/validations/uniqueness.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +3 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
- 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
|
-
|
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(
|
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
|
|
@@ -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
|
-
|
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
|
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.
|
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
|
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)
|
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(
|
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
|
-
|
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
|
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
|
4
|
+
# Initializing the cache is done by passing the statement in the create block:
|
5
5
|
#
|
6
|
-
# cache =
|
7
|
-
# Book.where(name: "my book").
|
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
|
-
|
17
|
-
|
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
|
data/lib/active_record/type.rb
CHANGED
@@ -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'
|
@@ -3,16 +3,14 @@ module ActiveRecord
|
|
3
3
|
class HashLookupTypeMap < TypeMap # :nodoc:
|
4
4
|
delegate :key?, to: :@mapping
|
5
5
|
|
6
|
-
def
|
7
|
-
|
6
|
+
def alias_type(type, alias_type)
|
7
|
+
register_type(type) { |_, *args| lookup(alias_type, *args) }
|
8
8
|
end
|
9
9
|
|
10
|
-
|
11
|
-
@mapping.fetch(type, block).call(type, *args)
|
12
|
-
end
|
10
|
+
private
|
13
11
|
|
14
|
-
def
|
15
|
-
|
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
|
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
|