activerecord 4.0.4 → 4.1.16
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 +1632 -1797
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +4 -0
- data/lib/active_record/associations/alias_tracker.rb +49 -29
- data/lib/active_record/associations/association.rb +9 -17
- data/lib/active_record/associations/association_scope.rb +59 -49
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
- data/lib/active_record/associations/builder/association.rb +84 -54
- data/lib/active_record/associations/builder/belongs_to.rb +90 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
- data/lib/active_record/associations/builder/has_many.rb +3 -3
- data/lib/active_record/associations/builder/has_one.rb +5 -7
- data/lib/active_record/associations/builder/singular_association.rb +6 -7
- data/lib/active_record/associations/collection_association.rb +121 -111
- data/lib/active_record/associations/collection_proxy.rb +73 -18
- data/lib/active_record/associations/has_many_association.rb +14 -11
- data/lib/active_record/associations/has_many_through_association.rb +33 -6
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
- data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
- data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
- data/lib/active_record/associations/join_dependency.rb +208 -168
- data/lib/active_record/associations/preloader/association.rb +69 -27
- data/lib/active_record/associations/preloader/collection_association.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +58 -26
- data/lib/active_record/associations/preloader.rb +63 -49
- data/lib/active_record/associations/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +30 -9
- data/lib/active_record/associations.rb +116 -42
- data/lib/active_record/attribute_assignment.rb +6 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +35 -26
- data/lib/active_record/attribute_methods/primary_key.rb +8 -1
- data/lib/active_record/attribute_methods/read.rb +56 -29
- data/lib/active_record/attribute_methods/serialization.rb +44 -12
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
- data/lib/active_record/attribute_methods/write.rb +59 -26
- data/lib/active_record/attribute_methods.rb +82 -43
- data/lib/active_record/autosave_association.rb +209 -194
- data/lib/active_record/base.rb +6 -2
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
- data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
- data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
- data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
- data/lib/active_record/connection_handling.rb +39 -5
- data/lib/active_record/core.rb +38 -54
- data/lib/active_record/counter_cache.rb +9 -10
- data/lib/active_record/dynamic_matchers.rb +6 -2
- data/lib/active_record/enum.rb +199 -0
- data/lib/active_record/errors.rb +22 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +173 -76
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +23 -9
- data/lib/active_record/integration.rb +54 -1
- data/lib/active_record/locking/optimistic.rb +7 -2
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +6 -13
- data/lib/active_record/migration/command_recorder.rb +8 -2
- data/lib/active_record/migration.rb +91 -56
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +25 -13
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +26 -6
- data/lib/active_record/persistence.rb +23 -29
- data/lib/active_record/querying.rb +15 -12
- data/lib/active_record/railtie.rb +12 -61
- data/lib/active_record/railties/databases.rake +37 -56
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +230 -79
- data/lib/active_record/relation/batches.rb +74 -24
- data/lib/active_record/relation/calculations.rb +52 -48
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +210 -67
- data/lib/active_record/relation/merger.rb +15 -12
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder.rb +81 -40
- data/lib/active_record/relation/query_methods.rb +185 -108
- data/lib/active_record/relation/spawn_methods.rb +8 -5
- data/lib/active_record/relation.rb +79 -84
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +4 -4
- data/lib/active_record/schema_dumper.rb +18 -6
- data/lib/active_record/schema_migration.rb +31 -18
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +14 -29
- data/lib/active_record/scoping.rb +5 -0
- data/lib/active_record/store.rb +67 -18
- data/lib/active_record/tasks/database_tasks.rb +66 -26
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +6 -6
- data/lib/active_record/transactions.rb +10 -12
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +19 -9
- data/lib/active_record/version.rb +4 -7
- data/lib/active_record.rb +5 -7
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- data/lib/rails/generators/active_record.rb +2 -8
- metadata +18 -30
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- data/lib/active_record/associations/join_helper.rb +0 -45
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
- data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
- data/lib/active_record/test_case.rb +0 -96
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'active_support/core_ext/object/deep_dup'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
# Declare an enum attribute where the values map to integers in the database,
|
5
|
+
# but can be queried by name. Example:
|
6
|
+
#
|
7
|
+
# class Conversation < ActiveRecord::Base
|
8
|
+
# enum status: [ :active, :archived ]
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# # conversation.update! status: 0
|
12
|
+
# conversation.active!
|
13
|
+
# conversation.active? # => true
|
14
|
+
# conversation.status # => "active"
|
15
|
+
#
|
16
|
+
# # conversation.update! status: 1
|
17
|
+
# conversation.archived!
|
18
|
+
# conversation.archived? # => true
|
19
|
+
# conversation.status # => "archived"
|
20
|
+
#
|
21
|
+
# # conversation.update! status: 1
|
22
|
+
# conversation.status = "archived"
|
23
|
+
#
|
24
|
+
# # conversation.update! status: nil
|
25
|
+
# conversation.status = nil
|
26
|
+
# conversation.status.nil? # => true
|
27
|
+
# conversation.status # => nil
|
28
|
+
#
|
29
|
+
# Scopes based on the allowed values of the enum field will be provided
|
30
|
+
# as well. With the above example, it will create an +active+ and +archived+
|
31
|
+
# scope.
|
32
|
+
#
|
33
|
+
# You can set the default value from the database declaration, like:
|
34
|
+
#
|
35
|
+
# create_table :conversations do |t|
|
36
|
+
# t.column :status, :integer, default: 0
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# Good practice is to let the first declared status be the default.
|
40
|
+
#
|
41
|
+
# Finally, it's also possible to explicitly map the relation between attribute and
|
42
|
+
# database integer with a +Hash+:
|
43
|
+
#
|
44
|
+
# class Conversation < ActiveRecord::Base
|
45
|
+
# enum status: { active: 0, archived: 1 }
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# Note that when an +Array+ is used, the implicit mapping from the values to database
|
49
|
+
# integers is derived from the order the values appear in the array. In the example,
|
50
|
+
# <tt>:active</tt> is mapped to +0+ as it's the first element, and <tt>:archived</tt>
|
51
|
+
# is mapped to +1+. In general, the +i+-th element is mapped to <tt>i-1</tt> in the
|
52
|
+
# database.
|
53
|
+
#
|
54
|
+
# Therefore, once a value is added to the enum array, its position in the array must
|
55
|
+
# be maintained, and new values should only be added to the end of the array. To
|
56
|
+
# remove unused values, the explicit +Hash+ syntax should be used.
|
57
|
+
#
|
58
|
+
# In rare circumstances you might need to access the mapping directly.
|
59
|
+
# The mappings are exposed through a class method with the pluralized attribute
|
60
|
+
# name:
|
61
|
+
#
|
62
|
+
# Conversation.statuses # => { "active" => 0, "archived" => 1 }
|
63
|
+
#
|
64
|
+
# Use that class method when you need to know the ordinal value of an enum:
|
65
|
+
#
|
66
|
+
# Conversation.where("status <> ?", Conversation.statuses[:archived])
|
67
|
+
#
|
68
|
+
# Where conditions on an enum attribute must use the ordinal value of an enum.
|
69
|
+
module Enum
|
70
|
+
def self.extended(base) # :nodoc:
|
71
|
+
base.class_attribute(:defined_enums, instance_writer: false)
|
72
|
+
base.defined_enums = {}
|
73
|
+
end
|
74
|
+
|
75
|
+
def inherited(base) # :nodoc:
|
76
|
+
base.defined_enums = defined_enums.deep_dup
|
77
|
+
super
|
78
|
+
end
|
79
|
+
|
80
|
+
def enum(definitions)
|
81
|
+
klass = self
|
82
|
+
definitions.each do |name, values|
|
83
|
+
# statuses = { }
|
84
|
+
enum_values = ActiveSupport::HashWithIndifferentAccess.new
|
85
|
+
name = name.to_sym
|
86
|
+
|
87
|
+
# def self.statuses statuses end
|
88
|
+
detect_enum_conflict!(name, name.to_s.pluralize, true)
|
89
|
+
klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values }
|
90
|
+
|
91
|
+
_enum_methods_module.module_eval do
|
92
|
+
# def status=(value) self[:status] = statuses[value] end
|
93
|
+
klass.send(:detect_enum_conflict!, name, "#{name}=")
|
94
|
+
define_method("#{name}=") { |value|
|
95
|
+
if enum_values.has_key?(value) || value.blank?
|
96
|
+
self[name] = enum_values[value]
|
97
|
+
elsif enum_values.has_value?(value)
|
98
|
+
# Assigning a value directly is not a end-user feature, hence it's not documented.
|
99
|
+
# This is used internally to make building objects from the generated scopes work
|
100
|
+
# as expected, i.e. +Conversation.archived.build.archived?+ should be true.
|
101
|
+
self[name] = value
|
102
|
+
else
|
103
|
+
raise ArgumentError, "'#{value}' is not a valid #{name}"
|
104
|
+
end
|
105
|
+
}
|
106
|
+
|
107
|
+
# def status() statuses.key self[:status] end
|
108
|
+
klass.send(:detect_enum_conflict!, name, name)
|
109
|
+
define_method(name) { enum_values.key self[name] }
|
110
|
+
|
111
|
+
# def status_before_type_cast() statuses.key self[:status] end
|
112
|
+
klass.send(:detect_enum_conflict!, name, "#{name}_before_type_cast")
|
113
|
+
define_method("#{name}_before_type_cast") { enum_values.key self[name] }
|
114
|
+
|
115
|
+
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
|
116
|
+
pairs.each do |value, i|
|
117
|
+
enum_values[value] = i
|
118
|
+
|
119
|
+
# def active?() status == 0 end
|
120
|
+
klass.send(:detect_enum_conflict!, name, "#{value}?")
|
121
|
+
define_method("#{value}?") { self[name] == i }
|
122
|
+
|
123
|
+
# def active!() update! status: :active end
|
124
|
+
klass.send(:detect_enum_conflict!, name, "#{value}!")
|
125
|
+
define_method("#{value}!") { update! name => value }
|
126
|
+
|
127
|
+
# scope :active, -> { where status: 0 }
|
128
|
+
klass.send(:detect_enum_conflict!, name, value, true)
|
129
|
+
klass.scope value, -> { klass.where name => i }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
defined_enums[name.to_s] = enum_values
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
def _enum_methods_module
|
138
|
+
@_enum_methods_module ||= begin
|
139
|
+
mod = Module.new do
|
140
|
+
private
|
141
|
+
def save_changed_attribute(attr_name, value)
|
142
|
+
if (mapping = self.class.defined_enums[attr_name.to_s])
|
143
|
+
if attribute_changed?(attr_name)
|
144
|
+
old = changed_attributes[attr_name]
|
145
|
+
|
146
|
+
if mapping[old] == value
|
147
|
+
changed_attributes.delete(attr_name)
|
148
|
+
end
|
149
|
+
else
|
150
|
+
old = clone_attribute_value(:read_attribute, attr_name)
|
151
|
+
|
152
|
+
if old != value
|
153
|
+
changed_attributes[attr_name] = mapping.key old
|
154
|
+
end
|
155
|
+
end
|
156
|
+
else
|
157
|
+
super
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
include mod
|
162
|
+
mod
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
ENUM_CONFLICT_MESSAGE = \
|
167
|
+
"You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
|
168
|
+
"this will generate a %{type} method \"%{method}\", which is already defined " \
|
169
|
+
"by %{source}."
|
170
|
+
|
171
|
+
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
|
172
|
+
if klass_method && dangerous_class_method?(method_name)
|
173
|
+
raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
|
174
|
+
enum: enum_name,
|
175
|
+
klass: self.name,
|
176
|
+
type: 'class',
|
177
|
+
method: method_name,
|
178
|
+
source: 'Active Record'
|
179
|
+
}
|
180
|
+
elsif !klass_method && dangerous_attribute_method?(method_name)
|
181
|
+
raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
|
182
|
+
enum: enum_name,
|
183
|
+
klass: self.name,
|
184
|
+
type: 'instance',
|
185
|
+
method: method_name,
|
186
|
+
source: 'Active Record'
|
187
|
+
}
|
188
|
+
elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
|
189
|
+
raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
|
190
|
+
enum: enum_name,
|
191
|
+
klass: self.name,
|
192
|
+
type: 'instance',
|
193
|
+
method: method_name,
|
194
|
+
source: 'another enum'
|
195
|
+
}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
data/lib/active_record/errors.rb
CHANGED
@@ -69,10 +69,6 @@ module ActiveRecord
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
-
# Raised when SQL statement is invalid and the application gets a blank result.
|
73
|
-
class ThrowResult < ActiveRecordError
|
74
|
-
end
|
75
|
-
|
76
72
|
# Defunct wrapper class kept for compatibility.
|
77
73
|
# +StatementInvalid+ wraps the original exception now.
|
78
74
|
class WrappedDatabaseException < StatementInvalid
|
@@ -98,6 +94,18 @@ module ActiveRecord
|
|
98
94
|
class PreparedStatementInvalid < ActiveRecordError
|
99
95
|
end
|
100
96
|
|
97
|
+
# Raised when a given database does not exist
|
98
|
+
class NoDatabaseError < ActiveRecordError
|
99
|
+
def initialize(message)
|
100
|
+
super extend_message(message)
|
101
|
+
end
|
102
|
+
|
103
|
+
# can be over written to add additional error information.
|
104
|
+
def extend_message(message)
|
105
|
+
message
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
101
109
|
# Raised on attempt to save stale record. Record is stale when it's being saved in another query after
|
102
110
|
# instantiation, for example, when two users edit the same wiki page and one starts editing and saves
|
103
111
|
# the page before the other.
|
@@ -159,6 +167,15 @@ module ActiveRecord
|
|
159
167
|
|
160
168
|
# Raised when unknown attributes are supplied via mass assignment.
|
161
169
|
class UnknownAttributeError < NoMethodError
|
170
|
+
|
171
|
+
attr_reader :record, :attribute
|
172
|
+
|
173
|
+
def initialize(record, attribute)
|
174
|
+
@record = record
|
175
|
+
@attribute = attribute.to_s
|
176
|
+
super("unknown attribute: #{attribute}")
|
177
|
+
end
|
178
|
+
|
162
179
|
end
|
163
180
|
|
164
181
|
# Raised when an error occurred while doing a mass assignment to an attribute through the
|
@@ -183,7 +200,7 @@ module ActiveRecord
|
|
183
200
|
end
|
184
201
|
end
|
185
202
|
|
186
|
-
# Raised when a primary key is needed, but
|
203
|
+
# Raised when a primary key is needed, but not specified in the schema or model.
|
187
204
|
class UnknownPrimaryKey < ActiveRecordError
|
188
205
|
attr_reader :model
|
189
206
|
|