activerecord 4.2.11.3 → 5.0.0.beta1

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