activerecord 3.2.22.5 → 4.2.11.3

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 (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
  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 +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +246 -217
  58. data/lib/active_record/base.rb +70 -474
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. 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 'thread_safe'
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,141 @@ 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)
23
+ delegate :column_for_attribute, to: :class
24
+ end
25
+
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
+ end
33
+ }
34
+
35
+ BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
36
+
37
+ class AttributeMethodCache
38
+ def initialize
39
+ @module = Module.new
40
+ @method_cache = ThreadSafe::Cache.new
26
41
  end
27
42
 
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)
43
+ def [](name)
44
+ @method_cache.compute_if_absent(name) do
45
+ safe_name = name.unpack('h*').first
46
+ temp_method = "__temp__#{safe_name}"
47
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
48
+ @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
49
+ @module.instance_method temp_method
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ # Override this method in the subclasses for method body.
56
+ def method_body(method_name, const_name)
57
+ raise NotImplementedError, "Subclasses must implement a method_body(method_name, const_name) method."
32
58
  end
33
59
  end
34
60
 
61
+ class GeneratedAttributeMethods < Module; end # :nodoc:
62
+
35
63
  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"
64
+ def inherited(child_class) #:nodoc:
65
+ child_class.initialize_generated_modules
66
+ super
67
+ end
55
68
 
56
- ActiveSupport::Deprecation.warn(msg)
69
+ def initialize_generated_modules # :nodoc:
70
+ @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m }
71
+ @attribute_methods_generated = false
72
+ include @generated_attribute_methods
57
73
 
58
- @attribute_methods_mutex = Mutex.new
59
- end
74
+ super
75
+ end
60
76
 
61
- # Use a mutex; we don't want two thread simaltaneously trying to define
77
+ # Generates all the attribute related methods for columns in the database
78
+ # accessors, mutators and query methods.
79
+ def define_attribute_methods # :nodoc:
80
+ return false if @attribute_methods_generated
81
+ # Use a mutex; we don't want two threads simultaneously trying to define
62
82
  # attribute methods.
63
- @attribute_methods_mutex.synchronize do
64
- return if attribute_methods_generated?
83
+ generated_attribute_methods.synchronize do
84
+ return false if @attribute_methods_generated
65
85
  superclass.define_attribute_methods unless self == base_class
66
86
  super(column_names)
67
- column_names.each { |name| define_external_attribute_method(name) }
68
87
  @attribute_methods_generated = true
69
88
  end
89
+ true
70
90
  end
71
91
 
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
92
+ def undefine_attribute_methods # :nodoc:
93
+ generated_attribute_methods.synchronize do
94
+ super if defined?(@attribute_methods_generated) && @attribute_methods_generated
95
+ @attribute_methods_generated = false
96
+ end
87
97
  end
88
98
 
99
+ # Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
100
+ # \Active \Record method is defined in the model, otherwise +false+.
101
+ #
102
+ # class Person < ActiveRecord::Base
103
+ # def save
104
+ # 'already defined by Active Record'
105
+ # end
106
+ # end
107
+ #
108
+ # Person.instance_method_already_implemented?(:save)
109
+ # # => ActiveRecord::DangerousAttributeError: save is defined by ActiveRecord
110
+ #
111
+ # Person.instance_method_already_implemented?(:name)
112
+ # # => false
89
113
  def instance_method_already_implemented?(method_name)
90
114
  if dangerous_attribute_method?(method_name)
91
- raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
115
+ 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
116
  end
93
117
 
94
118
  if superclass == Base
95
119
  super
96
120
  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
121
+ # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
122
+ # defines its own attribute method, then we don't want to overwrite that.
123
+ defined = method_defined_within?(method_name, superclass, Base) &&
124
+ ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
125
+ defined || super
100
126
  end
101
127
  end
102
128
 
103
- # A method name is 'dangerous' if it is already defined by Active Record, but
129
+ # A method name is 'dangerous' if it is already (re)defined by Active Record, but
104
130
  # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
105
- def dangerous_attribute_method?(name)
131
+ def dangerous_attribute_method?(name) # :nodoc:
106
132
  method_defined_within?(name, Base)
107
133
  end
108
134
 
109
- def method_defined_within?(name, klass, sup = klass.superclass)
135
+ def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
110
136
  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
137
+ if superklass.method_defined?(name) || superklass.private_method_defined?(name)
138
+ klass.instance_method(name).owner != superklass.instance_method(name).owner
139
+ else
140
+ true
141
+ end
142
+ else
143
+ false
144
+ end
145
+ end
146
+
147
+ # A class method is 'dangerous' if it is already (re)defined by Active Record, but
148
+ # not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
149
+ def dangerous_class_method?(method_name)
150
+ BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
151
+ end
152
+
153
+ def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc
154
+ if klass.respond_to?(name, true)
155
+ if superklass.respond_to?(name, true)
156
+ klass.method(name).owner != superklass.method(name).owner
113
157
  else
