activerecord 4.2.11.1 → 5.0.0

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.

Files changed (246) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1282 -1195
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record.rb +8 -4
  8. data/lib/active_record/aggregations.rb +35 -24
  9. data/lib/active_record/association_relation.rb +3 -3
  10. data/lib/active_record/associations.rb +317 -209
  11. data/lib/active_record/associations/alias_tracker.rb +19 -16
  12. data/lib/active_record/associations/association.rb +11 -9
  13. data/lib/active_record/associations/association_scope.rb +73 -102
  14. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  15. data/lib/active_record/associations/builder/association.rb +28 -34
  16. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  17. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +14 -11
  19. data/lib/active_record/associations/builder/has_many.rb +4 -4
  20. data/lib/active_record/associations/builder/has_one.rb +11 -6
  21. data/lib/active_record/associations/builder/singular_association.rb +3 -10
  22. data/lib/active_record/associations/collection_association.rb +49 -41
  23. data/lib/active_record/associations/collection_proxy.rb +67 -27
  24. data/lib/active_record/associations/foreign_association.rb +1 -1
  25. data/lib/active_record/associations/has_many_association.rb +20 -71
  26. data/lib/active_record/associations/has_many_through_association.rb +8 -47
  27. data/lib/active_record/associations/has_one_association.rb +12 -5
  28. data/lib/active_record/associations/join_dependency.rb +29 -19
  29. data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
  30. data/lib/active_record/associations/preloader.rb +14 -4
  31. data/lib/active_record/associations/preloader/association.rb +46 -52
  32. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  33. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  35. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  36. data/lib/active_record/associations/singular_association.rb +7 -1
  37. data/lib/active_record/associations/through_association.rb +11 -3
  38. data/lib/active_record/attribute.rb +68 -18
  39. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  40. data/lib/active_record/attribute_assignment.rb +19 -140
  41. data/lib/active_record/attribute_decorators.rb +6 -5
  42. data/lib/active_record/attribute_methods.rb +76 -47
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  44. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  45. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  46. data/lib/active_record/attribute_methods/query.rb +2 -2
  47. data/lib/active_record/attribute_methods/read.rb +31 -59
  48. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  50. data/lib/active_record/attribute_methods/write.rb +13 -37
  51. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  52. data/lib/active_record/attribute_set.rb +30 -3
  53. data/lib/active_record/attribute_set/builder.rb +6 -4
  54. data/lib/active_record/attributes.rb +199 -81
  55. data/lib/active_record/autosave_association.rb +49 -16
  56. data/lib/active_record/base.rb +32 -23
  57. data/lib/active_record/callbacks.rb +39 -43
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +20 -8
  60. data/lib/active_record/collection_cache_key.rb +40 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -182
  62. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  63. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
  64. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
  65. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -10
  66. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  67. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -185
  69. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  70. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +380 -141
  71. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  72. data/lib/active_record/connection_adapters/abstract_adapter.rb +141 -59
  73. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +401 -370
  74. data/lib/active_record/connection_adapters/column.rb +28 -43
  75. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  76. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  77. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  78. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  79. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  80. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  83. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  84. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  85. data/lib/active_record/connection_adapters/mysql2_adapter.rb +29 -166
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
  88. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  90. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -57
  91. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  95. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  104. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  106. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  107. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  108. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  109. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +234 -148
  111. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  112. data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
  113. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  114. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  115. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  116. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +149 -192
  119. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  120. data/lib/active_record/connection_handling.rb +37 -14
  121. data/lib/active_record/core.rb +89 -107
  122. data/lib/active_record/counter_cache.rb +13 -24
  123. data/lib/active_record/dynamic_matchers.rb +1 -20
  124. data/lib/active_record/enum.rb +113 -76
  125. data/lib/active_record/errors.rb +87 -48
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +1 -1
  128. data/lib/active_record/fixture_set/file.rb +26 -5
  129. data/lib/active_record/fixtures.rb +76 -40
  130. data/lib/active_record/gem_version.rb +4 -4
  131. data/lib/active_record/inheritance.rb +32 -40
  132. data/lib/active_record/integration.rb +4 -4
  133. data/lib/active_record/internal_metadata.rb +56 -0
  134. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  135. data/lib/active_record/locale/en.yml +3 -2
  136. data/lib/active_record/locking/optimistic.rb +15 -15
  137. data/lib/active_record/locking/pessimistic.rb +1 -1
  138. data/lib/active_record/log_subscriber.rb +43 -21
  139. data/lib/active_record/migration.rb +363 -133
  140. data/lib/active_record/migration/command_recorder.rb +59 -18
  141. data/lib/active_record/migration/compatibility.rb +126 -0
  142. data/lib/active_record/model_schema.rb +129 -41
  143. data/lib/active_record/nested_attributes.rb +58 -29
  144. data/lib/active_record/null_relation.rb +16 -8
  145. data/lib/active_record/persistence.rb +121 -80
  146. data/lib/active_record/query_cache.rb +15 -18
  147. data/lib/active_record/querying.rb +10 -9
  148. data/lib/active_record/railtie.rb +23 -16
  149. data/lib/active_record/railties/controller_runtime.rb +1 -1
  150. data/lib/active_record/railties/databases.rake +69 -46
  151. data/lib/active_record/readonly_attributes.rb +1 -1
  152. data/lib/active_record/reflection.rb +282 -115
  153. data/lib/active_record/relation.rb +176 -116
  154. data/lib/active_record/relation/batches.rb +139 -34
  155. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  156. data/lib/active_record/relation/calculations.rb +79 -108
  157. data/lib/active_record/relation/delegation.rb +7 -20
  158. data/lib/active_record/relation/finder_methods.rb +163 -81
  159. data/lib/active_record/relation/from_clause.rb +32 -0
  160. data/lib/active_record/relation/merger.rb +16 -42
  161. data/lib/active_record/relation/predicate_builder.rb +120 -107
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  163. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  164. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  165. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  166. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  167. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  168. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  169. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  170. data/lib/active_record/relation/query_attribute.rb +19 -0
  171. data/lib/active_record/relation/query_methods.rb +308 -244
  172. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  173. data/lib/active_record/relation/spawn_methods.rb +4 -7
  174. data/lib/active_record/relation/where_clause.rb +174 -0
  175. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  176. data/lib/active_record/result.rb +4 -3
  177. data/lib/active_record/runtime_registry.rb +1 -1
  178. data/lib/active_record/sanitization.rb +95 -66
  179. data/lib/active_record/schema.rb +26 -22
  180. data/lib/active_record/schema_dumper.rb +62 -38
  181. data/lib/active_record/schema_migration.rb +11 -14
  182. data/lib/active_record/scoping.rb +32 -15
  183. data/lib/active_record/scoping/default.rb +23 -9
  184. data/lib/active_record/scoping/named.rb +49 -28
  185. data/lib/active_record/secure_token.rb +38 -0
  186. data/lib/active_record/serialization.rb +2 -4
  187. data/lib/active_record/statement_cache.rb +16 -14
  188. data/lib/active_record/store.rb +8 -3
  189. data/lib/active_record/suppressor.rb +58 -0
  190. data/lib/active_record/table_metadata.rb +68 -0
  191. data/lib/active_record/tasks/database_tasks.rb +57 -43
  192. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  193. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  194. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  195. data/lib/active_record/timestamp.rb +20 -9
  196. data/lib/active_record/touch_later.rb +58 -0
  197. data/lib/active_record/transactions.rb +138 -56
  198. data/lib/active_record/type.rb +66 -17
  199. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  200. data/lib/active_record/type/date.rb +2 -45
  201. data/lib/active_record/type/date_time.rb +2 -49
  202. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  203. data/lib/active_record/type/internal/timezone.rb +15 -0
  204. data/lib/active_record/type/serialized.rb +15 -14
  205. data/lib/active_record/type/time.rb +10 -16
  206. data/lib/active_record/type/type_map.rb +4 -4
  207. data/lib/active_record/type_caster.rb +7 -0
  208. data/lib/active_record/type_caster/connection.rb +29 -0
  209. data/lib/active_record/type_caster/map.rb +19 -0
  210. data/lib/active_record/validations.rb +33 -32
  211. data/lib/active_record/validations/absence.rb +23 -0
  212. data/lib/active_record/validations/associated.rb +10 -3
  213. data/lib/active_record/validations/length.rb +24 -0
  214. data/lib/active_record/validations/presence.rb +11 -12
  215. data/lib/active_record/validations/uniqueness.rb +30 -29
  216. data/lib/rails/generators/active_record/migration.rb +7 -0
  217. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  218. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  219. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  220. data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
  221. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  222. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  223. metadata +59 -34
  224. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  225. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  226. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  227. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  228. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  229. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  230. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  231. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  232. data/lib/active_record/type/big_integer.rb +0 -13
  233. data/lib/active_record/type/binary.rb +0 -50
  234. data/lib/active_record/type/boolean.rb +0 -31
  235. data/lib/active_record/type/decimal.rb +0 -64
  236. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  237. data/lib/active_record/type/decorator.rb +0 -14
  238. data/lib/active_record/type/float.rb +0 -19
  239. data/lib/active_record/type/integer.rb +0 -59
  240. data/lib/active_record/type/mutable.rb +0 -16
  241. data/lib/active_record/type/numeric.rb +0 -36
  242. data/lib/active_record/type/string.rb +0 -40
  243. data/lib/active_record/type/text.rb +0 -11
  244. data/lib/active_record/type/time_value.rb +0 -38
  245. data/lib/active_record/type/unsigned_integer.rb +0 -15
  246. data/lib/active_record/type/value.rb +0 -110
