activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,13 +1,15 @@
1
- require 'active_support/core_ext/enumerable'
2
- require 'active_support/deprecation'
1
+ # frozen_string_literal: true
2
+
3
+ require "mutex_m"
3
4
 
4
5
  module ActiveRecord
5
6
  # = Active Record Attribute Methods
6
- module AttributeMethods #:nodoc:
7
+ module AttributeMethods
7
8
  extend ActiveSupport::Concern
8
9
  include ActiveModel::AttributeMethods
9
10
 
10
11
  included do
12
+ initialize_generated_modules
11
13
  include Read
12
14
  include Write
13
15
  include BeforeTypeCast
@@ -16,100 +18,118 @@ module ActiveRecord
16
18
  include TimeZoneConversion
17
19
  include Dirty
18
20
  include Serialization
19
- include DeprecatedUnderscoreRead
20
21
 
21
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
22
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
23
- # (Alias for the protected read_attribute method).
24
- def [](attr_name)
25
- read_attribute(attr_name)
26
- end
22
+ delegate :column_for_attribute, to: :class
23
+ end
27
24
 
28
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
29
- # (Alias for the protected write_attribute method).
30
- def []=(attr_name, value)
31
- write_attribute(attr_name, value)
25
+ AttrNames = Module.new {
26
+ def self.set_name_cache(name, value)
27
+ const_name = "ATTR_#{name}"
28
+ unless const_defined? const_name
29
+ const_set const_name, value.dup.freeze
30
+ end
32
31
  end
32
+ }
33
+
34
+ BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
35
+
36
+ class GeneratedAttributeMethods < Module #:nodoc:
37
+ include Mutex_m
33
38
  end
34
39
 
35
40
  module ClassMethods
36
- # Generates all the attribute related methods for columns in the database
37
- # accessors, mutators and query methods.
38
- def define_attribute_methods
39
- unless defined?(@attribute_methods_mutex)
40
- msg = "It looks like something (probably a gem/plugin) is overriding the " \
41
- "ActiveRecord::Base.inherited method. It is important that this hook executes so " \
42
- "that your models are set up correctly. A workaround has been added to stop this " \
43
- "causing an error in 3.2, but future versions will simply not work if the hook is " \
44
- "overridden. If you are using Kaminari, please upgrade as it is known to have had " \
45
- "this problem.\n\n"
46
- msg << "The following may help track down the problem:"
47
-
48
- meth = method(:inherited)
49
- if meth.respond_to?(:source_location)
50
- msg << " #{meth.source_location.inspect}"
51
- else
52
- msg << " #{meth.inspect}"
53
- end
54
- msg << "\n\n"
41
+ def inherited(child_class) #:nodoc:
42
+ child_class.initialize_generated_modules
43
+ super
44
+ end
55
45
 
56
- ActiveSupport::Deprecation.warn(msg)
46
+ def initialize_generated_modules # :nodoc:
47
+ @generated_attribute_methods = GeneratedAttributeMethods.new
48
+ @attribute_methods_generated = false
49
+ include @generated_attribute_methods
57
50
 
58
- @attribute_methods_mutex = Mutex.new
59
- end
51
+ super
52
+ end
60
53
 
61
- # Use a mutex; we don't want two thread simaltaneously trying to define
54
+ # Generates all the attribute related methods for columns in the database
55
+ # accessors, mutators and query methods.
56
+ def define_attribute_methods # :nodoc:
57
+ return false if @attribute_methods_generated
58
+ # Use a mutex; we don't want two threads simultaneously trying to define
62
59
  # attribute methods.
63
- @attribute_methods_mutex.synchronize do
64
- return if attribute_methods_generated?
60
+ generated_attribute_methods.synchronize do
61
+ return false if @attribute_methods_generated
65
62
  superclass.define_attribute_methods unless self == base_class
