activerecord 3.2.19 → 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 (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,13 +1,16 @@
1
1
  require 'active_support/core_ext/enumerable'
2
- require 'active_support/deprecation'
2
+ require 'active_support/core_ext/string/filters'
3
+ require 'mutex_m'
4
+ require 'concurrent/map'
3
5
 
4
6
  module ActiveRecord
5
7
  # = Active Record Attribute Methods
6
- module AttributeMethods #:nodoc:
8
+ module AttributeMethods
7
9
  extend ActiveSupport::Concern
8
10
  include ActiveModel::AttributeMethods
9
11
 
10
12
  included do
13
+ initialize_generated_modules
11
14
  include Read
12
15
  include Write
13
16
  include BeforeTypeCast
@@ -16,100 +19,99 @@ module ActiveRecord
16
19
  include TimeZoneConversion
17
20
  include Dirty
18
21
  include Serialization
19
- include DeprecatedUnderscoreRead
20
22
 
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
23
+ delegate :column_for_attribute, to: :class
24
+ end
27
25
 
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)
26
+ AttrNames = Module.new {
27
+ def self.set_name_cache(name, value)
28
+ const_name = "ATTR_#{name}"
29
+ unless const_defined? const_name
30
+ const_set const_name, value.dup.freeze
31
+ end
32
32
  end
33
- end
33
+ }
34
+
35
+ BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
36
+
37
+ class GeneratedAttributeMethods < Module; end # :nodoc:
34
38
 
35
39
  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"
40
+ def inherited(child_class) #:nodoc:
41
+ child_class.initialize_generated_modules
42
+ super
43
+ end
55
44
 
56
- ActiveSupport::Deprecation.warn(msg)
45
+ def initialize_generated_modules # :nodoc:
46
+ @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m }
47
+ @attribute_methods_generated = false
48
+ include @generated_attribute_methods
57
49
 
58
- @attribute_methods_mutex = Mutex.new
59
- end
50
+ super
51
+ end
60
52
 
61
- # Use a mutex; we don't want two thread simaltaneously trying to define
53
+ # Generates all the attribute related methods for columns in the database
54
+ # accessors, mutators and query methods.
55
+ def define_attribute_methods # :nodoc:
56
+ return false if @attribute_methods_generated
57
+ # Use a mutex; we don't want two threads simultaneously trying to define
62
58
  # attribute methods.
63
- @attribute_methods_mutex.synchronize do
64
- return if attribute_methods_generated?
59
+ generated_attribute_methods.synchronize do
60
+ return false if @attribute_methods_generated
65
61
  superclass.define_attribute_methods unless self == base_class
66
- super(column_names)
67
- column_names.each { |name| define_external_attribute_method(name) }
62
+ super(attribute_names)
68
63
  @attribute_methods_generated = true
69
64
  end
65
+ true
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
113
115
  else
114
116
  true
115
117
  end
@@ -118,158 +120,345 @@ module ActiveRecord
118
120
  end
119
121
  end
120
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
133
+ else
134
+ true
135
+ end
136
+ else
137
+ false
138
+ end
139
+ end
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
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
164
+ attribute_types.keys
131
165
  else
132
166
  []
133
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
+ # Returns true if the given attribute exists, otherwise false.
171
+ #
172
+ # class Person < ActiveRecord::Base
173
+ # end
174
+ #
175
+ # Person.has_attribute?('name') # => true
176
+ # Person.has_attribute?(:age) # => true
177
+ # Person.has_attribute?(:nothing) # => false
178
+ def has_attribute?(attr_name)
179
+ attribute_types.key?(attr_name.to_s)
180
+ end
142
181
 
143
- if respond_to_without_attributes?(method)
144
- send(method, *args, &block)
145
- else
146
- super
182
+ # Returns the column object for the named attribute.
183
+ # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the
184
+ # named attribute does not exist.
185
+ #
186
+ # class Person < ActiveRecord::Base
187
+ # end
188
+ #
189
+ # person = Person.new
190
+ # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
191
+ # # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
192
+ #
193
+ # person.column_for_attribute(:nothing)
194
+ # # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
195
+ def column_for_attribute(name)
196
+ name = name.to_s
197
+ columns_hash.fetch(name) do
198
+ ConnectionAdapters::NullColumn.new(name)
147
199
  end