@@ -1,26 +1,20 @@
1
1
  module ActiveRecord
2
2
  module Type
3
- class Time < Value # :nodoc:
4
- include TimeValue
3
+ class Time < ActiveModel::Type::Time
4
+ include Internal::Timezone
5
5
 
6
- def type
7
- :time
6
+ class Value < DelegateClass(::Time) # :nodoc:
8
7
  end
9
8
 
10
- private
11
-
12
- def cast_value(value)
13
- return value unless value.is_a?(::String)
14
- return if value.empty?
15
-
16
- dummy_time_value = "2000-01-01 #{value}"
17
-
18
- fast_string_to_time(dummy_time_value) || begin
19
- time_hash = ::Date._parse(dummy_time_value)
20
- return if time_hash[:hour].nil?
21
- new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
9
+ def serialize(value)
10
+ case value = super
11
+ when ::Time
12
+ Value.new(value)
13
+ else
14
+ value
22
15
  end
23
16
  end
24
17
  end
25
18
  end
26
19
  end
20
+
@@ -1,12 +1,12 @@
1
- require 'thread_safe'
1
+ require 'concurrent/map'
2
2
 
3
3
  module ActiveRecord
4
4
  module Type
5
5
  class TypeMap # :nodoc:
6
6
  def initialize
7
7
  @mapping = {}