66
- super(column_names)
67
- column_names.each { |name| define_external_attribute_method(name) }
63
+ super(attribute_names)
68
64
  @attribute_methods_generated = true
69
65
  end
70
66
  end
71
67
 
72
- def attribute_methods_generated?
73
- @attribute_methods_generated ||= false
74
- end
75
-
76
- # We will define the methods as instance methods, but will call them as singleton
77
- # methods. This allows us to use method_defined? to check if the method exists,
78
- # which is fast and won't give any false positives from the ancestors (because
79
- # there are no ancestors).
80
- def generated_external_attribute_methods
81
- @generated_external_attribute_methods ||= Module.new { extend self }
82
- end
83
-
84
- def undefine_attribute_methods
85
- super
86
- @attribute_methods_generated = false
68
+ def undefine_attribute_methods # :nodoc:
69
+ generated_attribute_methods.synchronize do
70
+ super if defined?(@attribute_methods_generated) && @attribute_methods_generated
71
+ @attribute_methods_generated = false
72
+ end
87
73
  end
88
74
 
75
+ # Raises an ActiveRecord::DangerousAttributeError exception when an
76
+ # \Active \Record method is defined in the model, otherwise +false+.
77
+ #
78
+ # class Person < ActiveRecord::Base
79
+ # def save
80
+ # 'already defined by Active Record'
81
+ # end
82
+ # end
83
+ #
84
+ # Person.instance_method_already_implemented?(:save)
85
+ # # => ActiveRecord::DangerousAttributeError: save is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name.
86
+ #
87
+ # Person.instance_method_already_implemented?(:name)
88
+ # # => false
89
89
  def instance_method_already_implemented?(method_name)
90
90
  if dangerous_attribute_method?(method_name)
91
- raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
91
+ raise DangerousAttributeError, "#{method_name} is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name."
92
92
  end
93
93
 
94
94
  if superclass == Base
95
95
  super
96
96
  else
97
- # If B < A and A defines its own attribute method, then we don't want to overwrite that.
98
- defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
99
- defined && !ActiveRecord::Base.method_defined?(method_name) || super
97
+ # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
98
+ # defines its own attribute method, then we don't want to overwrite that.
99
+ defined = method_defined_within?(method_name, superclass, Base) &&
100
+ ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
101
+ defined || super
100
102
  end
101
103
  end
102
104
 
103
- # A method name is 'dangerous' if it is already defined by Active Record, but
105
+ # A method name is 'dangerous' if it is already (re)defined by Active Record, but
104
106
  # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
105
- def dangerous_attribute_method?(name)
107
+ def dangerous_attribute_method?(name) # :nodoc:
106
108
  method_defined_within?(name, Base)
107
109
  end
108
110
 
109
- def method_defined_within?(name, klass, sup = klass.superclass)
111
+ def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
110
112
  if klass.method_defined?(name) || klass.private_method_defined?(name)
111
- if sup.method_defined?(name) || sup.private_method_defined?(name)
112
- klass.instance_method(name).owner != sup.instance_method(name).owner
113
+ if superklass.method_defined?(name) || superklass.private_method_defined?(name)
114
+ klass.instance_method(name).owner != superklass.instance_method(name).owner
115
+ else
116
+ true
117
+ end
118
+ else
119
+ false
120
+ end
121
+ end
122
+
123
+ # A class method is 'dangerous' if it is already (re)defined by Active Record, but
124
+ # not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
125
+ def dangerous_class_method?(method_name)
126
+ BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
127
+ end
128
+
129
+ def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
130
+ if klass.respond_to?(name, true)
131
+ if superklass.respond_to?(name, true)
132
+ klass.method(name).owner != superklass.method(name).owner
113
133
  else
114
134
  true
115
135
  end
@@ -118,92 +138,216 @@ module ActiveRecord
118
138
  end
119
139
  end
120
140
 