114
158
  true
115
159
  end
@@ -118,13 +162,27 @@ module ActiveRecord
118
162
  end
119
163
  end
120
164
 
165
+ # Returns +true+ if +attribute+ is an attribute method and table exists,
166
+ # +false+ otherwise.
167
+ #
168
+ # class Person < ActiveRecord::Base
169
+ # end
170
+ #
171
+ # Person.attribute_method?('name') # => true
172
+ # Person.attribute_method?(:age=) # => true
173
+ # Person.attribute_method?(:nothing) # => false
121
174
  def attribute_method?(attribute)
122
175
  super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
123
176
  end
124
177
 
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.
178
+ # Returns an array of column names as strings if it's not an abstract class and
179
+ # table exists. Otherwise it returns an empty array.
180
+ #
181
+ # class Person < ActiveRecord::Base
182
+ # end
183
+ #
184
+ # Person.attribute_names
185
+ # # => ["id", "created_at", "updated_at", "name", "age"]
128
186
  def attribute_names
129
187
  @attribute_names ||= if !abstract_class? && table_exists?
130
188
  column_names
@@ -132,78 +190,121 @@ module ActiveRecord
132
190
  []
133
191
  end
134
192
  end
135
- end
136
-
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
142
193
 
143
- if respond_to_without_attributes?(method)
144
- send(method, *args, &block)
145
- else
146
- super
194
+ # Returns the column object for the named attribute.
195
+ # Returns nil if the named attribute does not exist.
196
+ #
197
+ # class Person < ActiveRecord::Base
198
+ # end
199
+ #
200
+ # person = Person.new
201
+ # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
202
+ # # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
203
+ #
204
+ # person.column_for_attribute(:nothing)
205
+ # # => nil
206
+ def column_for_attribute(name)
207
+ column = columns_hash[name.to_s]
208
+ if column.nil?
209
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
210
+ `#column_for_attribute` will return a null object for non-existent
211
+ columns in Rails 5. Use `#has_attribute?` if you need to check for
212
+ an attribute's existence.
213
+ MSG
147
214
  end
148
- else
149
- super
215
+ column
150
216
  end
151
217
  end
152
218
 
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
- )
219
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
220
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
221
+ # which will all return +true+. It also define the attribute methods if they have
222
+ # not been generated.
223
+ #
224
+ # class Person < ActiveRecord::Base
225
+ # end
226
+ #
227
+ # person = Person.new
228
+ # person.respond_to(:name) # => true
229
+ # person.respond_to(:name=) # => true
230
+ # person.respond_to(:name?) # => true
231
+ # person.respond_to('age') # => true
232
+ # person.respond_to('age=') # => true
233
+ # person.respond_to('age?') # => true
234
+ # person.respond_to(:nothing) # => false
235
+ def respond_to?(name, include_private = false)
236
+ return false unless super
237
+ name = name.to_s
238
+
239
+ # If the result is true then check for the select case.
240
+ # For queries selecting a subset of columns, return false for unselected columns.
241
+ # We check defined?(@attributes) not to issue warnings if called on objects that
242
+ # have been allocated but not yet initialized.
243
+ if defined?(@attributes) && self.class.column_names.include?(name)
244
+ return has_attribute?(name)
162
245
  end
163
246
 
164
- super
165
- end
166
-
167
- def respond_to?(name, include_private = false)
168
- self.class.define_attribute_methods unless self.class.attribute_methods_generated?
169
- super
247
+ return true
170
248
  end
171
249
 
172
- # Returns true if the given attribute is in the attributes hash
250
+ # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
251
+ #
252
+ # class Person < ActiveRecord::Base
253
+ # end
254
+ #
255
+ # person = Person.new
256
+ # person.has_attribute?(:name) # => true
257
+ # person.has_attribute?('age') # => true
258
+ # person.has_attribute?(:nothing) # => false
173
259
  def has_attribute?(attr_name)
174
- @attributes.has_key?(attr_name.to_s)
260
+ @attributes.key?(attr_name.to_s)
175
261
  end
176
262
 
177
263
  # Returns an array of names for the attributes available on this object.
264
+ #
265
+ # class Person < ActiveRecord::Base
266
+ # end
267
+ #
268
+ # person = Person.new
269
+ # person.attribute_names
270
+ # # => ["id", "created_at", "updated_at", "name", "age"]
178
271
  def attribute_names
179
272
  @attributes.keys
180
273
  end
181
274
 
182
275
  # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