8
- @cache = ThreadSafe::Cache.new do |h, key|
9
- h.fetch_or_store(key, ThreadSafe::Cache.new)
8
+ @cache = Concurrent::Map.new do |h, key|
9
+ h.fetch_or_store(key, Concurrent::Map.new)
10
10
  end
11
11
  end
12
12
 
@@ -57,7 +57,7 @@ module ActiveRecord
57
57
  end
58
58
 
59
59
  def default_value
60
- @default_value ||= Value.new
60
+ @default_value ||= ActiveModel::Type::Value.new
61
61
  end
62
62
  end
63
63
  end
@@ -0,0 +1,7 @@
1
+ require 'active_record/type_caster/map'
2
+ require 'active_record/type_caster/connection'
3
+
4
+ module ActiveRecord
5
+ module TypeCaster # :nodoc:
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ module ActiveRecord
2
+ module TypeCaster
3
+ class Connection # :nodoc:
4
+ def initialize(klass, table_name)
5
+ @klass = klass
6
+ @table_name = table_name
7
+ end
8
+
9
+ def type_cast_for_database(attribute_name, value)
10
+ return value if value.is_a?(Arel::Nodes::BindParam)
11
+ column = column_for(attribute_name)
12
+ connection.type_cast_from_column(column, value)
13
+ end
14
+
15
+ protected
16
+
17
+ attr_reader :table_name
18
+ delegate :connection, to: :@klass
19
+
20
+ private
21
+
22
+ def column_for(attribute_name)
23
+ if connection.schema_cache.data_source_exists?(table_name)
24
+ connection.schema_cache.columns_hash(table_name)[attribute_name.to_s]
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveRecord
2
+ module TypeCaster
3
+ class Map # :nodoc:
4
+ def initialize(types)
5
+ @types = types
6
+ end
7
+
8
+ def type_cast_for_database(attr_name, value)
9
+ return value if value.is_a?(Arel::Nodes::BindParam)
10
+ type = types.type_for_attribute(attr_name.to_s)
11
+ type.serialize(value)
12
+ end
13
+
14
+ protected
15
+
16
+ attr_reader :types
17
+ end
18
+ end
19
+ end
@@ -1,8 +1,9 @@
1
1
  module ActiveRecord
