activerecord 3.2.22.4 → 4.0.13
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 +2799 -617
- data/MIT-LICENSE +1 -1
- data/README.rdoc +23 -32
- data/examples/performance.rb +1 -1
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/association_relation.rb +22 -0
- data/lib/active_record/associations/alias_tracker.rb +4 -2
- data/lib/active_record/associations/association.rb +60 -46
- data/lib/active_record/associations/association_scope.rb +46 -40
- data/lib/active_record/associations/belongs_to_association.rb +17 -4
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +73 -56
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +130 -96
- data/lib/active_record/associations/collection_proxy.rb +916 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
- data/lib/active_record/associations/has_many_association.rb +35 -8
- data/lib/active_record/associations/has_many_through_association.rb +37 -17
- data/lib/active_record/associations/has_one_association.rb +42 -19
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
- data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
- data/lib/active_record/associations/join_dependency.rb +30 -9
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/preloader.rb +20 -43
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +3 -3
- data/lib/active_record/associations.rb +223 -282
- data/lib/active_record/attribute_assignment.rb +134 -154
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +36 -29
- data/lib/active_record/attribute_methods/primary_key.rb +45 -31
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +67 -90
- data/lib/active_record/attribute_methods/serialization.rb +133 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
- data/lib/active_record/attribute_methods/write.rb +34 -39
- data/lib/active_record/attribute_methods.rb +268 -108
- data/lib/active_record/autosave_association.rb +80 -73
- data/lib/active_record/base.rb +54 -451
- data/lib/active_record/callbacks.rb +60 -22
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
- data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
- data/lib/active_record/connection_adapters/column.rb +67 -36
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
- data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
- data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +472 -0
- data/lib/active_record/counter_cache.rb +107 -108
- data/lib/active_record/dynamic_matchers.rb +115 -63
- data/lib/active_record/errors.rb +36 -18
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +8 -4
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +159 -155
- data/lib/active_record/inheritance.rb +93 -59
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +39 -43
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration/command_recorder.rb +102 -33
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +411 -173
- data/lib/active_record/model_schema.rb +81 -94
- data/lib/active_record/nested_attributes.rb +173 -131
- data/lib/active_record/null_relation.rb +67 -0
- data/lib/active_record/persistence.rb +254 -106
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +113 -38
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +115 -368
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +110 -61
- data/lib/active_record/relation/batches.rb +29 -29
- data/lib/active_record/relation/calculations.rb +155 -125
- data/lib/active_record/relation/delegation.rb +94 -18
- data/lib/active_record/relation/finder_methods.rb +151 -203
- data/lib/active_record/relation/merger.rb +188 -0
- data/lib/active_record/relation/predicate_builder.rb +85 -42
- data/lib/active_record/relation/query_methods.rb +793 -146
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/relation.rb +293 -173
- data/lib/active_record/result.rb +48 -7
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +41 -54
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +41 -41
- data/lib/active_record/schema_migration.rb +46 -0
- data/lib/active_record/scoping/default.rb +56 -52
- data/lib/active_record/scoping/named.rb +78 -103
- data/lib/active_record/scoping.rb +54 -124
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +131 -15
- data/lib/active_record/tasks/database_tasks.rb +204 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +67 -38
- data/lib/active_record/timestamp.rb +16 -11
- data/lib/active_record/transactions.rb +73 -51
- data/lib/active_record/validations/associated.rb +19 -13
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +110 -57
- data/lib/active_record/validations.rb +18 -17
- data/lib/active_record/version.rb +7 -6
- data/lib/active_record.rb +63 -45
- data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -5
- metadata +43 -29
- data/examples/associations.png +0 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/rails/generators/active_record/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'active_support/
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Inheritance
|
@@ -6,14 +6,36 @@ module ActiveRecord
|
|
6
6
|
|
7
7
|
included do
|
8
8
|
# Determine whether to store the full constant name including namespace when using STI
|
9
|
-
class_attribute :store_full_sti_class
|
9
|
+
class_attribute :store_full_sti_class, instance_writer: false
|
10
10
|
self.store_full_sti_class = true
|
11
11
|
end
|
12
12
|
|
13
13
|
module ClassMethods
|
14
|
+
# Determines if one of the attributes passed in is the inheritance column,
|
15
|
+
# and if the inheritance column is attr accessible, it initializes an
|
16
|
+
# instance of the given subclass instead of the base class
|
17
|
+
def new(*args, &block)
|
18
|
+
if abstract_class? || self == Base
|
19
|
+
raise NotImplementedError, "#{self} is an abstract class and can not be instantiated."
|
20
|
+
end
|
21
|
+
|
22
|
+
attrs = args.first
|
23
|
+
if subclass_from_attributes?(attrs)
|
24
|
+
subclass = subclass_from_attributes(attrs)
|
25
|
+
end
|
26
|
+
|
27
|
+
if subclass
|
28
|
+
subclass.new(*args, &block)
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
14
34
|
# True if this isn't a concrete subclass needing a STI type condition.
|
15
35
|
def descends_from_active_record?
|
16
|
-
if
|
36
|
+
if self == Base
|
37
|
+
false
|
38
|
+
elsif superclass.abstract_class?
|
17
39
|
superclass.descends_from_active_record?
|
18
40
|
else
|
19
41
|
superclass == Base || !columns_hash.include?(inheritance_column)
|
@@ -33,17 +55,41 @@ module ActiveRecord
|
|
33
55
|
@symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
|
34
56
|
end
|
35
57
|
|
36
|
-
# Returns the
|
37
|
-
#
|
58
|
+
# Returns the class descending directly from ActiveRecord::Base, or
|
59
|
+
# an abstract class, if any, in the inheritance hierarchy.
|
60
|
+
#
|
61
|
+
# If A extends AR::Base, A.base_class will return A. If B descends from A
|
38
62
|
# through some arbitrarily deep hierarchy, B.base_class will return A.
|
39
63
|
#
|
40
64
|
# If B < A and C < B and if A is an abstract_class then both B.base_class
|
41
65
|
# and C.base_class would return B as the answer since A is an abstract_class.
|
42
66
|
def base_class
|
43
|
-
|
67
|
+
unless self < Base
|
68
|
+
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
|
69
|
+
end
|
70
|
+
|
71
|
+
if superclass == Base || superclass.abstract_class?
|
72
|
+
self
|
73
|
+
else
|
74
|
+
superclass.base_class
|
75
|
+
end
|
44
76
|
end
|
45
77
|
|
46
78
|
# Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
|
79
|
+
# If you are using inheritance with ActiveRecord and don't want child classes
|
80
|
+
# to utilize the implied STI table name of the parent class, this will need to be true.
|
81
|
+
# For example, given the following:
|
82
|
+
#
|
83
|
+
# class SuperClass < ActiveRecord::Base
|
84
|
+
# self.abstract_class = true
|
85
|
+
# end
|
86
|
+
# class Child < SuperClass
|
87
|
+
# self.table_name = 'the_table_i_really_want'
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
#
|
91
|
+
# <tt>self.abstract_class = true</tt> is required to make <tt>Child<.find,.create, or any Arel method></tt> use <tt>the_table_i_really_want</tt> instead of a table called <tt>super_classes</tt>
|
92
|
+
#
|
47
93
|
attr_accessor :abstract_class
|
48
94
|
|
49
95
|
# Returns whether this class is an abstract class or not.
|
@@ -55,36 +101,8 @@ module ActiveRecord
|
|
55
101
|
store_full_sti_class ? name : name.demodulize
|
56
102
|
end
|
57
103
|
|
58
|
-
# Finder methods must instantiate through this method to work with the
|
59
|
-
# single-table inheritance model that makes it possible to create
|
60
|
-
# objects of different types from the same table.
|
61
|
-
def instantiate(record)
|
62
|
-
sti_class = find_sti_class(record[inheritance_column])
|
63
|
-
record_id = sti_class.primary_key && record[sti_class.primary_key]
|
64
|
-
|
65
|
-
if ActiveRecord::IdentityMap.enabled? && record_id
|
66
|
-
instance = use_identity_map(sti_class, record_id, record)
|
67
|
-
else
|
68
|
-
instance = sti_class.allocate.init_with('attributes' => record)
|
69
|
-
end
|
70
|
-
|
71
|
-
instance
|
72
|
-
end
|
73
|
-
|
74
104
|
protected
|
75
105
|
|
76
|
-
# Returns the class descending directly from ActiveRecord::Base or an
|
77
|
-
# abstract class, if any, in the inheritance hierarchy.
|
78
|
-
def class_of_active_record_descendant(klass)
|
79
|
-
if klass == Base || klass.superclass == Base || klass.superclass.abstract_class?
|
80
|
-
klass
|
81
|
-
elsif klass.superclass.nil?
|
82
|
-
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
|
83
|
-
else
|
84
|
-
class_of_active_record_descendant(klass.superclass)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
106
|
# Returns the class type of the record using the current module as a prefix. So descendants of
|
89
107
|
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
|
90
108
|
def compute_type(type_name)
|
@@ -114,39 +132,33 @@ module ActiveRecord
|
|
114
132
|
|
115
133
|
private
|
116
134
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
instance.reinit_with('attributes' => record)
|
135
|
+
# Called by +instantiate+ to decide which class to use for a new
|
136
|
+
# record instance. For single-table inheritance, we check the record
|
137
|
+
# for a +type+ column and return the corresponding class.
|
138
|
+
def discriminate_class_for_record(record)
|
139
|
+
if using_single_table_inheritance?(record)
|
140
|
+
find_sti_class(record[inheritance_column])
|
124
141
|
else
|
125
|
-
|
126
|
-
IdentityMap.add(instance)
|
142
|
+
super
|
127
143
|
end
|
144
|
+
end
|
128
145
|
|
129
|
-
|
146
|
+
def using_single_table_inheritance?(record)
|
147
|
+
record[inheritance_column].present? && columns_hash.include?(inheritance_column)
|
130
148
|
end
|
131
149
|
|
132
150
|
def find_sti_class(type_name)
|
133
|
-
if
|
134
|
-
|
151
|
+
if store_full_sti_class
|
152
|
+
ActiveSupport::Dependencies.constantize(type_name)
|
135
153
|
else
|
136
|
-
|
137
|
-
if store_full_sti_class
|
138
|
-
ActiveSupport::Dependencies.constantize(type_name)
|
139
|
-
else
|
140
|
-
compute_type(type_name)
|
141
|
-
end
|
142
|
-
rescue NameError
|
143
|
-
raise SubclassNotFound,
|
144
|
-
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
|
145
|
-
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
|
146
|
-
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
|
147
|
-
"or overwrite #{name}.inheritance_column to use another column for that information."
|
148
|
-
end
|
154
|
+
compute_type(type_name)
|
149
155
|
end
|
156
|
+
rescue NameError
|
157
|
+
raise SubclassNotFound,
|
158
|
+
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
|
159
|
+
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
|
160
|
+
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
|
161
|
+
"or overwrite #{name}.inheritance_column to use another column for that information."
|
150
162
|
end
|
151
163
|
|
152
164
|
def type_condition(table = arel_table)
|
@@ -155,6 +167,28 @@ module ActiveRecord
|
|
155
167
|
|
156
168
|
sti_column.in(sti_names)
|
157
169
|
end
|
170
|
+
|
171
|
+
# Detect the subclass from the inheritance column of attrs. If the inheritance column value
|
172
|
+
# is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
|
173
|
+
# If this is a StrongParameters hash, and access to inheritance_column is not permitted,
|
174
|
+
# this will ignore the inheritance column and return nil
|
175
|
+
def subclass_from_attributes?(attrs)
|
176
|
+
columns_hash.include?(inheritance_column) && attrs.is_a?(Hash)
|
177
|
+
end
|
178
|
+
|
179
|
+
def subclass_from_attributes(attrs)
|
180
|
+
subclass_name = attrs.with_indifferent_access[inheritance_column]
|
181
|
+
|
182
|
+
if subclass_name.present? && subclass_name != self.name
|
183
|
+
subclass = subclass_name.safe_constantize
|
184
|
+
|
185
|
+
unless descendants.include?(subclass)
|
186
|
+
raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
|
187
|
+
end
|
188
|
+
|
189
|
+
subclass
|
190
|
+
end
|
191
|
+
end
|
158
192
|
end
|
159
193
|
|
160
194
|
private
|
@@ -5,10 +5,12 @@ module ActiveRecord
|
|
5
5
|
included do
|
6
6
|
##
|
7
7
|
# :singleton-method:
|
8
|
-
# Indicates the format used to generate the timestamp
|
9
|
-
#
|
8
|
+
# Indicates the format used to generate the timestamp in the cache key.
|
9
|
+
# Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
|
10
|
+
#
|
11
|
+
# This is +:nsec+, by default.
|
10
12
|
class_attribute :cache_timestamp_format, :instance_writer => false
|
11
|
-
self.cache_timestamp_format = :
|
13
|
+
self.cache_timestamp_format = :nsec
|
12
14
|
end
|
13
15
|
|
14
16
|
# Returns a String, which Action Pack uses for constructing an URL to this
|
@@ -19,7 +21,7 @@ module ActiveRecord
|
|
19
21
|
# <tt>resources :users</tt> route. Normally, +user_path+ will
|
20
22
|
# construct a path with the user object's 'id' in it:
|
21
23
|
#
|
22
|
-
# user = User.
|
24
|
+
# user = User.find_by(name: 'Phusion')
|
23
25
|
# user_path(user) # => "/users/1"
|
24
26
|
#
|
25
27
|
# You can override +to_param+ in your model to make +user_path+ construct
|
@@ -31,7 +33,7 @@ module ActiveRecord
|
|
31
33
|
# end
|
32
34
|
# end
|
33
35
|
#
|
34
|
-
# user = User.
|
36
|
+
# user = User.find_by(name: 'Phusion')
|
35
37
|
# user_path(user) # => "/users/Phusion"
|
36
38
|
def to_param
|
37
39
|
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
|
@@ -40,8 +42,6 @@ module ActiveRecord
|
|
40
42
|
|
41
43
|
# Returns a cache key that can be used to identify this record.
|
42
44
|
#
|
43
|
-
# ==== Examples
|
44
|
-
#
|
45
45
|
# Product.new.cache_key # => "products/new"
|
46
46
|
# Product.find(5).cache_key # => "products/5" (updated_at not available)
|
47
47
|
# Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
|
@@ -49,7 +49,7 @@ module ActiveRecord
|
|
49
49
|
case
|
50
50
|
when new_record?
|
51
51
|
"#{self.class.model_name.cache_key}/new"
|
52
|
-
when timestamp =
|
52
|
+
when timestamp = max_updated_column_timestamp
|
53
53
|
timestamp = timestamp.utc.to_s(cache_timestamp_format)
|
54
54
|
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
|
55
55
|
else
|
@@ -4,12 +4,19 @@ en:
|
|
4
4
|
#created_at: "Created at"
|
5
5
|
#updated_at: "Updated at"
|
6
6
|
|
7
|
+
# Default error messages
|
8
|
+
errors:
|
9
|
+
messages:
|
10
|
+
taken: "has already been taken"
|
11
|
+
|
7
12
|
# Active Record models configuration
|
8
13
|
activerecord:
|
9
14
|
errors:
|
10
15
|
messages:
|
11
|
-
taken: "has already been taken"
|
12
16
|
record_invalid: "Validation failed: %{errors}"
|
17
|
+
restrict_dependent_destroy:
|
18
|
+
one: "Cannot delete record because a dependent %{record} exists"
|
19
|
+
many: "Cannot delete record because dependent %{record} exist"
|
13
20
|
# Append your own errors here or at the model/attributes scope.
|
14
21
|
|
15
22
|
# You can define own errors for models or model attributes.
|
@@ -40,16 +40,18 @@ module ActiveRecord
|
|
40
40
|
# This locking mechanism will function inside a single Ruby process. To make it work across all
|
41
41
|
# web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
|
42
42
|
#
|
43
|
-
# You must ensure that your database schema defaults the +lock_version+ column to 0.
|
44
|
-
#
|
45
43
|
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
|
46
|
-
# To override the name of the +lock_version+ column,
|
47
|
-
#
|
44
|
+
# To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
|
45
|
+
#
|
46
|
+
# class Person < ActiveRecord::Base
|
47
|
+
# self.locking_column = :lock_person
|
48
|
+
# end
|
49
|
+
#
|
48
50
|
module Optimistic
|
49
51
|
extend ActiveSupport::Concern
|
50
52
|
|
51
53
|
included do
|
52
|
-
|
54
|
+
class_attribute :lock_optimistically, instance_writer: false
|
53
55
|
self.lock_optimistically = true
|
54
56
|
end
|
55
57
|
|
@@ -64,7 +66,7 @@ module ActiveRecord
|
|
64
66
|
send(lock_col + '=', previous_lock_value + 1)
|
65
67
|
end
|
66
68
|
|
67
|
-
def
|
69
|
+
def _update_record(attribute_names = @attributes.keys) #:nodoc:
|
68
70
|
return super unless locking_enabled?
|
69
71
|
return 0 if attribute_names.empty?
|
70
72
|
|
@@ -80,11 +82,11 @@ module ActiveRecord
|
|
80
82
|
|
81
83
|
stmt = relation.where(
|
82
84
|
relation.table[self.class.primary_key].eq(id).and(
|
83
|
-
relation.table[lock_col].eq(quote_value(previous_lock_value,
|
85
|
+
relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col)))
|
84
86
|
)
|
85
|
-
).arel.compile_update(
|
87
|
+
).arel.compile_update(arel_attributes_with_values_for_update(attribute_names))
|
86
88
|
|
87
|
-
affected_rows = connection.update stmt
|
89
|
+
affected_rows = self.class.connection.update stmt
|
88
90
|
|
89
91
|
unless affected_rows == 1
|
90
92
|
raise ActiveRecord::StaleObjectError.new(self, "update")
|
@@ -99,26 +101,29 @@ module ActiveRecord
|
|
99
101
|
end
|
100
102
|
end
|
101
103
|
|
102
|
-
def
|
103
|
-
|
104
|
+
def destroy_row
|
105
|
+
affected_rows = super
|
106
|
+
|
107
|
+
if locking_enabled? && affected_rows != 1
|
108
|
+
raise ActiveRecord::StaleObjectError.new(self, "destroy")
|
109
|
+
end
|
104
110
|
|
105
|
-
|
111
|
+
affected_rows
|
112
|
+
end
|
106
113
|
|
107
|
-
|
108
|
-
|
109
|
-
lock_col = self.class.locking_column
|
110
|
-
predicate = table[self.class.primary_key].eq(id).
|
111
|
-
and(table[lock_col].eq(send(lock_col).to_i))
|
114
|
+
def relation_for_destroy
|
115
|
+
relation = super
|
112
116
|
|
113
|
-
|
117
|
+
if locking_enabled?
|
118
|
+
column_name = self.class.locking_column
|
119
|
+
column = self.class.columns_hash[column_name]
|
120
|
+
substitute = self.class.connection.substitute_at(column, relation.bind_values.length)
|
114
121
|
|
115
|
-
|
116
|
-
|
117
|
-
end
|
122
|
+
relation = relation.where(self.class.arel_table[column_name].eq(substitute))
|
123
|
+
relation.bind_values << [column, self[column_name].to_i]
|
118
124
|
end
|
119
125
|
|
120
|
-
|
121
|
-
freeze
|
126
|
+
relation
|
122
127
|
end
|
123
128
|
|
124
129
|
module ClassMethods
|
@@ -131,14 +136,9 @@ module ActiveRecord
|
|
131
136
|
lock_optimistically && columns_hash[locking_column]
|
132
137
|
end
|
133
138
|
|
134
|
-
def locking_column=(value)
|
135
|
-
@original_locking_column = @locking_column if defined?(@locking_column)
|
136
|
-
@locking_column = value.to_s
|
137
|
-
end
|
138
|
-
|
139
139
|
# Set the column to use for optimistic locking. Defaults to +lock_version+.
|
140
|
-
def
|
141
|
-
|
140
|
+
def locking_column=(value)
|
141
|
+
@locking_column = value.to_s
|
142
142
|
end
|
143
143
|
|
144
144
|
# The version column used for optimistic locking. Defaults to +lock_version+.
|
@@ -147,10 +147,6 @@ module ActiveRecord
|
|
147
147
|
@locking_column
|
148
148
|
end
|
149
149
|
|
150
|
-
def original_locking_column #:nodoc:
|
151
|
-
deprecated_original_property_getter :locking_column
|
152
|
-
end
|
153
|
-
|
154
150
|
# Quote the column name used for optimistic locking.
|
155
151
|
def quoted_locking_column
|
156
152
|
connection.quote_column_name(locking_column)
|
@@ -168,16 +164,16 @@ module ActiveRecord
|
|
168
164
|
super
|
169
165
|
end
|
170
166
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
end
|
167
|
+
def column_defaults
|
168
|
+
@column_defaults ||= begin
|
169
|
+
defaults = super
|
170
|
+
|
171
|
+
if defaults.key?(locking_column) && lock_optimistically
|
172
|
+
defaults[locking_column] ||= 0
|
173
|
+
end
|
179
174
|
|
180
|
-
|
175
|
+
defaults
|
176
|
+
end
|
181
177
|
end
|
182
178
|
end
|
183
179
|
end
|
@@ -3,12 +3,12 @@ module ActiveRecord
|
|
3
3
|
# Locking::Pessimistic provides support for row-level locking using
|
4
4
|
# SELECT ... FOR UPDATE and other lock types.
|
5
5
|
#
|
6
|
-
#
|
6
|
+
# Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
|
7
7
|
# lock on the selected rows:
|
8
8
|
# # select * from accounts where id=1 for update
|
9
|
-
# Account.find(1
|
9
|
+
# Account.lock.find(1)
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# Call <tt>lock('some locking clause')</tt> to use a database-specific locking clause
|
12
12
|
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
|
13
13
|
#
|
14
14
|
# Account.transaction do
|
@@ -26,7 +26,7 @@ module ActiveRecord
|
|
26
26
|
#
|
27
27
|
# Account.transaction do
|
28
28
|
# # select * from accounts where ...
|
29
|
-
# accounts = Account.where(...)
|
29
|
+
# accounts = Account.where(...)
|
30
30
|
# account1 = accounts.detect { |account| ... }
|
31
31
|
# account2 = accounts.detect { |account| ... }
|
32
32
|
# # select * from accounts where id=? for update
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
|
4
|
+
|
3
5
|
def self.runtime=(value)
|
4
|
-
|
6
|
+
ActiveRecord::RuntimeRegistry.sql_runtime = value
|
5
7
|
end
|
6
8
|
|
7
9
|
def self.runtime
|
8
|
-
|
10
|
+
ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
|
9
11
|
end
|
10
12
|
|
11
13
|
def self.reset_runtime
|
@@ -18,25 +20,33 @@ module ActiveRecord
|
|
18
20
|
@odd_or_even = false
|
19
21
|
end
|
20
22
|
|
23
|
+
def render_bind(column, value)
|
24
|
+
if column
|
25
|
+
if column.binary?
|
26
|
+
value = "<#{value.bytesize} bytes of binary data>"
|
27
|
+
end
|
28
|
+
|
29
|
+
[column.name, value]
|
30
|
+
else
|
31
|
+
[nil, value]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
21
35
|
def sql(event)
|
22
36
|
self.class.runtime += event.duration
|
23
37
|
return unless logger.debug?
|
24
38
|
|
25
39
|
payload = event.payload
|
26
40
|
|
27
|
-
return if
|
41
|
+
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
28
42
|
|
29
|
-
name =
|
43
|
+
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
30
44
|
sql = payload[:sql].squeeze(' ')
|
31
45
|
binds = nil
|
32
46
|
|
33
47
|
unless (payload[:binds] || []).empty?
|
34
48
|
binds = " " + payload[:binds].map { |col,v|
|
35
|
-
|
36
|
-
[col.name, v]
|
37
|
-
else
|
38
|
-
[nil, v]
|
39
|
-
end
|
49
|
+
render_bind(col, v)
|
40
50
|
}.inspect
|
41
51
|
end
|
42
52
|
|