148
- else
149
- super
150
200
  end
151
201
  end
152
202
 
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
- )
203
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
204
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
205
+ # which will all return +true+. It also defines the attribute methods if they have
206
+ # not been generated.
207
+ #
208
+ # class Person < ActiveRecord::Base
209
+ # end
210
+ #
211
+ # person = Person.new
212
+ # person.respond_to(:name) # => true
213
+ # person.respond_to(:name=) # => true
214
+ # person.respond_to(:name?) # => true
215
+ # person.respond_to('age') # => true
216
+ # person.respond_to('age=') # => true
217
+ # person.respond_to('age?') # => true
218
+ # person.respond_to(:nothing) # => false
219
+ def respond_to?(name, include_private = false)
220
+ return false unless super
221
+
222
+ case name
223
+ when :to_partial_path
224
+ name = "to_partial_path".freeze
225
+ when :to_model
226
+ name = "to_model".freeze
227
+ else
228
+ name = name.to_s
162
229
  end
163
230
 
164
- super
165
- end
231
+ # If the result is true then check for the select case.
232
+ # For queries selecting a subset of columns, return false for unselected columns.
233
+ # We check defined?(@attributes) not to issue warnings if called on objects that
234
+ # have been allocated but not yet initialized.
235
+ if defined?(@attributes) && self.class.column_names.include?(name)
236
+ return has_attribute?(name)
237
+ end
166
238
 
167
- def respond_to?(name, include_private = false)
168
- self.class.define_attribute_methods unless self.class.attribute_methods_generated?
169
- super
239
+ return true
170
240
  end
171
241
 
172
- # Returns true if the given attribute is in the attributes hash
242
+ # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
243
+ #
244
+ # class Person < ActiveRecord::Base
245
+ # end
246
+ #
247
+ # person = Person.new
248
+ # person.has_attribute?(:name) # => true
249
+ # person.has_attribute?('age') # => true
250
+ # person.has_attribute?(:nothing) # => false
173
251
  def has_attribute?(attr_name)
174
- @attributes.has_key?(attr_name.to_s)
252
+ @attributes.key?(attr_name.to_s)
175
253
  end
176
254
 
177
255
  # Returns an array of names for the attributes available on this object.
256
+ #
257
+ # class Person < ActiveRecord::Base
258
+ # end
259
+ #
260
+ # person = Person.new
261
+ # person.attribute_names
262
+ # # => ["id", "created_at", "updated_at", "name", "age"]
178
263
  def attribute_names
179
264
  @attributes.keys
180
265
  end
181
266
 
182
267
  # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
268
+ #
269
+ # class Person < ActiveRecord::Base
270
+ # end
271
+ #
272
+ # person = Person.create(name: 'Francesco', age: 22)
273
+ # person.attributes
274
+ # # => {"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
275
  def attributes
184
- attrs = {}
185
- attribute_names.each { |name| attrs[name] = read_attribute(name) }
186
- attrs
276
+ @attributes.to_hash
187
277
  end
188
278
 
189
279
  # 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
192
- # <tt>:db</tt> format. Other attributes return the value of
193
- # <tt>#inspect</tt> without modification.
280
+ # attribute +attr_name+. String attributes are truncated up to 50
281
+ # characters, Date and Time attributes are returned in the
282
+ # <tt>:db</tt> format, Array attributes are truncated up to 10 values.
283
+ # Other attributes return the value of <tt>#inspect</tt> without
284
+ # modification.
194
285
  #
195
- # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
286
+ # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
196
287
  #
197
288
  # person.attribute_for_inspect(:name)
198
- # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
289
+ # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
199
290
  #
200
291
  # person.attribute_for_inspect(:created_at)
201
- # # => '"2009-01-12 04:48:57"'
292
+ # # => "\"2012-10-22 00:15:07\""
293
+ #
294
+ # person.attribute_for_inspect(:tag_ids)
295
+ # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
202
296
  def attribute_for_inspect(attr_name)