2
- # = Active Record RecordInvalid
2
+ # = Active Record \RecordInvalid
3
3
  #
4
- # Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
5
- # +record+ method to retrieve the record which did not validate.
4
+ # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
5
+ # {ActiveRecord::Base#create!}[rdoc-ref:Persistence::ClassMethods#create!] when the record is invalid.
6
+ # Use the #record method to retrieve the record which did not validate.
6
7
  #
7
8
  # begin
8
9
  # complex_operation_that_internally_calls_save!
@@ -12,70 +13,68 @@ module ActiveRecord
12
13
  class RecordInvalid < ActiveRecordError
13
14
  attr_reader :record
14
15
 
15
- def initialize(record)
16
- @record = record
17
- errors = @record.errors.full_messages.join(", ")
18
- super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid"))
16
+ def initialize(record = nil)
17
+ if record
18
+ @record = record
19
+ errors = @record.errors.full_messages.join(", ")
20
+ message = I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", errors: errors, default: :"errors.messages.record_invalid")
21
+ else
22
+ message = "Record invalid"
23
+ end
24
+
25
+ super(message)
19
26
  end
20
27
  end
21
28
 
22
- # = Active Record Validations
29
+ # = Active Record \Validations
23
30
  #
24
- # Active Record includes the majority of its validations from <tt>ActiveModel::Validations</tt>
31
+ # Active Record includes the majority of its validations from ActiveModel::Validations
25
32
  # all of which accept the <tt>:on</tt> argument to define the context where the
26
33
  # validations are active. Active Record will always supply either the context of
27
34
  # <tt>:create</tt> or <tt>:update</tt> dependent on whether the model is a
28
- # <tt>new_record?</tt>.
35
+ # {new_record?}[rdoc-ref:Persistence#new_record?].
29
36
  module Validations
30
37
  extend ActiveSupport::Concern
31
38
  include ActiveModel::Validations
32
39
 
33
40
  # 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.
41
+ # The regular {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] method is replaced
42
+ # with this when the validations module is mixed in, which it is by default.
36
43
  def save(options={})
37
44
  perform_validations(options) ? super : false
38
45
  end
39
46
 
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.
47
+ # Attempts to save the record just like {ActiveRecord::Base#save}[rdoc-ref:Base#save] but
48
+ # will raise an ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid.
42
49
  def save!(options={})
43
- perform_validations(options) ? super : raise_record_invalid
50
+ perform_validations(options) ? super : raise_validation_error
44
51
  end
45
52
 
46
53
  # Runs all the validations within the specified context. Returns +true+ if
47
54
  # no errors are found, +false+ otherwise.
48
55
  #
49
- # Aliased as validate.
56
+ # Aliased as #validate.
50
57
  #
51
58
  # 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.
59
+ # {new_record?}[rdoc-ref:Persistence#new_record?] is +true+, and to <tt>:update</tt> if it is not.
53
60
  #
54
- # Validations with no <tt>:on</tt> option will run no matter the context. Validations with
61
+ # \Validations with no <tt>:on</tt> option will run no matter the context. \Validations with
55
62
  # some <tt>:on</tt> option will only run in the specified context.
56
63
  def valid?(context = nil)
57
- context ||= (new_record? ? :create : :update)
64
+ context ||= default_validation_context
58
65
  output = super(context)
59
66
  errors.empty? && output
60
67
  end
61
68
 
62
69
  alias_method :validate, :valid?
63
70
 
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
-
76
71
  protected
77
72
 
78
- def raise_record_invalid
73
+ def default_validation_context
74
+ new_record? ? :create : :update
75
+ end
76
+
77
+ def raise_validation_error
79
78
  raise(RecordInvalid.new(self))
80
79
  end
81
80
 
@@ -88,3 +87,5 @@ end
88
87
  require "active_record/validations/associated"
89
88
  require "active_record/validations/uniqueness"
90
89
  require "active_record/validations/presence"
90
+ require "active_record/validations/absence"
91
+ require "active_record/validations/length"
@@ -0,0 +1,23 @@
1
+ module ActiveRecord
2
+ module Validations
3
+ class AbsenceValidator < ActiveModel::Validations::AbsenceValidator # :nodoc:
4
+ def validate_each(record, attribute, association_or_value)
5
+ if record.class._reflect_on_association(attribute)
6
+ association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
7
+ end
8
+ super
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ # Validates that the specified attributes are not present (as defined by
14
+ # Object#present?). If the attribute is an association, the associated object
15
+ # is considered absent if it was marked for destruction.
16
+ #
17
+ # See ActiveModel::Validations::HelperMethods.validates_absence_of for more information.
18
+ def validates_absence_of(*attr_names)
19
+ validates_with AbsenceValidator, _merge_attributes(attr_names)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -2,10 +2,16 @@ module ActiveRecord
2
2
  module Validations