141
+ # Returns +true+ if +attribute+ is an attribute method and table exists,
142
+ # +false+ otherwise.
143
+ #
144
+ # class Person < ActiveRecord::Base
145
+ # end
146
+ #
147
+ # Person.attribute_method?('name') # => true
148
+ # Person.attribute_method?(:age=) # => true
149
+ # Person.attribute_method?(:nothing) # => false
121
150
  def attribute_method?(attribute)
122
- super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
151
+ super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, "")))
123
152
  end
124
153
 
125
- # Returns an array of column names as strings if it's not
126
- # an abstract class and table exists.
127
- # Otherwise it returns an empty array.
154
+ # Returns an array of column names as strings if it's not an abstract class and
155
+ # table exists. Otherwise it returns an empty array.
156
+ #
157
+ # class Person < ActiveRecord::Base
158
+ # end
159
+ #
160
+ # Person.attribute_names
161
+ # # => ["id", "created_at", "updated_at", "name", "age"]
128
162
  def attribute_names
129
163
  @attribute_names ||= if !abstract_class? && table_exists?
130
- column_names
131
- else
132
- []
133
- end
164
+ attribute_types.keys
165
+ else
166
+ []
167
+ end
134
168
  end
135
- end
136
169
 
137
- # If we haven't generated any methods yet, generate them, then
138
- # see if we've created the method we're looking for.
139
- def method_missing(method, *args, &block)
140
- unless self.class.attribute_methods_generated?
141
- self.class.define_attribute_methods
170
+ # Regexp whitelist. Matches the following:
171
+ # "#{table_name}.#{column_name}"
172
+ # "#{column_name}"
173
+ COLUMN_NAME_WHITELIST = /\A(?:\w+\.)?\w+\z/i
174
+
175
+ # Regexp whitelist. Matches the following:
176
+ # "#{table_name}.#{column_name}"
177
+ # "#{table_name}.#{column_name} #{direction}"
178
+ # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
179
+ # "#{table_name}.#{column_name} NULLS LAST"
180
+ # "#{column_name}"
181
+ # "#{column_name} #{direction}"
182
+ # "#{column_name} #{direction} NULLS FIRST"
183
+ # "#{column_name} NULLS LAST"
184
+ COLUMN_NAME_ORDER_WHITELIST = /
185
+ \A
186
+ (?:\w+\.)?
187
+ \w+
188
+ (?:\s+asc|\s+desc)?
189
+ (?:\s+nulls\s+(?:first|last))?
190
+ \z
191
+ /ix
192
+
193
+ def enforce_raw_sql_whitelist(args, whitelist: COLUMN_NAME_WHITELIST) # :nodoc:
194
+ unexpected = args.reject do |arg|
195
+ arg.kind_of?(Arel::Node) ||
196
+ arg.is_a?(Arel::Nodes::SqlLiteral) ||
197
+ arg.is_a?(Arel::Attributes::Attribute) ||
198
+ arg.to_s.split(/\s*,\s*/).all? { |part| whitelist.match?(part) }
199
+ end
142
200
 
143
- if respond_to_without_attributes?(method)
144
- send(method, *args, &block)
201
+ return if unexpected.none?
202
+
203
+ if allow_unsafe_raw_sql == :deprecated
204
+ ActiveSupport::Deprecation.warn(
205
+ "Dangerous query method (method whose arguments are used as raw " \
206
+ "SQL) called with non-attribute argument(s): " \
207
+ "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
208
+ "arguments will be disallowed in Rails 6.0. This method should " \
209
+ "not be called with user-provided values, such as request " \
210
+ "parameters or model attributes. Known-safe values can be passed " \
211
+ "by wrapping them in Arel.sql()."
212
+ )
145
213
  else
146
- super
214
+ raise(ActiveRecord::UnknownAttributeReference,
215
+ "Query method called with non-attribute argument(s): " +
216
+ unexpected.map(&:inspect).join(", ")
217
+ )
147
218
  end
148
- else
149
- super
150
219
  end