203
297
  value = read_attribute(attr_name)
204
298
 
205
299
  if value.is_a?(String) && value.length > 50
206
- "#{value[0..50]}...".inspect
300
+ "#{value[0, 50]}...".inspect
207
301
  elsif value.is_a?(Date) || value.is_a?(Time)
208
302
  %("#{value.to_s(:db)}")
303
+ elsif value.is_a?(Array) && value.size > 10
304
+ inspected = value.first(10).inspect
305
+ %(#{inspected[0...-1]}, ...])
209
306
  else
210
307
  value.inspect
211
308
  end
212
309
  end
213
310
 
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).
311
+ # Returns +true+ if the specified +attribute+ has been set by the user or by a
312
+ # database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
313
+ # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
314
+ # Note that it always returns +true+ with boolean attributes.
315
+ #
316
+ # class Task < ActiveRecord::Base
317
+ # end
318
+ #
319
+ # task = Task.new(title: '', is_done: false)
320
+ # task.attribute_present?(:title) # => false
321
+ # task.attribute_present?(:is_done) # => true
322
+ # task.title = 'Buy milk'
323
+ # task.is_done = true
324
+ # task.attribute_present?(:title) # => true
325
+ # task.attribute_present?(:is_done) # => true
216
326
  def attribute_present?(attribute)
217
- value = read_attribute(attribute)
327
+ value = _read_attribute(attribute)
218
328
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
219
329
  end
220
330
 
221
- # Returns the column object for the named attribute.
222
- def column_for_attribute(name)
223
- self.class.columns_hash[name.to_s]
331
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
332
+ # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
333
+ # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
334
+ #
335
+ # Note: +:id+ is always present.
336
+ #
337
+ # Alias for the #read_attribute method.
338
+ #
339
+ # class Person < ActiveRecord::Base
340
+ # belongs_to :organization
341
+ # end
342
+ #
343
+ # person = Person.new(name: 'Francesco', age: '22')
344
+ # person[:name] # => "Francesco"
345
+ # person[:age] # => 22
346
+ #
347
+ # person = Person.select('id').first
348
+ # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
349
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
350
+ def [](attr_name)
351
+ read_attribute(attr_name) { |n| missing_attribute(n, caller) }
224
352
  end
225
353
 
226
- protected
354
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
355
+ # (Alias for the protected #write_attribute method).
356
+ #
357
+ # class Person < ActiveRecord::Base
358
+ # end
359
+ #
360
+ # person = Person.new
361
+ # person[:age] = '22'
362
+ # person[:age] # => 22
363
+ # person[:age].class # => Integer
364
+ def []=(attr_name, value)
365
+ write_attribute(attr_name, value)
366
+ end
227
367
 
228
- def clone_attributes(reader_method = :read_attribute, attributes = {})
229
- attribute_names.each do |name|
230
- attributes[name] = clone_attribute_value(reader_method, name)
231
- end
232
- attributes
368
+ # Returns the name of all database fields which have been read from this
369
+ # model. This can be useful in development mode to determine which fields
370
+ # need to be selected. For performance critical pages, selecting only the
371
+ # required fields can be an easy performance win (assuming you aren't using
372
+ # all of the fields on the model).
373
+ #
374
+ # For example:
375
+ #
376
+ # class PostsController < ActionController::Base
377
+ # after_action :print_accessed_fields, only: :index
378
+ #
379
+ # def index
380
+ # @posts = Post.all
381
+ # end
382
+ #
383
+ # private
384
+ #
385
+ # def print_accessed_fields
386
+ # p @posts.first.accessed_fields
387
+ # end
388
+ # end
389
+ #
390
+ # Which allows you to quickly change your code to:
391
+ #
392
+ # class PostsController < ActionController::Base
393
+ # def index
394
+ # @posts = Post.select(:id, :title, :author_id, :updated_at)
395
+ # end
396
+ # end
397
+ def accessed_fields
398
+ @attributes.accessed
233
399
  end
234
400
 
235
- def clone_attribute_value(reader_method, attribute_name)
401
+ protected
402
+
403
+ def clone_attribute_value(reader_method, attribute_name) # :nodoc:
236
404
  value = send(reader_method, attribute_name)