3
3
  class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
4
4
  def validate_each(record, attribute, value)
5
- if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any?
6
- record.errors.add(attribute, :invalid, options.merge(:value => value))
5
+ if Array(value).reject { |r| valid_object?(r) }.any?
6
+ record.errors.add(attribute, :invalid, options.merge(value: value))
7
7
  end
8
8
  end
9
+
10
+ private
11
+
12
+ def valid_object?(record)
13
+ (record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid?
14
+ end
9
15
  end
10
16
 
11
17
  module ClassMethods
@@ -24,7 +30,8 @@ module ActiveRecord
24
30
  #
25
31
  # NOTE: This validation will not fail if the association hasn't been
26
32
  # assigned. If you want to ensure that the association is both present and
27
- # guaranteed to be valid, you also need to use +validates_presence_of+.
33
+ # guaranteed to be valid, you also need to use
34
+ # {validates_presence_of}[rdoc-ref:Validations::ClassMethods#validates_presence_of].
28
35
  #
29
36
  # Configuration options:
30
37
  #
@@ -0,0 +1,24 @@
1
+ module ActiveRecord
2
+ module Validations
3
+ class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
4
+ def validate_each(record, attribute, association_or_value)
5
+ if association_or_value.respond_to?(:loaded?) && association_or_value.loaded?
6
+ association_or_value = association_or_value.target.reject(&:marked_for_destruction?)
7
+ end
8
+ super
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ # Validates that the specified attributes match the length restrictions supplied.
14
+ # If the attribute is an association, records that are marked for destruction are not counted.
15
+ #
16
+ # See ActiveModel::Validations::HelperMethods.validates_length_of for more information.
17
+ def validates_length_of(*attr_names)
18
+ validates_with LengthValidator, _merge_attributes(attr_names)
19
+ end
20
+
21
+ alias_method :validates_size_of, :validates_length_of
22
+ end
23
+ end
24
+ end
@@ -1,17 +1,11 @@
1
1
  module ActiveRecord
2
2
  module Validations
3
3
  class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
4
- def validate(record)
5
- super
6
- attributes.each do |attribute|
7
- next unless record.class._reflect_on_association(attribute)
8
- associated_records = Array.wrap(record.send(attribute))
9
-
10
- # Superclass validates presence. Ensure present records aren't about to be destroyed.
11
- if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? }
12
- record.errors.add(attribute, :blank, options)
13
- end
4
+ def validate_each(record, attribute, association_or_value)
5
+ if record.class._reflect_on_association(attribute)
6
+ association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
14
7
  end
8
+ super
15
9
  end
16
10
  end
17
11
 
@@ -36,12 +30,17 @@ module ActiveRecord
36
30
  # This is due to the way Object#blank? handles boolean values:
37
31
  # <tt>false.blank? # => true</tt>.
38
32
  #
39
- # This validator defers to the ActiveModel validation for presence, adding the
33
+ # This validator defers to the Active Model validation for presence, adding the
40
34
  # check to see that an associated object is not marked for destruction. This
41
35
  # prevents the parent object from validating successfully and saving, which then
42
36
  # deletes the associated object, thus putting the parent object into an invalid
43
37
  # state.
44
38
  #
39
+ # NOTE: This validation will not fail while using it with an association
40
+ # if the latter was assigned but not valid. If you want to ensure that
41
+ # it is both present and valid, you also need to use
42
+ # {validates_associated}[rdoc-ref:Validations::ClassMethods#validates_associated].
43
+ #
45
44
  # Configuration options:
46
45
  # * <tt>:message</tt> - A custom error message (default is: "can't be blank").
47
46
  # * <tt>:on</tt> - Specifies the contexts where this validation is active.
@@ -58,7 +57,7 @@ module ActiveRecord
58
57
  # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
59
58
  # proc or string should return or evaluate to a +true+ or +false+ value.
60
59
  # * <tt>:strict</tt> - Specifies whether validation should be strict.