151
- end
152
220
 
153
- def attribute_missing(match, *args, &block)
154
- if self.class.columns_hash[match.attr_name]
155
- ActiveSupport::Deprecation.warn(
156
- "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
157
- "dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
158
- "is a column of the table. If this error has happened through normal usage of Active " \
159
- "Record (rather than through your own code or external libraries), please report it as " \
160
- "a bug."
161
- )
221
+ # Returns true if the given attribute exists, otherwise false.
222
+ #
223
+ # class Person < ActiveRecord::Base
224
+ # end
225
+ #
226
+ # Person.has_attribute?('name') # => true
227
+ # Person.has_attribute?(:age) # => true
228
+ # Person.has_attribute?(:nothing) # => false
229
+ def has_attribute?(attr_name)
230
+ attribute_types.key?(attr_name.to_s)
162
231
  end
163
232
 
164
- super
233
+ # Returns the column object for the named attribute.
234
+ # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the
235
+ # named attribute does not exist.
236
+ #
237
+ # class Person < ActiveRecord::Base
238
+ # end
239
+ #
240
+ # person = Person.new
241
+ # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
242
+ # # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
243
+ #
244
+ # person.column_for_attribute(:nothing)
245
+ # # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
246
+ def column_for_attribute(name)
247
+ name = name.to_s
248
+ columns_hash.fetch(name) do
249
+ ConnectionAdapters::NullColumn.new(name)
250
+ end
251
+ end
165
252
  end
166
253
 
254
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
255
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
256
+ # which will all return +true+. It also defines the attribute methods if they have
257
+ # not been generated.
258
+ #
259
+ # class Person < ActiveRecord::Base
260
+ # end
261
+ #
262
+ # person = Person.new
263
+ # person.respond_to?(:name) # => true
264
+ # person.respond_to?(:name=) # => true
265
+ # person.respond_to?(:name?) # => true
266
+ # person.respond_to?('age') # => true
267
+ # person.respond_to?('age=') # => true
268
+ # person.respond_to?('age?') # => true
269
+ # person.respond_to?(:nothing) # => false
167
270
  def respond_to?(name, include_private = false)
168
- self.class.define_attribute_methods unless self.class.attribute_methods_generated?
169
- super
271
+ return false unless super
272
+
273
+ case name
274
+ when :to_partial_path
275
+ name = "to_partial_path".freeze
276
+ when :to_model
277
+ name = "to_model".freeze
278
+ else
279
+ name = name.to_s
280
+ end
281
+
282
+ # If the result is true then check for the select case.
283
+ # For queries selecting a subset of columns, return false for unselected columns.
284
+ # We check defined?(@attributes) not to issue warnings if called on objects that
285
+ # have been allocated but not yet initialized.
286
+ if defined?(@attributes) && self.class.column_names.include?(name)
287
+ return has_attribute?(name)
288
+ end
289
+
290
+ true
170
291
  end
171
292
 
172
- # Returns true if the given attribute is in the attributes hash
293
+ # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
294
+ #
295
+ # class Person < ActiveRecord::Base
296
+ # end
297
+ #
298
+ # person = Person.new
299
+ # person.has_attribute?(:name) # => true
300
+ # person.has_attribute?('age') # => true
301
+ # person.has_attribute?(:nothing) # => false
173
302
  def has_attribute?(attr_name)
174
- @attributes.has_key?(attr_name.to_s)
303
+ @attributes.key?(attr_name.to_s)
175
304
  end
176
305
 
177
306
  # Returns an array of names for the attributes available on this object.
307
+ #
308
+ # class Person < ActiveRecord::Base
309
+ # end
310
+ #
311
+ # person = Person.new
312
+ # person.attribute_names
313
+ # # => ["id", "created_at", "updated_at", "name", "age"]
178
314
  def attribute_names
179
315
  @attributes.keys
180
316
  end
181
317
 
182
318
  # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
