activerecord 3.1.10 → 4.2.11
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.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +6 -6
- data/CHANGELOG.md +1837 -338
- data/MIT-LICENSE +1 -1
- data/README.rdoc +39 -43
- data/examples/performance.rb +51 -20
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +57 -43
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -39
- data/lib/active_record/associations/association.rb +71 -85
- data/lib/active_record/associations/association_scope.rb +138 -89
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
- data/lib/active_record/associations/builder/association.rb +125 -29
- data/lib/active_record/associations/builder/belongs_to.rb +91 -60
- data/lib/active_record/associations/builder/collection_association.rb +69 -49
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +12 -52
- data/lib/active_record/associations/builder/singular_association.rb +22 -29
- data/lib/active_record/associations/collection_association.rb +294 -187
- data/lib/active_record/associations/collection_proxy.rb +961 -94
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +118 -23
- data/lib/active_record/associations/has_many_through_association.rb +115 -45
- data/lib/active_record/associations/has_one_association.rb +57 -24
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +230 -156
- data/lib/active_record/associations/preloader/association.rb +96 -55
- data/lib/active_record/associations/preloader/collection_association.rb +3 -3
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +61 -32
- data/lib/active_record/associations/preloader.rb +113 -87
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +37 -19
- data/lib/active_record/associations.rb +505 -371
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +212 -0
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +141 -51
- data/lib/active_record/attribute_methods/primary_key.rb +87 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +74 -117
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
- data/lib/active_record/attribute_methods/write.rb +60 -21
- data/lib/active_record/attribute_methods.rb +409 -48
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +279 -232
- data/lib/active_record/base.rb +84 -1969
- data/lib/active_record/callbacks.rb +66 -28
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +422 -243
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +273 -170
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
- data/lib/active_record/connection_adapters/column.rb +33 -221
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
- data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +445 -902
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +159 -102
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +102 -34
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +318 -260
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +247 -0
- data/lib/active_record/integration.rb +113 -0
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +80 -52
- data/lib/active_record/locking/pessimistic.rb +27 -5
- data/lib/active_record/log_subscriber.rb +25 -18
- data/lib/active_record/migration/command_recorder.rb +130 -38
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +532 -201
- data/lib/active_record/model_schema.rb +342 -0
- data/lib/active_record/nested_attributes.rb +229 -139
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +304 -99
- data/lib/active_record/query_cache.rb +25 -43
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +86 -45
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +7 -4
- data/lib/active_record/railties/databases.rake +198 -377
- data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +516 -165
- data/lib/active_record/relation/batches.rb +96 -45
- data/lib/active_record/relation/calculations.rb +221 -144
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +362 -243
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +135 -41
- data/lib/active_record/relation/query_methods.rb +982 -155
- data/lib/active_record/relation/spawn_methods.rb +50 -110
- data/lib/active_record/relation.rb +371 -180
- data/lib/active_record/result.rb +109 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +111 -61
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +135 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/serialization.rb +7 -45
- data/lib/active_record/serializers/xml_serializer.rb +14 -65
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +205 -0
- data/lib/active_record/tasks/database_tasks.rb +299 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +35 -14
- data/lib/active_record/transactions.rb +141 -74
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +27 -18
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +125 -66
- data/lib/active_record/validations.rb +37 -30
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +80 -25
- data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +132 -53
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
- data/lib/active_record/dynamic_finder_match.rb +0 -56
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/identity_map.rb +0 -163
- data/lib/active_record/named_scope.rb +0 -200
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -358
- data/lib/active_record/test_case.rb +0 -69
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
- 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 -16
@@ -1,42 +1,45 @@
|
|
1
|
-
require 'active_support/core_ext/array/wrap'
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
module Validations
|
5
|
-
class UniquenessValidator < ActiveModel::EachValidator
|
3
|
+
class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
|
6
4
|
def initialize(options)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@klass =
|
5
|
+
if options[:conditions] && !options[:conditions].respond_to?(:call)
|
6
|
+
raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \
|
7
|
+
"Pass a callable instead: `conditions: -> { where(approved: true) }`"
|
8
|
+
end
|
9
|
+
super({ case_sensitive: true }.merge!(options))
|
10
|
+
@klass = options[:class]
|
13
11
|
end
|
14
12
|
|
15
13
|
def validate_each(record, attribute, value)
|
16
14
|
finder_class = find_finder_class_for(record)
|
17
15
|
table = finder_class.arel_table
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
value = map_enum_attribute(finder_class, attribute, value)
|
17
|
+
|
18
|
+
begin
|
19
|
+
relation = build_relation(finder_class, table, attribute, value)
|
20
|
+
if record.persisted?
|
21
|
+
if finder_class.primary_key
|
22
|
+
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id))
|
23
|
+
else
|
24
|
+
raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
relation = scope_relation(record, table, relation)
|
28
|
+
relation = finder_class.unscoped.where(relation)
|
29
|
+
relation = relation.merge(options[:conditions]) if options[:conditions]
|
30
|
+
rescue RangeError
|
31
|
+
relation = finder_class.none
|
23
32
|
end
|
24
33
|
|
25
|
-
relation
|
26
|
-
|
27
|
-
|
28
|
-
Array.wrap(options[:scope]).each do |scope_item|
|
29
|
-
scope_value = record.send(scope_item)
|
30
|
-
relation = relation.and(table[scope_item].eq(scope_value))
|
31
|
-
end
|
34
|
+
if relation.exists?
|
35
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
36
|
+
error_options[:value] = value
|
32
37
|
|
33
|
-
|
34
|
-
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
|
38
|
+
record.errors.add(attribute, :taken, error_options)
|
35
39
|
end
|
36
40
|
end
|
37
41
|
|
38
42
|
protected
|
39
|
-
|
40
43
|
# The check for an existing value should be run from a class that
|
41
44
|
# isn't abstract. This means working down from the current class
|
42
45
|
# (self), to the first non-abstract class. Since classes don't know
|
@@ -46,67 +49,123 @@ module ActiveRecord
|
|
46
49
|
class_hierarchy = [record.class]
|
47
50
|
|
48
51
|
while class_hierarchy.first != @klass
|
49
|
-
class_hierarchy.
|
52
|
+
class_hierarchy.unshift(class_hierarchy.first.superclass)
|
50
53
|
end
|
51
54
|
|
52
55
|
class_hierarchy.detect { |klass| !klass.abstract_class? }
|
53
56
|
end
|
54
57
|
|
55
58
|
def build_relation(klass, table, attribute, value) #:nodoc:
|
56
|
-
|
57
|
-
|
59
|
+
if reflection = klass._reflect_on_association(attribute)
|
60
|
+
attribute = reflection.foreign_key
|
61
|
+
value = value.attributes[reflection.klass.primary_key] unless value.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
attribute_name = attribute.to_s
|
65
|
+
|
66
|
+
# the attribute may be an aliased attribute
|
67
|
+
if klass.attribute_aliases[attribute_name]
|
68
|
+
attribute = klass.attribute_aliases[attribute_name]
|
69
|
+
attribute_name = attribute.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
column = klass.columns_hash[attribute_name]
|
73
|
+
value = klass.connection.type_cast(value, column)
|
74
|
+
if value.is_a?(String) && column.limit
|
75
|
+
value = value.to_s[0, column.limit]
|
76
|
+
end
|
58
77
|
|
59
78
|
if !options[:case_sensitive] && value && column.text?
|
60
|
-
# will use SQL LOWER function before comparison
|
61
|
-
|
79
|
+
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
|
80
|
+
klass.connection.case_insensitive_comparison(table, attribute, column, value)
|
62
81
|
else
|
63
|
-
|
64
|
-
|
82
|
+
klass.connection.case_sensitive_comparison(table, attribute, column, value)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def scope_relation(record, table, relation)
|
87
|
+
Array(options[:scope]).each do |scope_item|
|
88
|
+
if reflection = record.class._reflect_on_association(scope_item)
|
89
|
+
scope_value = record.send(reflection.foreign_key)
|
90
|
+
scope_item = reflection.foreign_key
|
91
|
+
else
|
92
|
+
scope_value = record._read_attribute(scope_item)
|
93
|
+
end
|
94
|
+
relation = relation.and(table[scope_item].eq(scope_value))
|
65
95
|
end
|
66
96
|
|
67
97
|
relation
|
68
98
|
end
|
99
|
+
|
100
|
+
def map_enum_attribute(klass, attribute, value)
|
101
|
+
mapping = klass.defined_enums[attribute.to_s]
|
102
|
+
value = mapping[value] if value && mapping
|
103
|
+
value
|
104
|
+
end
|
69
105
|
end
|
70
106
|
|
71
107
|
module ClassMethods
|
72
|
-
# Validates whether the value of the specified attributes are unique
|
73
|
-
# Useful for making sure that only one user
|
108
|
+
# Validates whether the value of the specified attributes are unique
|
109
|
+
# across the system. Useful for making sure that only one user
|
74
110
|
# can be named "davidhh".
|
75
111
|
#
|
76
112
|
# class Person < ActiveRecord::Base
|
77
113
|
# validates_uniqueness_of :user_name
|
78
114
|
# end
|
79
115
|
#
|
80
|
-
# It can also validate whether the value of the specified attributes are
|
116
|
+
# It can also validate whether the value of the specified attributes are
|
117
|
+
# unique based on a <tt>:scope</tt> parameter:
|
81
118
|
#
|
82
119
|
# class Person < ActiveRecord::Base
|
83
|
-
# validates_uniqueness_of :user_name, :
|
84
|
-
# end
|
120
|
+
# validates_uniqueness_of :user_name, scope: :account_id
|
121
|
+
# end
|
85
122
|
#
|
86
|
-
# Or even multiple scope parameters.
|
87
|
-
# per semester for a particular
|
123
|
+
# Or even multiple scope parameters. For example, making sure that a
|
124
|
+
# teacher can only be on the schedule once per semester for a particular
|
125
|
+
# class.
|
88
126
|
#
|
89
127
|
# class TeacherSchedule < ActiveRecord::Base
|
90
|
-
# validates_uniqueness_of :teacher_id, :
|
128
|
+
# validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
|
91
129
|
# end
|
92
130
|
#
|
93
|
-
#
|
94
|
-
#
|
131
|
+
# It is also possible to limit the uniqueness constraint to a set of
|
132
|
+
# records matching certain conditions. In this example archived articles
|
133
|
+
# are not being taken into consideration when validating uniqueness
|
134
|
+
# of the title attribute:
|
135
|
+
#
|
136
|
+
# class Article < ActiveRecord::Base
|
137
|
+
# validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') }
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# When the record is created, a check is performed to make sure that no
|
141
|
+
# record exists in the database with the given value for the specified
|
142
|
+
# attribute (that maps to a column). When the record is updated,
|
95
143
|
# the same check is made but disregarding the record itself.
|
96
144
|
#
|
97
145
|
# Configuration options:
|
98
|
-
#
|
99
|
-
# * <tt>:
|
100
|
-
#
|
101
|
-
# * <tt>:
|
102
|
-
#
|
103
|
-
# * <tt>:
|
104
|
-
#
|
105
|
-
#
|
106
|
-
# * <tt>:
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
146
|
+
#
|
147
|
+
# * <tt>:message</tt> - Specifies a custom error message (default is:
|
148
|
+
# "has already been taken").
|
149
|
+
# * <tt>:scope</tt> - One or more columns by which to limit the scope of
|
150
|
+
# the uniqueness constraint.
|
151
|
+
# * <tt>:conditions</tt> - Specify the conditions to be included as a
|
152
|
+
# <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
|
153
|
+
# (e.g. <tt>conditions: -> { where(status: 'active') }</tt>).
|
154
|
+
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
|
155
|
+
# non-text columns (+true+ by default).
|
156
|
+
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
|
157
|
+
# attribute is +nil+ (default is +false+).
|
158
|
+
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
|
159
|
+
# attribute is blank (default is +false+).
|
160
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
161
|
+
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
162
|
+
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
|
163
|
+
# proc or string should return or evaluate to a +true+ or +false+ value.
|
164
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
|
165
|
+
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
|
166
|
+
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
167
|
+
# method, proc or string should return or evaluate to a +true+ or +false+
|
168
|
+
# value.
|
110
169
|
#
|
111
170
|
# === Concurrency and integrity
|
112
171
|
#
|
@@ -126,11 +185,11 @@ module ActiveRecord
|
|
126
185
|
# WHERE title = 'My Post' |
|
127
186
|
# |
|
128
187
|
# | # User 2 does the same thing and also
|
129
|
-
# | # infers that
|
188
|
+
# | # infers that their title is unique.
|
130
189
|
# | SELECT * FROM comments
|
131
190
|
# | WHERE title = 'My Post'
|
132
191
|
# |
|
133
|
-
# # User 1 inserts
|
192
|
+
# # User 1 inserts their comment. |
|
134
193
|
# INSERT INTO comments |
|
135
194
|
# (title, content) VALUES |
|
136
195
|
# ('My Post', 'hi!') |
|
@@ -156,22 +215,22 @@ module ActiveRecord
|
|
156
215
|
# exception. You can either choose to let this error propagate (which
|
157
216
|
# will result in the default Rails exception page being shown), or you
|
158
217
|
# can catch it and restart the transaction (e.g. by telling the user
|
159
|
-
# that the title already exists, and asking
|
160
|
-
# This technique is also known as
|
161
|
-
# http://en.wikipedia.org/wiki/Optimistic_concurrency_control
|
218
|
+
# that the title already exists, and asking them to re-enter the title).
|
219
|
+
# This technique is also known as
|
220
|
+
# {optimistic concurrency control}[http://en.wikipedia.org/wiki/Optimistic_concurrency_control].
|
162
221
|
#
|
163
222
|
# The bundled ActiveRecord::ConnectionAdapters distinguish unique index
|
164
223
|
# constraint errors from other types of database errors by throwing an
|
165
|
-
# ActiveRecord::RecordNotUnique exception.
|
166
|
-
#
|
167
|
-
#
|
224
|
+
# ActiveRecord::RecordNotUnique exception. For other adapters you will
|
225
|
+
# have to parse the (database-specific) exception message to detect such
|
226
|
+
# a case.
|
227
|
+
#
|
168
228
|
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
|
169
|
-
# * ActiveRecord::ConnectionAdapters::MysqlAdapter
|
170
|
-
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter
|
171
|
-
# * ActiveRecord::ConnectionAdapters::SQLiteAdapter
|
172
|
-
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter
|
173
|
-
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
174
229
|
#
|
230
|
+
# * ActiveRecord::ConnectionAdapters::MysqlAdapter.
|
231
|
+
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter.
|
232
|
+
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter.
|
233
|
+
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.
|
175
234
|
def validates_uniqueness_of(*attr_names)
|
176
235
|
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
177
236
|
end
|
@@ -1,20 +1,21 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
# = Active Record RecordInvalid
|
3
3
|
#
|
4
|
-
# Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid.
|
4
|
+
# Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
|
5
5
|
# +record+ method to retrieve the record which did not validate.
|
6
6
|
#
|
7
7
|
# begin
|
8
|
-
#
|
8
|
+
# complex_operation_that_internally_calls_save!
|
9
9
|
# rescue ActiveRecord::RecordInvalid => invalid
|
10
10
|
# puts invalid.record.errors
|
11
11
|
# end
|
12
12
|
class RecordInvalid < ActiveRecordError
|
13
13
|
attr_reader :record
|
14
|
+
|
14
15
|
def initialize(record)
|
15
16
|
@record = record
|
16
17
|
errors = @record.errors.full_messages.join(", ")
|
17
|
-
super(I18n.t("
|
18
|
+
super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid"))
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
@@ -29,38 +30,26 @@ module ActiveRecord
|
|
29
30
|
extend ActiveSupport::Concern
|
30
31
|
include ActiveModel::Validations
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
def create!(attributes = nil, options = {}, &block)
|
36
|
-
if attributes.is_a?(Array)
|
37
|
-
attributes.collect { |attr| create!(attr, options, &block) }
|
38
|
-
else
|
39
|
-
object = new(attributes, options)
|
40
|
-
yield(object) if block_given?
|
41
|
-
object.save!
|
42
|
-
object
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# The validation process on save can be skipped by passing <tt>:validate => false</tt>. The regular Base#save method is
|
48
|
-
# replaced with this when the validations module is mixed in, which it is by default.
|
33
|
+
# The validation process on save can be skipped by passing <tt>validate: false</tt>.
|
34
|
+
# The regular Base#save method is replaced with this when the validations
|
35
|
+
# module is mixed in, which it is by default.
|
49
36
|
def save(options={})
|
50
37
|
perform_validations(options) ? super : false
|
51
38
|
end
|
52
39
|
|
53
|
-
# Attempts to save the record just like Base#save but will raise a +RecordInvalid+
|
54
|
-
# if the record is not valid.
|
40
|
+
# Attempts to save the record just like Base#save but will raise a +RecordInvalid+
|
41
|
+
# exception instead of returning +false+ if the record is not valid.
|
55
42
|
def save!(options={})
|
56
|
-
perform_validations(options) ? super :
|
43
|
+
perform_validations(options) ? super : raise_record_invalid
|
57
44
|
end
|
58
45
|
|
59
|
-
# Runs all the validations within the specified context. Returns true if
|
60
|
-
# false otherwise.
|
46
|
+
# Runs all the validations within the specified context. Returns +true+ if
|
47
|
+
# no errors are found, +false+ otherwise.
|
48
|
+
#
|
49
|
+
# Aliased as validate.
|
61
50
|
#
|
62
|
-
# If the argument is false (default is +nil+), the context is set to <tt>:create</tt> if
|
63
|
-
# <tt>new_record?</tt> is true
|
51
|
+
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
|
52
|
+
# <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
|
64
53
|
#
|
65
54
|
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
|
66
55
|
# some <tt>:on</tt> option will only run in the specified context.
|
@@ -70,14 +59,32 @@ module ActiveRecord
|
|
70
59
|
errors.empty? && output
|
71
60
|
end
|
72
61
|
|
62
|
+
alias_method :validate, :valid?
|
63
|
+
|
64
|
+
# Runs all the validations within the specified context. Returns +true+ if
|
65
|
+
# no errors are found, raises +RecordInvalid+ otherwise.
|
66
|
+
#
|
67
|
+
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
|
68
|
+
# <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
|
69
|
+
#
|
70
|
+
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
|
71
|
+
# some <tt>:on</tt> option will only run in the specified context.
|
72
|
+
def validate!(context = nil)
|
73
|
+
valid?(context) || raise_record_invalid
|
74
|
+
end
|
75
|
+
|
73
76
|
protected
|
74
77
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
+
def raise_record_invalid
|
79
|
+
raise(RecordInvalid.new(self))
|
80
|
+
end
|
81
|
+
|
82
|
+
def perform_validations(options={}) # :nodoc:
|
83
|
+
options[:validate] == false || valid?(options[:context])
|
78
84
|
end
|
79
85
|
end
|
80
86
|
end
|
81
87
|
|
82
88
|
require "active_record/validations/associated"
|
83
89
|
require "active_record/validations/uniqueness"
|
90
|
+
require "active_record/validations/presence"
|
@@ -1,10 +1,8 @@
|
|
1
|
-
|
2
|
-
module VERSION #:nodoc:
|
3
|
-
MAJOR = 3
|
4
|
-
MINOR = 1
|
5
|
-
TINY = 10
|
6
|
-
PRE = nil
|
1
|
+
require_relative 'gem_version'
|
7
2
|
|
8
|
-
|
3
|
+
module ActiveRecord
|
4
|
+
# Returns the version of the currently loaded ActiveRecord as a <tt>Gem::Version</tt>
|
5
|
+
def self.version
|
6
|
+
gem_version
|
9
7
|
end
|
10
8
|
end
|
data/lib/active_record.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2004-
|
2
|
+
# Copyright (c) 2004-2014 David Heinemeier Hansson
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -22,25 +22,68 @@
|
|
22
22
|
#++
|
23
23
|
|
24
24
|
require 'active_support'
|
25
|
-
require 'active_support/
|
25
|
+
require 'active_support/rails'
|
26
26
|
require 'active_model'
|
27
27
|
require 'arel'
|
28
28
|
|
29
29
|
require 'active_record/version'
|
30
|
+
require 'active_record/attribute_set'
|
30
31
|
|
31
32
|
module ActiveRecord
|
32
33
|
extend ActiveSupport::Autoload
|
33
34
|
|
35
|
+
autoload :Attribute
|
36
|
+
autoload :Base
|
37
|
+
autoload :Callbacks
|
38
|
+
autoload :Core
|
39
|
+
autoload :ConnectionHandling
|
40
|
+
autoload :CounterCache
|
41
|
+
autoload :DynamicMatchers
|
42
|
+
autoload :Enum
|
43
|
+
autoload :Explain
|
44
|
+
autoload :Inheritance
|
45
|
+
autoload :Integration
|
46
|
+
autoload :Migration
|
47
|
+
autoload :Migrator, 'active_record/migration'
|
48
|
+
autoload :ModelSchema
|
49
|
+
autoload :NestedAttributes
|
50
|
+
autoload :NoTouching
|
51
|
+
autoload :Persistence
|
52
|
+
autoload :QueryCache
|
53
|
+
autoload :Querying
|
54
|
+
autoload :ReadonlyAttributes
|
55
|
+
autoload :RecordInvalid, 'active_record/validations'
|
56
|
+
autoload :Reflection
|
57
|
+
autoload :RuntimeRegistry
|
58
|
+
autoload :Sanitization
|
59
|
+
autoload :Schema
|
60
|
+
autoload :SchemaDumper
|
61
|
+
autoload :SchemaMigration
|
62
|
+
autoload :Scoping
|
63
|
+
autoload :Serialization
|
64
|
+
autoload :StatementCache
|
65
|
+
autoload :Store
|
66
|
+
autoload :Timestamp
|
67
|
+
autoload :Transactions
|
68
|
+
autoload :Translation
|
69
|
+
autoload :Validations
|
70
|
+
|
34
71
|
eager_autoload do
|
35
72
|
autoload :ActiveRecordError, 'active_record/errors'
|
36
73
|
autoload :ConnectionNotEstablished, 'active_record/errors'
|
74
|
+
autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter'
|
37
75
|
|
38
76
|
autoload :Aggregations
|
39
77
|
autoload :Associations
|
78
|
+
autoload :AttributeAssignment
|
40
79
|
autoload :AttributeMethods
|
41
80
|
autoload :AutosaveAssociation
|
42
81
|
|
82
|
+
autoload :LegacyYamlAdapter
|
83
|
+
|
43
84
|
autoload :Relation
|
85
|
+
autoload :AssociationRelation
|
86
|
+
autoload :NullRelation
|
44
87
|
|
45
88
|
autoload_under 'relation' do
|
46
89
|
autoload :QueryMethods
|
@@ -49,33 +92,15 @@ module ActiveRecord
|
|
49
92
|
autoload :PredicateBuilder
|
50
93
|
autoload :SpawnMethods
|
51
94
|
autoload :Batches
|
95
|
+
autoload :Delegation
|
52
96
|
end
|
53
97
|
|
54
|
-
autoload :
|
55
|
-
autoload :Callbacks
|
56
|
-
autoload :CounterCache
|
57
|
-
autoload :DynamicFinderMatch
|
58
|
-
autoload :DynamicScopeMatch
|
59
|
-
autoload :Migration
|
60
|
-
autoload :Migrator, 'active_record/migration'
|
61
|
-
autoload :NamedScope
|
62
|
-
autoload :NestedAttributes
|
63
|
-
autoload :Observer
|
64
|
-
autoload :Persistence
|
65
|
-
autoload :QueryCache
|
66
|
-
autoload :Reflection
|
67
|
-
autoload :Schema
|
68
|
-
autoload :SchemaDumper
|
69
|
-
autoload :Serialization
|
70
|
-
autoload :SessionStore
|
71
|
-
autoload :Timestamp
|
72
|
-
autoload :Transactions
|
73
|
-
autoload :Validations
|
74
|
-
autoload :IdentityMap
|
98
|
+
autoload :Result
|
75
99
|
end
|
76
100
|
|
77
101
|
module Coders
|
78
102
|
autoload :YAMLColumn, 'active_record/coders/yaml_column'
|
103
|
+
autoload :JSON, 'active_record/coders/json'
|
79
104
|
end
|
80
105
|
|
81
106
|
module AttributeMethods
|
@@ -89,6 +114,7 @@ module ActiveRecord
|
|
89
114
|
autoload :Read
|
90
115
|
autoload :TimeZoneConversion
|
91
116
|
autoload :Write
|
117
|
+
autoload :Serialization
|
92
118
|
end
|
93
119
|
end
|
94
120
|
|
@@ -110,12 +136,41 @@ module ActiveRecord
|
|
110
136
|
end
|
111
137
|
end
|
112
138
|
|
113
|
-
|
139
|
+
module Scoping
|
140
|
+
extend ActiveSupport::Autoload
|
141
|
+
|
142
|
+
eager_autoload do
|
143
|
+
autoload :Named
|
144
|
+
autoload :Default
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
module Tasks
|
149
|
+
extend ActiveSupport::Autoload
|
150
|
+
|
151
|
+
autoload :DatabaseTasks
|
152
|
+
autoload :SQLiteDatabaseTasks, 'active_record/tasks/sqlite_database_tasks'
|
153
|
+
autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
|
154
|
+
autoload :PostgreSQLDatabaseTasks,
|
155
|
+
'active_record/tasks/postgresql_database_tasks'
|
156
|
+
end
|
157
|
+
|
114
158
|
autoload :TestFixtures, 'active_record/fixtures'
|
159
|
+
|
160
|
+
def self.eager_load!
|
161
|
+
super
|
162
|
+
ActiveRecord::Locking.eager_load!
|
163
|
+
ActiveRecord::Scoping.eager_load!
|
164
|
+
ActiveRecord::Associations.eager_load!
|
165
|
+
ActiveRecord::AttributeMethods.eager_load!
|
166
|
+
ActiveRecord::ConnectionAdapters.eager_load!
|
167
|
+
end
|
115
168
|
end
|
116
169
|
|
117
170
|
ActiveSupport.on_load(:active_record) do
|
118
171
|
Arel::Table.engine = self
|
119
172
|
end
|
120
173
|
|
121
|
-
|
174
|
+
ActiveSupport.on_load(:i18n) do
|
175
|
+
I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
|
176
|
+
end
|
@@ -1,25 +1,70 @@
|
|
1
1
|
require 'rails/generators/active_record'
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
|
-
module Generators
|
5
|
-
class MigrationGenerator < Base
|
6
|
-
argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
|
4
|
+
module Generators # :nodoc:
|
5
|
+
class MigrationGenerator < Base # :nodoc:
|
6
|
+
argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]"
|
7
7
|
|
8
8
|
def create_migration_file
|
9
9
|
set_local_assigns!
|
10
|
-
|
10
|
+
validate_file_name!
|
11
|
+
migration_template @migration_template, "db/migrate/#{file_name}.rb"
|
11
12
|
end
|
12
13
|
|
13
14
|
protected
|
14
|
-
|
15
|
+
attr_reader :migration_action, :join_tables
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
# sets the default migration template that is being used for the generation of the migration
|
18
|
+
# depending on the arguments which would be sent out in the command line, the migration template
|
19
|
+
# and the table name instance variables are setup.
|
20
|
+
|
21
|
+
def set_local_assigns!
|
22
|
+
@migration_template = "migration.rb"
|
23
|
+
case file_name
|
24
|
+
when /^(add|remove)_.*_(?:to|from)_(.*)/
|
25
|
+
@migration_action = $1
|
26
|
+
@table_name = normalize_table_name($2)
|
27
|
+
when /join_table/
|
28
|
+
if attributes.length == 2
|
29
|
+
@migration_action = 'join'
|
30
|
+
@join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name)
|
31
|
+
|
32
|
+
set_index_names
|
33
|
+
end
|
34
|
+
when /^create_(.+)/
|
35
|
+
@table_name = normalize_table_name($1)
|
36
|
+
@migration_template = "create_table_migration.rb"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def set_index_names
|
41
|
+
attributes.each_with_index do |attr, i|
|
42
|
+
attr.index_name = [attr, attributes[i - 1]].map{ |a| index_name_for(a) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def index_name_for(attribute)
|
47
|
+
if attribute.foreign_key?
|
48
|
+
attribute.name
|
49
|
+
else
|
50
|
+
attribute.name.singularize.foreign_key
|
51
|
+
end.to_sym
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def attributes_with_index
|
56
|
+
attributes.select { |a| !a.reference? && a.has_index? }
|
57
|
+
end
|
58
|
+
|
59
|
+
def validate_file_name!
|
60
|
+
unless file_name =~ /^[_a-z0-9]+$/
|
61
|
+
raise IllegalMigrationNameError.new(file_name)
|
20
62
|
end
|
21
63
|
end
|
22
64
|
|
65
|
+
def normalize_table_name(_table_name)
|
66
|
+
pluralize_table_names? ? _table_name.pluralize : _table_name.singularize
|
67
|
+
end
|
23
68
|
end
|
24
69
|
end
|
25
70
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :<%= table_name %> do |t|
|
4
|
+
<% attributes.each do |attribute| -%>
|
5
|
+
<% if attribute.password_digest? -%>
|
6
|
+
t.string :password_digest<%= attribute.inject_options %>
|
7
|
+
<% else -%>
|
8
|
+
t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
|
9
|
+
<% end -%>
|
10
|
+
<% end -%>
|
11
|
+
<% if options[:timestamps] %>
|
12
|
+
t.timestamps null: false
|
13
|
+
<% end -%>
|
14
|
+
end
|
15
|
+
<% attributes_with_index.each do |attribute| -%>
|
16
|
+
add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
|
17
|
+
<% end -%>
|
18
|
+
end
|
19
|
+
end
|