61
- # See <tt>ActiveModel::Validation#validates!</tt> for more information.
60
+ # See ActiveModel::Validation#validates! for more information.
62
61
  def validates_presence_of(*attr_names)
63
62
  validates_with PresenceValidator, _merge_attributes(attr_names)
64
63
  end
@@ -15,21 +15,16 @@ module ActiveRecord
15
15
  table = finder_class.arel_table
16
16
  value = map_enum_attribute(finder_class, attribute, value)
17
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
18
+ relation = build_relation(finder_class, table, attribute, value)
19
+ if record.persisted?
20
+ if finder_class.primary_key
21
+ relation = relation.where.not(finder_class.primary_key => record.id_was || record.id)
22
+ else
23
+ raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
26
24
  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
32
25
  end
26
+ relation = scope_relation(record, table, relation)
27
+ relation = relation.merge(options[:conditions]) if options[:conditions]
33
28
 
34
29
  if relation.exists?
35
30
  error_options = options.except(:case_sensitive, :scope, :conditions)
@@ -61,26 +56,32 @@ module ActiveRecord
61
56
  value = value.attributes[reflection.klass.primary_key] unless value.nil?
62
57
  end
63
58
 
64
- attribute_name = attribute.to_s
65
-
66
59
  # 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
60
+ if klass.attribute_alias?(attribute)
61
+ attribute = klass.attribute_alias(attribute)
70
62
  end
71
63
 
64
+ attribute_name = attribute.to_s
65
+
72
66
  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
67
+ cast_type = klass.type_for_attribute(attribute_name)
68
+ value = cast_type.serialize(value)
69
+ value = klass.connection.type_cast(value)
77
70
 
78
- if !options[:case_sensitive] && value && column.text?
71
+ comparison = if !options[:case_sensitive] && !value.nil?
79
72
  # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
80
73
  klass.connection.case_insensitive_comparison(table, attribute, column, value)
81
74
  else
82
75
  klass.connection.case_sensitive_comparison(table, attribute, column, value)
83
76
  end
77
+ if value.nil?
78
+ klass.unscoped.where(comparison)
79
+ else
80
+ bind = Relation::QueryAttribute.new(attribute_name, value, Type::Value.new)
81
+ klass.unscoped.where(comparison, bind)
82
+ end
83
+ rescue RangeError
84
+ klass.none
84
85
  end
85
86
 
86
87
  def scope_relation(record, table, relation)
@@ -91,7 +92,7 @@ module ActiveRecord
91
92
  else
92
93
  scope_value = record._read_attribute(scope_item)
93
94
  end
94
- relation = relation.and(table[scope_item].eq(scope_value))
95
+ relation = relation.where(scope_item => scope_value)
95
96
  end
96
97
 
97
98
  relation
@@ -169,7 +170,8 @@ module ActiveRecord
169
170
  #
170
171
  # === Concurrency and integrity
171
172
  #
172
- # Using this validation method in conjunction with ActiveRecord::Base#save
173
+ # Using this validation method in conjunction with
174
+ # {ActiveRecord::Base#save}[rdoc-ref:Persistence#save]
173
175
  # does not guarantee the absence of duplicate record insertions, because
174
176
  # uniqueness checks on the application level are inherently prone to race
175
177
  # conditions. For example, suppose that two users try to post a Comment at
@@ -206,12 +208,12 @@ module ActiveRecord
206
208
  # This could even happen if you use transactions with the 'serializable'
207
209
  # isolation level. The best way to work around this problem is to add a unique
208
210
  # index to the database table using
209
- # ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
210
- # rare case that a race condition occurs, the database will guarantee
211
+ # {connection.add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index].
212
+ # In the rare case that a race condition occurs, the database will guarantee
211
213
  # the field's uniqueness.
212
214
  #
213
215
  # When the database catches such a duplicate insertion,
214
- # ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
216
+ # {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will raise an ActiveRecord::StatementInvalid
215
217
  # exception. You can either choose to let this error propagate (which
216
218
  # will result in the default Rails exception page being shown), or you
217
219
  # can catch it and restart the transaction (e.g. by telling the user
@@ -227,7 +229,6 @@ module ActiveRecord
227
229
  #
228
230
  # The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
229
231
  #
230
- # * ActiveRecord::ConnectionAdapters::MysqlAdapter.
231
232
  # * ActiveRecord::ConnectionAdapters::Mysql2Adapter.
232
233
  # * ActiveRecord::ConnectionAdapters::SQLite3Adapter.
233
234
  # * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.