319
+ #
320
+ # class Person < ActiveRecord::Base
321
+ # end
322
+ #
323
+ # person = Person.create(name: 'Francesco', age: 22)
324
+ # person.attributes
325
+ # # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
183
326
  def attributes
184
- attrs = {}
185
- attribute_names.each { |name| attrs[name] = read_attribute(name) }
186
- attrs
327
+ @attributes.to_hash
187
328
  end
188
329
 
189
330
  # Returns an <tt>#inspect</tt>-like string for the value of the
190
- # attribute +attr_name+. String attributes are truncated upto 50
191
- # characters, and Date and Time attributes are returned in the
331
+ # attribute +attr_name+. String attributes are truncated up to 50
332
+ # characters, Date and Time attributes are returned in the
192
333
  # <tt>:db</tt> format. Other attributes return the value of
193
334
  # <tt>#inspect</tt> without modification.
194
335
  #
195
- # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
336
+ # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
196
337
  #
197
338
  # person.attribute_for_inspect(:name)
198
- # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
339
+ # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
199
340
  #
200
341
  # person.attribute_for_inspect(:created_at)
201
- # # => '"2009-01-12 04:48:57"'
342
+ # # => "\"2012-10-22 00:15:07\""
343
+ #
344
+ # person.attribute_for_inspect(:tag_ids)
345
+ # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
202
346
  def attribute_for_inspect(attr_name)
203
347
  value = read_attribute(attr_name)
204
348
 
205
349
  if value.is_a?(String) && value.length > 50
206
- "#{value[0..50]}...".inspect
350
+ "#{value[0, 50]}...".inspect
207
351
  elsif value.is_a?(Date) || value.is_a?(Time)
208
352
  %("#{value.to_s(:db)}")
209
353
  else
@@ -211,65 +355,138 @@ module ActiveRecord
211
355
  end
212
356
  end
213
357
 
214
- # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
215
- # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
358
+ # Returns +true+ if the specified +attribute+ has been set by the user or by a
359
+ # database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
360
+ # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
361
+ # Note that it always returns +true+ with boolean attributes.
362
+ #
363
+ # class Task < ActiveRecord::Base
364
+ # end
365
+ #
366
+ # task = Task.new(title: '', is_done: false)
367
+ # task.attribute_present?(:title) # => false
368
+ # task.attribute_present?(:is_done) # => true
369
+ # task.title = 'Buy milk'
370
+ # task.is_done = true
371
+ # task.attribute_present?(:title) # => true
372
+ # task.attribute_present?(:is_done) # => true
216
373
  def attribute_present?(attribute)
217
- value = read_attribute(attribute)
374
+ value = _read_attribute(attribute)
218
375
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
219
376
  end
220
377
 
221
- # Returns the column object for the named attribute.
222
- def column_for_attribute(name)
223
- self.class.columns_hash[name.to_s]
378
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
379
+ # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
380
+ # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
381
+ #
382
+ # Note: +:id+ is always present.
383
+ #
384
+ # class Person < ActiveRecord::Base
385
+ # belongs_to :organization
386
+ # end
387
+ #
388
+ # person = Person.new(name: 'Francesco', age: '22')
389
+ # person[:name] # => "Francesco"
390
+ # person[:age] # => 22
391
+ #
392
+ # person = Person.select('id').first
393
+ # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
394
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
395
+ def [](attr_name)
396
+ read_attribute(attr_name) { |n| missing_attribute(n, caller) }
397
+ end
398
+
399
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
400
+ # (Alias for the protected #write_attribute method).
401
+ #
402
+ # class Person < ActiveRecord::Base
403
+ # end
404
+ #
405
+ # person = Person.new
406
+ # person[:age] = '22'
407
+ # person[:age] # => 22
408
+ # person[:age].class # => Integer
409
+ def []=(attr_name, value)
410
+ write_attribute(attr_name, value)
411
+ end
412
+
413
+ # Returns the name of all database fields which have been read from this
414
+ # model. This can be useful in development mode to determine which fields
415
+ # need to be selected. For performance critical pages, selecting only the
416
+ # required fields can be an easy performance win (assuming you aren't using
417
+ # all of the fields on the model).
418
+ #
419
+ # For example:
420
+ #
421
+ # class PostsController < ActionController::Base
422
+ # after_action :print_accessed_fields, only: :index
423
+ #
424
+ # def index
425
+ # @posts = Post.all
426
+ # end
427
+ #
428
+ # private
429
+ #
430
+ # def print_accessed_fields
431
+ # p @posts.first.accessed_fields
432
+ # end
433
+ # end
434
+ #
435
+ # Which allows you to quickly change your code to:
436
+ #
437
+ # class PostsController < ActionController::Base
438
+ # def index
439
+ # @posts = Post.select(:id, :title, :author_id, :updated_at)
440
+ # end
441
+ # end
442
+ def accessed_fields
443
+ @attributes.accessed
224
444
  end