237
405
  value.duplicable? ? value.clone : value
238
406
  rescue TypeError, NoMethodError
239
407
  value
240
408
  end
241
409
 
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
410
+ def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
411
+ arel_attributes_with_values(attributes_for_create(attribute_names))
412
+ end
248
413
 
249
- attribute_names.each do |name|
250
- if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
414
+ def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
415
+ arel_attributes_with_values(attributes_for_update(attribute_names))
416
+ end
251
417
 
252
- if include_readonly_attributes || !self.class.readonly_attributes.include?(name)
418
+ def attribute_method?(attr_name) # :nodoc:
419
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
420
+ defined?(@attributes) && @attributes.key?(attr_name)
421
+ end
253
422
 
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
423
+ private
262
424
 
263
- attrs[arel_table[name]] = value
264
- end
265
- end
266
- end
425
+ # Returns a Hash of the Arel::Attributes and attribute values that have been
426
+ # typecasted for use in an Arel insert/update method.
427
+ def arel_attributes_with_values(attribute_names)
428
+ attrs = {}
429
+ arel_table = self.class.arel_table
267
430
 
431
+ attribute_names.each do |name|
432
+ attrs[arel_table[name]] = typecasted_attribute_value(name)
433
+ end
268
434
  attrs
269
435
  end
270
436
 
271
- def attribute_method?(attr_name)
272
- attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
437
+ # Filters the primary keys and readonly attributes from the attribute names.
438
+ def attributes_for_update(attribute_names)
439
+ attribute_names.reject do |name|
440
+ readonly_attribute?(name)
441
+ end
442
+ end
443
+
444
+ # Filters out the primary keys, from the attribute names, when the primary
445
+ # key is to be generated (e.g. the id attribute has no value).
446
+ def attributes_for_create(attribute_names)
447
+ attribute_names.reject do |name|
448
+ pk_attribute?(name) && id.nil?
449
+ end
450
+ end
451
+
452
+ def readonly_attribute?(name)
453
+ self.class.readonly_attributes.include?(name)
454
+ end
455
+
456
+ def pk_attribute?(name)
457
+ name == self.class.primary_key
458
+ end
459
+
460
+ def typecasted_attribute_value(name)
461
+ _read_attribute(name)
273
462
  end
274
463
  end
275
464
  end
@@ -0,0 +1,70 @@
1
+ module ActiveRecord
2
+ class AttributeMutationTracker # :nodoc:
3
+ def initialize(attributes)
4
+ @attributes = attributes
5
+ end
6
+
7
+ def changed_values
8
+ attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
9
+ if changed?(attr_name)
10
+ result[attr_name] = attributes[attr_name].original_value
11
+ end
12
+ end
13
+ end
14
+
15
+ def changes
16
+ attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
17
+ if changed?(attr_name)
18
+ result[attr_name] = [attributes[attr_name].original_value, attributes.fetch_value(attr_name)]
19
+ end
20
+ end
21
+ end
22
+
23
+ def changed?(attr_name)
24
+ attr_name = attr_name.to_s
25
+ attributes[attr_name].changed?
26
+ end
27
+
28
+ def changed_in_place?(attr_name)
29
+ attributes[attr_name].changed_in_place?
30
+ end
31
+
32
+ def forget_change(attr_name)
33
+ attr_name = attr_name.to_s
34
+ attributes[attr_name] = attributes[attr_name].forgetting_assignment
35
+ end
36
+
37
+ protected
38
+
39
+ attr_reader :attributes
40
+
41
+ private
42
+
43
+ def attr_names
44
+ attributes.keys
45
+ end
46
+ end
47
+
48
+ class NullMutationTracker # :nodoc:
49
+ include Singleton
50
+
51
+ def changed_values
52
+ {}
53
+ end
54
+
55
+ def changes
56
+ {}
57
+ end
58
+
59
+ def changed?(*)
60
+ false
61
+ end
62
+
63
+ def changed_in_place?(*)
64
+ false
65
+ end
66
+
67
+ def forget_change(*)
68
+ end
69
+ end
70
+ end