276
+ #
277
+ # class Person < ActiveRecord::Base
278
+ # end
279
+ #
280
+ # person = Person.create(name: 'Francesco', age: 22)
281
+ # person.attributes
282
+ # # => {"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
283
  def attributes
184
- attrs = {}
185
- attribute_names.each { |name| attrs[name] = read_attribute(name) }
186
- attrs
284
+ @attributes.to_hash
187
285
  end
188
286
 
189
287
  # 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
288
+ # attribute +attr_name+. String attributes are truncated up to 50
289
+ # characters, Date and Time attributes are returned in the
192
290
  # <tt>:db</tt> format. Other attributes return the value of
193
291
  # <tt>#inspect</tt> without modification.
194
292
  #
195
- # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
293
+ # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
196
294
  #
197
295
  # person.attribute_for_inspect(:name)
198
- # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
296
+ # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
199
297
  #
200
298
  # person.attribute_for_inspect(:created_at)
201
- # # => '"2009-01-12 04:48:57"'
299
+ # # => "\"2012-10-22 00:15:07\""
300
+ #
301
+ # person.attribute_for_inspect(:tag_ids)
302
+ # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
202
303
  def attribute_for_inspect(attr_name)
203
304
  value = read_attribute(attr_name)
204
305
 
205
306
  if value.is_a?(String) && value.length > 50
206
- "#{value[0..50]}...".inspect
307
+ "#{value[0, 50]}...".inspect
207
308
  elsif value.is_a?(Date) || value.is_a?(Time)
208
309
  %("#{value.to_s(:db)}")
209
310
  else
@@ -211,65 +312,124 @@ module ActiveRecord
211
312
  end
212
313
  end
213
314
 
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).
315
+ # Returns +true+ if the specified +attribute+ has been set by the user or by a
316
+ # database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
317
+ # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
318
+ # Note that it always returns +true+ with boolean attributes.
319
+ #
320
+ # class Task < ActiveRecord::Base
321
+ # end
322
+ #
323
+ # task = Task.new(title: '', is_done: false)
324
+ # task.attribute_present?(:title) # => false
325
+ # task.attribute_present?(:is_done) # => true
326
+ # task.title = 'Buy milk'
327
+ # task.is_done = true
328
+ # task.attribute_present?(:title) # => true
329
+ # task.attribute_present?(:is_done) # => true
216
330
  def attribute_present?(attribute)
217
- value = read_attribute(attribute)
331
+ value = _read_attribute(attribute)
218
332
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
219
333
  end
220
334
 
221
- # Returns the column object for the named attribute.
222
- def column_for_attribute(name)
223
- self.class.columns_hash[name.to_s]
335
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
336
+ # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
337
+ # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
338
+ #
339
+ # Note: +:id+ is always present.
340
+ #
341
+ # Alias for the <tt>read_attribute</tt> method.
342
+ #
343
+ # class Person < ActiveRecord::Base
344
+ # belongs_to :organization
345
+ # end
346
+ #
347
+ # person = Person.new(name: 'Francesco', age: '22')
348
+ # person[:name] # => "Francesco"
349
+ # person[:age] # => 22
350
+ #
351
+ # person = Person.select('id').first
352
+ # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
353
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
354
+ def [](attr_name)
355
+ read_attribute(attr_name) { |n| missing_attribute(n, caller) }
224
356
  end
225
357
 
226
- protected
227
-
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
358
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
359
+ # (Alias for the protected <tt>write_attribute</tt> method).
360
+ #
361
+ # class Person < ActiveRecord::Base
362
+ # end
363
+ #
364
+ # person = Person.new
365
+ # person[:age] = '22'
366
+ # person[:age] # => 22
367
+ # person[:age].class # => Integer
368
+ def []=(attr_name, value)
369
+ write_attribute(attr_name, value)
233
370
  end
234
371
 
235
- def clone_attribute_value(reader_method, attribute_name)
372
+ protected
373
+
374
+ def clone_attribute_value(reader_method, attribute_name) # :nodoc:
236
375
  value = send(reader_method, attribute_name)
237
376
  value.duplicable? ? value.clone : value
238
377
  rescue TypeError, NoMethodError
239
378
  value
240
379
  end
241
380
 
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
381
+ def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
382
+ arel_attributes_with_values(attributes_for_create(attribute_names))
383
+ end
248
384
 
249
- attribute_names.each do |name|
250
- if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
385
+ def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
386
+ arel_attributes_with_values(attributes_for_update(attribute_names))
387
+ end
251
388
 
252
- if include_readonly_attributes || !self.class.readonly_attributes.include?(name)
389
+ def attribute_method?(attr_name) # :nodoc:
390
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
391
+ defined?(@attributes) && @attributes.key?(attr_name)
392
+ end
253
393
 
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
394
+ private
262
395
 
