activerecord 5.0.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +218 -2
- data/README.rdoc +1 -1
- data/lib/active_record/aggregations.rb +4 -2
- data/lib/active_record/association_relation.rb +4 -1
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +4 -2
- data/lib/active_record/associations/builder/singular_association.rb +10 -1
- data/lib/active_record/associations/collection_association.rb +22 -17
- data/lib/active_record/associations/collection_proxy.rb +20 -7
- data/lib/active_record/associations/has_many_through_association.rb +4 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
- data/lib/active_record/associations/join_dependency.rb +10 -4
- data/lib/active_record/associations/preloader/association.rb +18 -4
- data/lib/active_record/associations/preloader/collection_association.rb +0 -1
- data/lib/active_record/associations/preloader/singular_association.rb +0 -1
- data/lib/active_record/associations/singular_association.rb +8 -2
- data/lib/active_record/associations.rb +5 -0
- data/lib/active_record/attribute.rb +3 -3
- data/lib/active_record/attribute_methods/primary_key.rb +14 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
- data/lib/active_record/attribute_methods.rb +3 -7
- data/lib/active_record/attribute_set/builder.rb +29 -7
- data/lib/active_record/attribute_set.rb +2 -0
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +15 -11
- data/lib/active_record/base.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +40 -32
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +4 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +8 -7
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +38 -33
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -5
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -45
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +6 -23
- data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -5
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +12 -1
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +7 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +6 -2
- data/lib/active_record/core.rb +3 -1
- data/lib/active_record/enum.rb +6 -5
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/integration.rb +13 -10
- data/lib/active_record/migration.rb +6 -5
- data/lib/active_record/model_schema.rb +134 -47
- data/lib/active_record/no_touching.rb +4 -0
- data/lib/active_record/persistence.rb +10 -4
- data/lib/active_record/query_cache.rb +13 -15
- data/lib/active_record/querying.rb +3 -3
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/reflection.rb +12 -0
- data/lib/active_record/relation/calculations.rb +11 -11
- data/lib/active_record/relation/delegation.rb +1 -1
- data/lib/active_record/relation/finder_methods.rb +11 -9
- data/lib/active_record/relation/query_methods.rb +3 -3
- data/lib/active_record/relation.rb +7 -4
- data/lib/active_record/result.rb +7 -1
- data/lib/active_record/sanitization.rb +11 -1
- data/lib/active_record/schema_dumper.rb +10 -17
- data/lib/active_record/scoping/named.rb +1 -1
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/table_metadata.rb +4 -3
- data/lib/active_record/touch_later.rb +6 -1
- data/lib/active_record/type/internal/abstract_json.rb +5 -1
- data/lib/active_record/validations/uniqueness.rb +3 -4
- data/lib/active_record.rb +1 -1
- metadata +8 -7
|
@@ -2,71 +2,150 @@ module ActiveRecord
|
|
|
2
2
|
module ModelSchema
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
4
|
|
|
5
|
+
##
|
|
6
|
+
# :singleton-method: primary_key_prefix_type
|
|
7
|
+
# :call-seq: primary_key_prefix_type
|
|
8
|
+
#
|
|
9
|
+
# The prefix type that will be prepended to every primary key column name.
|
|
10
|
+
# The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified,
|
|
11
|
+
# the Product class will look for "productid" instead of "id" as the primary column. If the
|
|
12
|
+
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
|
|
13
|
+
# that this is a global setting for all Active Records.
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# :singleton-method: primary_key_prefix_type=
|
|
17
|
+
# :call-seq: primary_key_prefix_type=(prefix_type)
|
|
18
|
+
#
|
|
19
|
+
# Sets the prefix type that will be prepended to every primary key column name.
|
|
20
|
+
# The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified,
|
|
21
|
+
# the Product class will look for "productid" instead of "id" as the primary column. If the
|
|
22
|
+
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
|
|
23
|
+
# that this is a global setting for all Active Records.
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# :singleton-method: table_name_prefix
|
|
27
|
+
# :call-seq: table_name_prefix
|
|
28
|
+
#
|
|
29
|
+
# The prefix string to prepend to every table name.
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# :singleton-method: table_name_prefix=
|
|
33
|
+
# :call-seq: table_name_prefix=(prefix)
|
|
34
|
+
#
|
|
35
|
+
# Sets the prefix string to prepend to every table name. So if set to "basecamp_", all table
|
|
36
|
+
# names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient
|
|
37
|
+
# way of creating a namespace for tables in a shared database. By default, the prefix is the
|
|
38
|
+
# empty string.
|
|
39
|
+
#
|
|
40
|
+
# If you are organising your models within modules you can add a prefix to the models within
|
|
41
|
+
# a namespace by defining a singleton method in the parent module called table_name_prefix which
|
|
42
|
+
# returns your chosen prefix.
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# :singleton-method: table_name_suffix
|
|
46
|
+
# :call-seq: table_name_suffix
|
|
47
|
+
#
|
|
48
|
+
# The suffix string to append to every table name.
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# :singleton-method: table_name_suffix=
|
|
52
|
+
# :call-seq: table_name_suffix=(suffix)
|
|
53
|
+
#
|
|
54
|
+
# Works like +table_name_prefix=+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
|
|
55
|
+
# "people_basecamp"). By default, the suffix is the empty string.
|
|
56
|
+
#
|
|
57
|
+
# If you are organising your models within modules, you can add a suffix to the models within
|
|
58
|
+
# a namespace by defining a singleton method in the parent module called table_name_suffix which
|
|
59
|
+
# returns your chosen suffix.
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# :singleton-method: schema_migrations_table_name
|
|
63
|
+
# :call-seq: schema_migrations_table_name
|
|
64
|
+
#
|
|
65
|
+
# The name of the schema migrations table. By default, the value is <tt>"schema_migrations"</tt>.
|
|
66
|
+
|
|
67
|
+
##
|
|
68
|
+
# :singleton-method: schema_migrations_table_name=
|
|
69
|
+
# :call-seq: schema_migrations_table_name=(table_name)
|
|
70
|
+
#
|
|
71
|
+
# Sets the name of the schema migrations table.
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# :singleton-method: internal_metadata_table_name
|
|
75
|
+
# :call-seq: internal_metadata_table_name
|
|
76
|
+
#
|
|
77
|
+
# The name of the internal metadata table. By default, the value is <tt>"ar_internal_metadata"</tt>.
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# :singleton-method: internal_metadata_table_name=
|
|
81
|
+
# :call-seq: internal_metadata_table_name=(table_name)
|
|
82
|
+
#
|
|
83
|
+
# Sets the name of the internal metadata table.
|
|
84
|
+
|
|
85
|
+
##
|
|
86
|
+
# :singleton-method: protected_environments
|
|
87
|
+
# :call-seq: protected_environments
|
|
88
|
+
#
|
|
89
|
+
# The array of names of environments where destructive actions should be prohibited. By default,
|
|
90
|
+
# the value is <tt>["production"]</tt>.
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# :singleton-method: protected_environments=
|
|
94
|
+
# :call-seq: protected_environments=(environments)
|
|
95
|
+
#
|
|
96
|
+
# Sets an array of names of environments where destructive actions should be prohibited.
|
|
97
|
+
|
|
98
|
+
##
|
|
99
|
+
# :singleton-method: pluralize_table_names
|
|
100
|
+
# :call-seq: pluralize_table_names
|
|
101
|
+
#
|
|
102
|
+
# Indicates whether table names should be the pluralized versions of the corresponding class names.
|
|
103
|
+
# If true, the default table name for a Product class will be "products". If false, it would just be "product".
|
|
104
|
+
# See table_name for the full rules on table/class naming. This is true, by default.
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
# :singleton-method: pluralize_table_names=
|
|
108
|
+
# :call-seq: pluralize_table_names=(value)
|
|
109
|
+
#
|
|
110
|
+
# Set whether table names should be the pluralized versions of the corresponding class names.
|
|
111
|
+
# If true, the default table name for a Product class will be "products". If false, it would just be "product".
|
|
112
|
+
# See table_name for the full rules on table/class naming. This is true, by default.
|
|
113
|
+
|
|
114
|
+
##
|
|
115
|
+
# :singleton-method: ignored_columns
|
|
116
|
+
# :call-seq: ignored_columns
|
|
117
|
+
#
|
|
118
|
+
# The list of columns names the model should ignore. Ignored columns won't have attribute
|
|
119
|
+
# accessors defined, and won't be referenced in SQL queries.
|
|
120
|
+
|
|
121
|
+
##
|
|
122
|
+
# :singleton-method: ignored_columns=
|
|
123
|
+
# :call-seq: ignored_columns=(columns)
|
|
124
|
+
#
|
|
125
|
+
# Sets the columns names the model should ignore. Ignored columns won't have attribute
|
|
126
|
+
# accessors defined, and won't be referenced in SQL queries.
|
|
127
|
+
|
|
5
128
|
included do
|
|
6
|
-
##
|
|
7
|
-
# :singleton-method:
|
|
8
|
-
# Accessor for the prefix type that will be prepended to every primary key column name.
|
|
9
|
-
# The options are :table_name and :table_name_with_underscore. If the first is specified,
|
|
10
|
-
# the Product class will look for "productid" instead of "id" as the primary column. If the
|
|
11
|
-
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
|
|
12
|
-
# that this is a global setting for all Active Records.
|
|
13
129
|
mattr_accessor :primary_key_prefix_type, instance_writer: false
|
|
14
130
|
|
|
15
|
-
##
|
|
16
|
-
# :singleton-method:
|
|
17
|
-
# Accessor for the name of the prefix string to prepend to every table name. So if set
|
|
18
|
-
# to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
|
|
19
|
-
# etc. This is a convenient way of creating a namespace for tables in a shared database.
|
|
20
|
-
# By default, the prefix is the empty string.
|
|
21
|
-
#
|
|
22
|
-
# If you are organising your models within modules you can add a prefix to the models within
|
|
23
|
-
# a namespace by defining a singleton method in the parent module called table_name_prefix which
|
|
24
|
-
# returns your chosen prefix.
|
|
25
131
|
class_attribute :table_name_prefix, instance_writer: false
|
|
26
132
|
self.table_name_prefix = ""
|
|
27
133
|
|
|
28
|
-
##
|
|
29
|
-
# :singleton-method:
|
|
30
|
-
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
|
|
31
|
-
# "people_basecamp"). By default, the suffix is the empty string.
|
|
32
|
-
#
|
|
33
|
-
# If you are organising your models within modules, you can add a suffix to the models within
|
|
34
|
-
# a namespace by defining a singleton method in the parent module called table_name_suffix which
|
|
35
|
-
# returns your chosen suffix.
|
|
36
134
|
class_attribute :table_name_suffix, instance_writer: false
|
|
37
135
|
self.table_name_suffix = ""
|
|
38
136
|
|
|
39
|
-
##
|
|
40
|
-
# :singleton-method:
|
|
41
|
-
# Accessor for the name of the schema migrations table. By default, the value is "schema_migrations"
|
|
42
137
|
class_attribute :schema_migrations_table_name, instance_accessor: false
|
|
43
138
|
self.schema_migrations_table_name = "schema_migrations"
|
|
44
139
|
|
|
45
|
-
##
|
|
46
|
-
# :singleton-method:
|
|
47
|
-
# Accessor for the name of the internal metadata table. By default, the value is "ar_internal_metadata"
|
|
48
140
|
class_attribute :internal_metadata_table_name, instance_accessor: false
|
|
49
141
|
self.internal_metadata_table_name = "ar_internal_metadata"
|
|
50
142
|
|
|
51
|
-
##
|
|
52
|
-
# :singleton-method:
|
|
53
|
-
# Accessor for an array of names of environments where destructive actions should be prohibited. By default,
|
|
54
|
-
# the value is ["production"]
|
|
55
143
|
class_attribute :protected_environments, instance_accessor: false
|
|
56
144
|
self.protected_environments = ["production"]
|
|
57
145
|
|
|
58
|
-
##
|
|
59
|
-
# :singleton-method:
|
|
60
|
-
# Indicates whether table names should be the pluralized versions of the corresponding class names.
|
|
61
|
-
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
|
|
62
|
-
# See table_name for the full rules on table/class naming. This is true, by default.
|
|
63
146
|
class_attribute :pluralize_table_names, instance_writer: false
|
|
64
147
|
self.pluralize_table_names = true
|
|
65
148
|
|
|
66
|
-
##
|
|
67
|
-
# :singleton-method:
|
|
68
|
-
# Accessor for the list of columns names the model should ignore. Ignored columns won't have attribute
|
|
69
|
-
# accessors defined, and won't be referenced in SQL queries.
|
|
70
149
|
class_attribute :ignored_columns, instance_accessor: false
|
|
71
150
|
self.ignored_columns = [].freeze
|
|
72
151
|
|
|
@@ -249,7 +328,11 @@ module ActiveRecord
|
|
|
249
328
|
end
|
|
250
329
|
|
|
251
330
|
def attributes_builder # :nodoc:
|
|
252
|
-
@attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key)
|
|
331
|
+
@attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) do |name|
|
|
332
|
+
unless columns_hash.key?(name)
|
|
333
|
+
_default_attributes[name].dup
|
|
334
|
+
end
|
|
335
|
+
end
|
|
253
336
|
end
|
|
254
337
|
|
|
255
338
|
def columns_hash # :nodoc:
|
|
@@ -278,8 +361,12 @@ module ActiveRecord
|
|
|
278
361
|
#
|
|
279
362
|
# +attr_name+ The name of the attribute to retrieve the type for. Must be
|
|
280
363
|
# a string
|
|
281
|
-
def type_for_attribute(attr_name)
|
|
282
|
-
|
|
364
|
+
def type_for_attribute(attr_name, &block)
|
|
365
|
+
if block
|
|
366
|
+
attribute_types.fetch(attr_name, &block)
|
|
367
|
+
else
|
|
368
|
+
attribute_types[attr_name]
|
|
369
|
+
end
|
|
283
370
|
end
|
|
284
371
|
|
|
285
372
|
# Returns a hash where the keys are column names and the values are
|
|
@@ -63,10 +63,10 @@ module ActiveRecord
|
|
|
63
63
|
#
|
|
64
64
|
# See <tt>ActiveRecord::Inheritance#discriminate_class_for_record</tt> to see
|
|
65
65
|
# how this "single-table" inheritance mapping is implemented.
|
|
66
|
-
def instantiate(attributes, column_types = {})
|
|
66
|
+
def instantiate(attributes, column_types = {}, &block)
|
|
67
67
|
klass = discriminate_class_for_record(attributes)
|
|
68
68
|
attributes = klass.attributes_builder.build_from_database(attributes, column_types)
|
|
69
|
-
klass.allocate.init_with(
|
|
69
|
+
klass.allocate.init_with("attributes" => attributes, "new_record" => false, &block)
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
private
|
|
@@ -252,7 +252,8 @@ module ActiveRecord
|
|
|
252
252
|
name = name.to_s
|
|
253
253
|
verify_readonly_attribute(name)
|
|
254
254
|
public_send("#{name}=", value)
|
|
255
|
-
|
|
255
|
+
|
|
256
|
+
changed? ? save(validate: false) : true
|
|
256
257
|
end
|
|
257
258
|
|
|
258
259
|
# Updates the attributes of the model from the passed-in hash and saves the
|
|
@@ -479,7 +480,12 @@ module ActiveRecord
|
|
|
479
480
|
# ball.touch(:updated_at) # => raises ActiveRecordError
|
|
480
481
|
#
|
|
481
482
|
def touch(*names, time: nil)
|
|
482
|
-
|
|
483
|
+
unless persisted?
|
|
484
|
+
raise ActiveRecordError, <<-MSG.squish
|
|
485
|
+
cannot touch on a new or destroyed record object. Consider using
|
|
486
|
+
persisted?, new_record?, or destroyed? before touching
|
|
487
|
+
MSG
|
|
488
|
+
end
|
|
483
489
|
|
|
484
490
|
time ||= current_time_from_proper_timezone
|
|
485
491
|
attributes = timestamp_attributes_for_update_in_model
|
|
@@ -24,30 +24,28 @@ module ActiveRecord
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def self.run
|
|
27
|
-
connection = ActiveRecord::Base.connection
|
|
28
|
-
enabled = connection.query_cache_enabled
|
|
29
27
|
connection_id = ActiveRecord::Base.connection_id
|
|
30
|
-
connection.enable_query_cache!
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
caching_pool = ActiveRecord::Base.connection_pool
|
|
30
|
+
caching_was_enabled = caching_pool.query_cache_enabled
|
|
31
|
+
|
|
32
|
+
caching_pool.enable_query_cache!
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
[caching_pool, caching_was_enabled, connection_id]
|
|
35
|
+
end
|
|
37
36
|
|
|
37
|
+
def self.complete((caching_pool, caching_was_enabled, connection_id))
|
|
38
38
|
ActiveRecord::Base.connection_id = connection_id
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
|
|
40
|
+
caching_pool.disable_query_cache! unless caching_was_enabled
|
|
41
|
+
|
|
42
|
+
ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool|
|
|
43
|
+
pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
|
|
44
|
+
end
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
def self.install_executor_hooks(executor = ActiveSupport::Executor)
|
|
44
48
|
executor.register_hook(self)
|
|
45
|
-
|
|
46
|
-
executor.to_complete do
|
|
47
|
-
unless ActiveRecord::Base.connection.transaction_open?
|
|
48
|
-
ActiveRecord::Base.clear_active_connections!
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
49
|
end
|
|
52
50
|
end
|
|
53
51
|
end
|
|
@@ -35,7 +35,7 @@ module ActiveRecord
|
|
|
35
35
|
#
|
|
36
36
|
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
|
|
37
37
|
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
|
|
38
|
-
def find_by_sql(sql, binds = [], preparable: nil)
|
|
38
|
+
def find_by_sql(sql, binds = [], preparable: nil, &block)
|
|
39
39
|
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
|
|
40
40
|
column_types = result_set.column_types.dup
|
|
41
41
|
columns_hash.each_key { |k| column_types.delete k }
|
|
@@ -46,8 +46,8 @@ module ActiveRecord
|
|
|
46
46
|
class_name: name
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
message_bus.instrument(
|
|
50
|
-
result_set.map { |record| instantiate(record, column_types) }
|
|
49
|
+
message_bus.instrument("instantiation.active_record", payload) do
|
|
50
|
+
result_set.map { |record| instantiate(record, column_types, &block) }
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
@@ -19,7 +19,7 @@ module ActiveRecord
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def cleanup_view_runtime
|
|
22
|
-
if logger.info? && ActiveRecord::Base.connected?
|
|
22
|
+
if logger && logger.info? && ActiveRecord::Base.connected?
|
|
23
23
|
db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
|
|
24
24
|
self.db_runtime = (db_runtime || 0) + db_rt_before_render
|
|
25
25
|
runtime = super
|
|
@@ -311,6 +311,10 @@ module ActiveRecord
|
|
|
311
311
|
active_record == other_aggregation.active_record
|
|
312
312
|
end
|
|
313
313
|
|
|
314
|
+
def scope_for(klass)
|
|
315
|
+
scope ? klass.unscoped.instance_exec(nil, &scope) : klass.unscoped
|
|
316
|
+
end
|
|
317
|
+
|
|
314
318
|
private
|
|
315
319
|
def derive_class_name
|
|
316
320
|
name.to_s.camelize
|
|
@@ -394,6 +398,10 @@ module ActiveRecord
|
|
|
394
398
|
options[:primary_key] || primary_key(klass || self.klass)
|
|
395
399
|
end
|
|
396
400
|
|
|
401
|
+
def association_primary_key_type
|
|
402
|
+
klass.type_for_attribute(association_primary_key)
|
|
403
|
+
end
|
|
404
|
+
|
|
397
405
|
def active_record_primary_key
|
|
398
406
|
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
|
|
399
407
|
end
|
|
@@ -847,6 +855,10 @@ module ActiveRecord
|
|
|
847
855
|
actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
|
|
848
856
|
end
|
|
849
857
|
|
|
858
|
+
def association_primary_key_type
|
|
859
|
+
klass.type_for_attribute(association_primary_key)
|
|
860
|
+
end
|
|
861
|
+
|
|
850
862
|
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
|
|
851
863
|
#
|
|
852
864
|
# class Post < ActiveRecord::Base
|
|
@@ -113,7 +113,10 @@ module ActiveRecord
|
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
if has_include?(column_name)
|
|
116
|
-
construct_relation_for_association_calculations
|
|
116
|
+
relation = construct_relation_for_association_calculations
|
|
117
|
+
relation = relation.distinct if operation.to_s.downcase == "count"
|
|
118
|
+
|
|
119
|
+
relation.calculate(operation, column_name)
|
|
117
120
|
else
|
|
118
121
|
perform_calculation(operation, column_name)
|
|
119
122
|
end
|
|
@@ -156,7 +159,7 @@ module ActiveRecord
|
|
|
156
159
|
#
|
|
157
160
|
def pluck(*column_names)
|
|
158
161
|
if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
|
|
159
|
-
return
|
|
162
|
+
return records.pluck(*column_names)
|
|
160
163
|
end
|
|
161
164
|
|
|
162
165
|
if has_include?(column_names.first)
|
|
@@ -194,11 +197,6 @@ module ActiveRecord
|
|
|
194
197
|
|
|
195
198
|
if operation == "count"
|
|
196
199
|
column_name ||= select_for_count
|
|
197
|
-
|
|
198
|
-
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
|
|
199
|
-
distinct = true
|
|
200
|
-
end
|
|
201
|
-
|
|
202
200
|
column_name = primary_key if column_name == :all && distinct
|
|
203
201
|
distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
|
|
204
202
|
end
|
|
@@ -308,8 +306,10 @@ module ActiveRecord
|
|
|
308
306
|
|
|
309
307
|
Hash[calculated_data.map do |row|
|
|
310
308
|
key = group_columns.map { |aliaz, col_name|
|
|
311
|
-
column =
|
|
312
|
-
|
|
309
|
+
column = type_for(col_name) do
|
|
310
|
+
calculated_data.column_types.fetch(aliaz) do
|
|
311
|
+
Type::Value.new
|
|
312
|
+
end
|
|
313
313
|
end
|
|
314
314
|
type_cast_calculated_value(row[aliaz], column)
|
|
315
315
|
}
|
|
@@ -342,9 +342,9 @@ module ActiveRecord
|
|
|
342
342
|
@klass.connection.table_alias_for(table_name)
|
|
343
343
|
end
|
|
344
344
|
|
|
345
|
-
def type_for(field)
|
|
345
|
+
def type_for(field, &block)
|
|
346
346
|
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
|
|
347
|
-
@klass.type_for_attribute(field_name)
|
|
347
|
+
@klass.type_for_attribute(field_name, &block)
|
|
348
348
|
end
|
|
349
349
|
|
|
350
350
|
def type_cast_calculated_value(value, type, operation = nil)
|
|
@@ -37,7 +37,7 @@ module ActiveRecord
|
|
|
37
37
|
|
|
38
38
|
delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
|
|
39
39
|
:[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
|
|
40
|
-
:shuffle, :split, to: :records
|
|
40
|
+
:shuffle, :split, :index, to: :records
|
|
41
41
|
|
|
42
42
|
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
|
|
43
43
|
:connection, :columns_hash, :to => :klass
|
|
@@ -343,18 +343,20 @@ module ActiveRecord
|
|
|
343
343
|
# of results obtained should be provided in the +result_size+ argument and
|
|
344
344
|
# the expected number of results should be provided in the +expected_size+
|
|
345
345
|
# argument.
|
|
346
|
-
def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
|
|
346
|
+
def raise_record_not_found_exception!(ids, result_size, expected_size, key = primary_key) #:nodoc:
|
|
347
347
|
conditions = arel.where_sql(@klass.arel_engine)
|
|
348
348
|
conditions = " [#{conditions}]" if conditions
|
|
349
|
+
name = @klass.name
|
|
349
350
|
|
|
350
351
|
if Array(ids).size == 1
|
|
351
|
-
error = "Couldn't find #{
|
|
352
|
+
error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
|
|
353
|
+
raise RecordNotFound.new(error, name, key, ids)
|
|
352
354
|
else
|
|
353
|
-
error = "Couldn't find all #{
|
|
355
|
+
error = "Couldn't find all #{name.pluralize} with '#{key}': "
|
|
354
356
|
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
|
|
355
|
-
end
|
|
356
357
|
|
|
357
|
-
|
|
358
|
+
raise RecordNotFound, error
|
|
359
|
+
end
|
|
358
360
|
end
|
|
359
361
|
|
|
360
362
|
private
|
|
@@ -520,7 +522,7 @@ module ActiveRecord
|
|
|
520
522
|
|
|
521
523
|
def find_take
|
|
522
524
|
if loaded?
|
|
523
|
-
|
|
525
|
+
records.first
|
|
524
526
|
else
|
|
525
527
|
@take ||= limit(1).records.first
|
|
526
528
|
end
|
|
@@ -537,7 +539,7 @@ module ActiveRecord
|
|
|
537
539
|
MSG
|
|
538
540
|
end
|
|
539
541
|
if loaded?
|
|
540
|
-
|
|
542
|
+
records[index]
|
|
541
543
|
else
|
|
542
544
|
offset ||= offset_index
|
|
543
545
|
@offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
|
|
@@ -563,7 +565,7 @@ module ActiveRecord
|
|
|
563
565
|
|
|
564
566
|
def find_nth_from_last(index)
|
|
565
567
|
if loaded?
|
|
566
|
-
|
|
568
|
+
records[-index]
|
|
567
569
|
else
|
|
568
570
|
relation = if order_values.empty? && primary_key
|
|
569
571
|
order(arel_attribute(primary_key).asc)
|
|
@@ -584,7 +586,7 @@ module ActiveRecord
|
|
|
584
586
|
|
|
585
587
|
def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
|
|
586
588
|
if loaded?
|
|
587
|
-
|
|
589
|
+
records[index, limit]
|
|
588
590
|
else
|
|
589
591
|
index += offset
|
|
590
592
|
find_nth_with_limit(index, limit)
|
|
@@ -1141,9 +1141,9 @@ module ActiveRecord
|
|
|
1141
1141
|
end
|
|
1142
1142
|
|
|
1143
1143
|
def does_not_support_reverse?(order)
|
|
1144
|
-
#
|
|
1145
|
-
order
|
|
1146
|
-
#
|
|
1144
|
+
# Uses SQL function with multiple arguments.
|
|
1145
|
+
(order.include?(',') && order.split(',').find { |section| section.count('(') != section.count(')')}) ||
|
|
1146
|
+
# Uses "nulls first" like construction.
|
|
1147
1147
|
order =~ /nulls (first|last)\Z/i
|
|
1148
1148
|
end
|
|
1149
1149
|
|
|
@@ -372,6 +372,9 @@ module ActiveRecord
|
|
|
372
372
|
#
|
|
373
373
|
# # Update all books that match conditions, but limit it to 5 ordered by date
|
|
374
374
|
# Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
|
|
375
|
+
#
|
|
376
|
+
# # Update all invoices and set the number column to its id value.
|
|
377
|
+
# Invoice.update_all('number = id')
|
|
375
378
|
def update_all(updates)
|
|
376
379
|
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
|
|
377
380
|
|
|
@@ -576,8 +579,8 @@ module ActiveRecord
|
|
|
576
579
|
# return value is the relation itself, not the records.
|
|
577
580
|
#
|
|
578
581
|
# Post.where(published: true).load # => #<ActiveRecord::Relation>
|
|
579
|
-
def load
|
|
580
|
-
exec_queries unless loaded?
|
|
582
|
+
def load(&block)
|
|
583
|
+
exec_queries(&block) unless loaded?
|
|
581
584
|
|
|
582
585
|
self
|
|
583
586
|
end
|
|
@@ -695,8 +698,8 @@ module ActiveRecord
|
|
|
695
698
|
|
|
696
699
|
private
|
|
697
700
|
|
|
698
|
-
def exec_queries
|
|
699
|
-
@records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes).freeze
|
|
701
|
+
def exec_queries(&block)
|
|
702
|
+
@records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes, &block).freeze
|
|
700
703
|
|
|
701
704
|
preload = preload_values
|
|
702
705
|
preload += includes_values unless eager_loading?
|
data/lib/active_record/result.rb
CHANGED
|
@@ -75,8 +75,14 @@ module ActiveRecord
|
|
|
75
75
|
hash_rows[idx]
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
+
def first
|
|
79
|
+
return nil if @rows.empty?
|
|
80
|
+
Hash[@columns.zip(@rows.first)]
|
|
81
|
+
end
|
|
82
|
+
|
|
78
83
|
def last
|
|
79
|
-
|
|
84
|
+
return nil if @rows.empty?
|
|
85
|
+
Hash[@columns.zip(@rows.last)]
|
|
80
86
|
end
|
|
81
87
|
|
|
82
88
|
def cast_values(type_overrides = {}) # :nodoc:
|
|
@@ -116,7 +116,17 @@ module ActiveRecord
|
|
|
116
116
|
def sanitize_sql_hash_for_assignment(attrs, table)
|
|
117
117
|
c = connection
|
|
118
118
|
attrs.map do |attr, value|
|
|
119
|
-
value
|
|
119
|
+
if value.is_a?(Base)
|
|
120
|
+
require "active_support/core_ext/string/filters"
|
|
121
|
+
ActiveSupport::Deprecation.warn(<<-WARNING.squish)
|
|
122
|
+
Passing `ActiveRecord::Base` objects to
|
|
123
|
+
`sanitize_sql_hash_for_assignment` (or methods which call it,
|
|
124
|
+
such as `update_all`) is deprecated. Please pass the id directly,
|
|
125
|
+
instead.
|
|
126
|
+
WARNING
|
|
127
|
+
else
|
|
128
|
+
value = type_for_attribute(attr.to_s).serialize(value)
|
|
129
|
+
end
|
|
120
130
|
"#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
|
|
121
131
|
end.join(', ')
|
|
122
132
|
end
|
|
@@ -105,12 +105,7 @@ HEADER
|
|
|
105
105
|
tbl = StringIO.new
|
|
106
106
|
|
|
107
107
|
# first dump primary key column
|
|
108
|
-
|
|
109
|
-
pk = @connection.primary_keys(table)
|
|
110
|
-
pk = pk.first unless pk.size > 1
|
|
111
|
-
else
|
|
112
|
-
pk = @connection.primary_key(table)
|
|
113
|
-
end
|
|
108
|
+
pk = @connection.primary_key(table)
|
|
114
109
|
|
|
115
110
|
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
|
116
111
|
|
|
@@ -132,10 +127,8 @@ HEADER
|
|
|
132
127
|
tbl.print ", force: :cascade"
|
|
133
128
|
|
|
134
129
|
table_options = @connection.table_options(table)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if comment = @connection.table_comment(table).presence
|
|
138
|
-
tbl.print ", comment: #{comment.inspect}"
|
|
130
|
+
if table_options.present?
|
|
131
|
+
tbl.print ", #{format_options(table_options)}"
|
|
139
132
|
end
|
|
140
133
|
|
|
141
134
|
tbl.puts " do |t|"
|
|
@@ -218,13 +211,9 @@ HEADER
|
|
|
218
211
|
index.columns.inspect,
|
|
219
212
|
"name: #{index.name.inspect}",
|
|
220
213
|
]
|
|
221
|
-
index_parts <<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
index_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
|
|
225
|
-
|
|
226
|
-
index_orders = index.orders || {}
|
|
227
|
-
index_parts << "order: #{index.orders.inspect}" if index_orders.any?
|
|
214
|
+
index_parts << "unique: true" if index.unique
|
|
215
|
+
index_parts << "length: { #{format_options(index.lengths)} }" if index.lengths.present?
|
|
216
|
+
index_parts << "order: { #{format_options(index.orders)} }" if index.orders.present?
|
|
228
217
|
index_parts << "where: #{index.where.inspect}" if index.where
|
|
229
218
|
index_parts << "using: #{index.using.inspect}" if index.using
|
|
230
219
|
index_parts << "type: #{index.type.inspect}" if index.type
|
|
@@ -262,6 +251,10 @@ HEADER
|
|
|
262
251
|
end
|
|
263
252
|
end
|
|
264
253
|
|
|
254
|
+
def format_options(options)
|
|
255
|
+
options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
|
|
256
|
+
end
|
|
257
|
+
|
|
265
258
|
def remove_prefix_and_suffix(table)
|
|
266
259
|
table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
|
|
267
260
|
end
|
|
@@ -101,12 +101,12 @@ module ActiveRecord
|
|
|
101
101
|
@bind_map = bind_map
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
-
def execute(params, klass, connection)
|
|
104
|
+
def execute(params, klass, connection, &block)
|
|
105
105
|
bind_values = bind_map.bind params
|
|
106
106
|
|
|
107
107
|
sql = query_builder.sql_for bind_values, connection
|
|
108
108
|
|
|
109
|
-
klass.find_by_sql(sql, bind_values, preparable: true)
|
|
109
|
+
klass.find_by_sql(sql, bind_values, preparable: true, &block)
|
|
110
110
|
end
|
|
111
111
|
alias :call :execute
|
|
112
112
|
end
|