225
445
 
226
446
  protected
227
447
 
228
- def clone_attributes(reader_method = :read_attribute, attributes = {})
229
- attribute_names.each do |name|
230
- attributes[name] = clone_attribute_value(reader_method, name)
448
+ def attribute_method?(attr_name) # :nodoc:
449
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
450
+ defined?(@attributes) && @attributes.key?(attr_name)
231
451
  end
232
- attributes
233
- end
234
452
 
235
- def clone_attribute_value(reader_method, attribute_name)
236
- value = send(reader_method, attribute_name)
237
- value.duplicable? ? value.clone : value
238
- rescue TypeError, NoMethodError
239
- value
240
- end
453
+ private
241
454
 
242
- # Returns a copy of the attributes hash where all the values have been safely quoted for use in
243
- # an Arel insert/update method.
244
- def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
245
- attrs = {}
246
- klass = self.class
247
- arel_table = klass.arel_table
455
+ def attributes_with_values_for_create(attribute_names)
456
+ attributes_with_values(attributes_for_create(attribute_names))
457
+ end
248
458
 
249
- attribute_names.each do |name|
250
- if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
459
+ def attributes_with_values_for_update(attribute_names)
460
+ attributes_with_values(attributes_for_update(attribute_names))
461
+ end
251
462
 
252
- if include_readonly_attributes || !self.class.readonly_attributes.include?(name)
463
+ def attributes_with_values(attribute_names)
464
+ attribute_names.each_with_object({}) do |name, attrs|
465
+ attrs[name] = _read_attribute(name)
466
+ end
467
+ end
253
468
 
254
- value = if klass.serialized_attributes.include?(name)
255
- @attributes[name].serialized_value
256
- else
257
- # FIXME: we need @attributes to be used consistently.
258
- # If the values stored in @attributes were already type
259
- # casted, this code could be simplified
260
- read_attribute(name)
261
- end
469
+ # Filters the primary keys and readonly attributes from the attribute names.
470
+ def attributes_for_update(attribute_names)
471
+ attribute_names.reject do |name|
472
+ readonly_attribute?(name)
473
+ end
474
+ end
262
475
 
263
- attrs[arel_table[name]] = value
264
- end
476
+ # Filters out the primary keys, from the attribute names, when the primary
477
+ # key is to be generated (e.g. the id attribute has no value).
478
+ def attributes_for_create(attribute_names)
479
+ attribute_names.reject do |name|
480
+ pk_attribute?(name) && id.nil?
265
481
  end
266
482
  end
267
483
 
268
- attrs
269
- end
484
+ def readonly_attribute?(name)
485
+ self.class.readonly_attributes.include?(name)
486
+ end
270
487
 
271
- def attribute_method?(attr_name)
272
- attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
273
- end
488
+ def pk_attribute?(name)
489
+ name == self.class.primary_key
490
+ end
274
491
  end
275
492
  end