263
- attrs[arel_table[name]] = value
264
- end
265
- end
266
- end
396
+ # Returns a Hash of the Arel::Attributes and attribute values that have been
397
+ # typecasted for use in an Arel insert/update method.
398
+ def arel_attributes_with_values(attribute_names)
399
+ attrs = {}
400
+ arel_table = self.class.arel_table
267
401
 
402
+ attribute_names.each do |name|
403
+ attrs[arel_table[name]] = typecasted_attribute_value(name)
404
+ end
268
405
  attrs
269
406
  end
270
407
 
271
- def attribute_method?(attr_name)
272
- attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
408
+ # Filters the primary keys and readonly attributes from the attribute names.
409
+ def attributes_for_update(attribute_names)
410
+ attribute_names.reject do |name|
411
+ readonly_attribute?(name)
412
+ end
413
+ end
414
+
415
+ # Filters out the primary keys, from the attribute names, when the primary
416
+ # key is to be generated (e.g. the id attribute has no value).
417
+ def attributes_for_create(attribute_names)
418
+ attribute_names.reject do |name|
419
+ pk_attribute?(name) && id.nil?
420
+ end
421
+ end
422
+
423
+ def readonly_attribute?(name)
424
+ self.class.readonly_attributes.include?(name)
425
+ end
426
+
427
+ def pk_attribute?(name)
428
+ name == self.class.primary_key
429
+ end
430
+
431
+ def typecasted_attribute_value(name)
432
+ _read_attribute(name)
273
433
  end
274
434
  end
275
435
  end
@@ -0,0 +1,106 @@
1
+ require 'active_record/attribute'
2
+
3
+ module ActiveRecord
4
+ class AttributeSet # :nodoc:
5
+ class Builder # :nodoc:
6
+ attr_reader :types, :always_initialized
7
+
8
+ def initialize(types, always_initialized = nil)
9
+ @types = types
10
+ @always_initialized = always_initialized
11
+ end
12
+
13
+ def build_from_database(values = {}, additional_types = {})
14
+ if always_initialized && !values.key?(always_initialized)
15
+ values[always_initialized] = nil
16
+ end
17
+
18
+ attributes = LazyAttributeHash.new(types, values, additional_types)
19
+ AttributeSet.new(attributes)
20
+ end
21
+ end
22
+ end
23
+
24
+ class LazyAttributeHash # :nodoc:
25
+ delegate :transform_values, to: :materialize
26
+
27
+ def initialize(types, values, additional_types)
28
+ @types = types
29
+ @values = values
30
+ @additional_types = additional_types
31
+ @materialized = false
32
+ @delegate_hash = {}
33
+ end
34
+
35
+ def key?(key)
36
+ delegate_hash.key?(key) || values.key?(key) || types.key?(key)
37
+ end
38
+
39
+ def [](key)
40
+ delegate_hash[key] || assign_default_value(key)
41
+ end
42
+
43
+ def []=(key, value)
44
+ if frozen?
45
+ raise RuntimeError, "Can't modify frozen hash"
46
+ end
47
+ delegate_hash[key] = value
48
+ end
49
+
50
+ def initialized_keys
51
+ delegate_hash.keys | values.keys
52
+ end
53
+
54
+ def initialize_dup(_)
55
+ @delegate_hash = delegate_hash.transform_values(&:dup)
56
+ super
57
+ end
58
+
59
+ def select
60
+ keys = types.keys | values.keys | delegate_hash.keys
61
+ keys.each_with_object({}) do |key, hash|
62
+ attribute = self[key]
63
+ if yield(key, attribute)
64
+ hash[key] = attribute
65
+ end
66
+ end
67
+ end
68
+
69
+ def ==(other)
70
+ if other.is_a?(LazyAttributeHash)
71
+ materialize == other.materialize
72
+ else
73
+ materialize == other
74
+ end
75
+ end
76
+
77
+ protected
78
+
79
+ attr_reader :types, :values, :additional_types, :delegate_hash
80
+
81
+ def materialize
82
+ unless @materialized
83
+ values.each_key { |key| self[key] }
84
+ types.each_key { |key| self[key] }
85
+ unless frozen?
86
+ @materialized = true
87
+ end
88
+ end
89
+ delegate_hash
90
+ end
91
+
92
+ private
93
+
94
+ def assign_default_value(name)
95
+ type = additional_types.fetch(name, types[name])
96
+ value_present = true
97
+ value = values.fetch(name) { value_present = false }
98
+
99
+ if value_present
100
+ delegate_hash[name] = Attribute.from_database(name, value, type)
101
+ elsif types.key?(name)
102
+ delegate_hash[name] = Attribute.uninitialized(name, type)
103
+ end
104
+ end
105
+ end
106
+ end