activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,74 +1,435 @@
1
1
  require 'active_support/core_ext/enumerable'
2
+ require 'active_support/core_ext/string/filters'
3
+ require 'mutex_m'
4
+ require 'thread_safe'
2
5
 
3
6
  module ActiveRecord
4
7
  # = Active Record Attribute Methods
5
- module AttributeMethods #:nodoc:
8
+ module AttributeMethods
6
9
  extend ActiveSupport::Concern
7
10
  include ActiveModel::AttributeMethods
8
11
 
9
- module ClassMethods
10
- # Generates all the attribute related methods for columns in the database
11
- # accessors, mutators and query methods.
12
- def define_attribute_methods
13
- return if attribute_methods_generated?
14
- super(column_names)
15
- @attribute_methods_generated = true
12
+ included do
13
+ initialize_generated_modules
14
+ include Read
15
+ include Write
16
+ include BeforeTypeCast
17
+ include Query
18
+ include PrimaryKey
19
+ include TimeZoneConversion
20
+ include Dirty
21
+ include Serialization
22
+
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
41
+ end
42
+
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
16
51
  end
17
52
 
18
- def attribute_methods_generated?
19
- @attribute_methods_generated ||= false
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."
20
58
  end
59
+ end
60
+
61
+ class GeneratedAttributeMethods < Module; end # :nodoc:
21
62
 
22
- def undefine_attribute_methods(*args)
63
+ module ClassMethods
64
+ def inherited(child_class) #:nodoc:
65
+ child_class.initialize_generated_modules
23
66
  super
67
+ end
68
+
69
+ def initialize_generated_modules # :nodoc:
70
+ @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m }
24
71
  @attribute_methods_generated = false
72
+ include @generated_attribute_methods
73
+
74
+ super
25
75
  end
26
76
 
27
- # Checks whether the method is defined in the model or any of its subclasses
28
- # that also derive from Active Record. Raises DangerousAttributeError if the
29
- # method is defined by Active Record though.
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
82
+ # attribute methods.
83
+ generated_attribute_methods.synchronize do
84
+ return false if @attribute_methods_generated
85
+ superclass.define_attribute_methods unless self == base_class
86
+ super(column_names)
87
+ @attribute_methods_generated = true
88
+ end
89
+ true
90
+ end
91
+
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
97
+ end
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
30
113
  def instance_method_already_implemented?(method_name)
31
- method_name = method_name.to_s
32
- index = ancestors.index(ActiveRecord::Base) || ancestors.length
33
- @_defined_class_methods ||= ancestors.first(index).map { |m|
34
- m.instance_methods(false) | m.private_instance_methods(false)
35
- }.flatten.map {|m| m.to_s }.to_set
36
-
37
- @@_defined_activerecord_methods ||= defined_activerecord_methods
38
- raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
39
- @_defined_class_methods.include?(method_name)
40
- end
41
-
42
- def defined_activerecord_methods
43
- active_record = ActiveRecord::Base
44
- super_klass = ActiveRecord::Base.superclass
45
- methods = (active_record.instance_methods - super_klass.instance_methods) +
46
- (active_record.private_instance_methods - super_klass.private_instance_methods)
47
- methods.map {|m| m.to_s }.to_set
48
- end
49
- end
50
-
51
- def method_missing(method_id, *args, &block)
52
- # If we haven't generated any methods yet, generate them, then
53
- # see if we've created the method we're looking for.
54
- if !self.class.attribute_methods_generated?
55
- self.class.define_attribute_methods
56
- method_name = method_id.to_s
57
- guard_private_attribute_method!(method_name, args)
58
- send(method_id, *args, &block)
59
- else
60
- super
114
+ if dangerous_attribute_method?(method_name)
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."
116
+ end
117
+
118
+ if superclass == Base
119
+ super
120
+ else
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
126
+ end
127
+ end
128
+
129
+ # A method name is 'dangerous' if it is already (re)defined by Active Record, but
130
+ # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
131
+ def dangerous_attribute_method?(name) # :nodoc:
132
+ method_defined_within?(name, Base)
133
+ end
134
+
135
+ def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
136
+ if klass.method_defined?(name) || klass.private_method_defined?(name)
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
157
+ else
158
+ true
159
+ end
160
+ else
161
+ false
162
+ end
163
+ end
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
174
+ def attribute_method?(attribute)
175
+ super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
176
+ end
177
+
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"]
186
+ def attribute_names
187
+ @attribute_names ||= if !abstract_class? && table_exists?
188
+ column_names
189
+ else
190
+ []
191
+ end
192
+ end
193
+
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
214
+ end
215
+ column
61
216
  end
62
217
  end
63
218
 
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
64
235
  def respond_to?(name, include_private = false)
65
- self.class.define_attribute_methods unless self.class.attribute_methods_generated?
66
- super
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)
245
+ end
246
+
247
+ return true
248
+ end
249
+
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
259
+ def has_attribute?(attr_name)
260
+ @attributes.key?(attr_name.to_s)
261
+ end
262
+
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"]
271
+ def attribute_names
272
+ @attributes.keys
273
+ end
274
+
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}
283
+ def attributes
284
+ @attributes.to_hash
285
+ end
286
+
287
+ # Returns an <tt>#inspect</tt>-like string for the value of the
288
+ # attribute +attr_name+. String attributes are truncated up to 50
289
+ # characters, Date and Time attributes are returned in the
290
+ # <tt>:db</tt> format. Other attributes return the value of
291
+ # <tt>#inspect</tt> without modification.
292
+ #
293
+ # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
294
+ #
295
+ # person.attribute_for_inspect(:name)
296
+ # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
297
+ #
298
+ # person.attribute_for_inspect(:created_at)
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]"
303
+ def attribute_for_inspect(attr_name)
304
+ value = read_attribute(attr_name)
305
+
306
+ if value.is_a?(String) && value.length > 50
307
+ "#{value[0, 50]}...".inspect
308
+ elsif value.is_a?(Date) || value.is_a?(Time)
309
+ %("#{value.to_s(:db)}")
310
+ else
311
+ value.inspect
312
+ end
313
+ end
314
+
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
330
+ def attribute_present?(attribute)
331
+ value = _read_attribute(attribute)
332
+ !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
333
+ end
334
+
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) }
356
+ end
357
+
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)
67
370
  end
68
371
 
69
372
  protected
70
- def attribute_method?(attr_name)
71
- attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
373
+
374
+ def clone_attribute_value(reader_method, attribute_name) # :nodoc:
375
+ value = send(reader_method, attribute_name)
376
+ value.duplicable? ? value.clone : value
377
+ rescue TypeError, NoMethodError
378
+ value
379
+ end
380
+
381
+ def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
382
+ arel_attributes_with_values(attributes_for_create(attribute_names))
383
+ end
384
+
385
+ def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
386
+ arel_attributes_with_values(attributes_for_update(attribute_names))
387
+ end
388
+
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
393
+
394
+ private
395
+
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
401
+
402
+ attribute_names.each do |name|
403
+ attrs[arel_table[name]] = typecasted_attribute_value(name)
404
+ end
405
+ attrs
406
+ end
407
+
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)
72
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)
433
+ end
73
434
  end
74
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
@@ -0,0 +1,81 @@
1
+ require 'active_record/attribute_set/builder'
2
+
3
+ module ActiveRecord
4
+ class AttributeSet # :nodoc:
5
+ def initialize(attributes)
6
+ @attributes = attributes
7
+ end
8
+
9
+ def [](name)
10
+ attributes[name] || Attribute.null(name)
11
+ end
12
+
13
+ def values_before_type_cast
14
+ attributes.transform_values(&:value_before_type_cast)
15
+ end
16
+
17
+ def to_hash
18
+ initialized_attributes.transform_values(&:value)
19
+ end
20
+ alias_method :to_h, :to_hash
21
+
22
+ def key?(name)
23
+ attributes.key?(name) && self[name].initialized?
24
+ end
25
+
26
+ def keys
27
+ attributes.initialized_keys
28
+ end
29
+
30
+ def fetch_value(name)
31
+ self[name].value { |n| yield n if block_given? }
32
+ end
33
+
34
+ def write_from_database(name, value)
35
+ attributes[name] = self[name].with_value_from_database(value)
36
+ end
37
+
38
+ def write_from_user(name, value)
39
+ attributes[name] = self[name].with_value_from_user(value)
40
+ end
41
+
42
+ def write_cast_value(name, value)
43
+ attributes[name] = self[name].with_cast_value(value)
44
+ end
45
+
46
+ def freeze
47
+ @attributes.freeze
48
+ super
49
+ end
50
+
51
+ def initialize_dup(_)
52
+ @attributes = attributes.dup
53
+ super
54
+ end
55
+
56
+ def initialize_clone(_)
57
+ @attributes = attributes.clone
58
+ super
59
+ end
60
+
61
+ def reset(key)
62
+ if key?(key)
63
+ write_from_database(key, nil)
64
+ end
65
+ end
66
+
67
+ def ==(other)
68
+ attributes == other.attributes
69
+ end
70
+
71
+ protected
72
+
73
+ attr_reader :attributes
74
+
75
+ private
76
+
77
+ def initialized_attributes
78
+ attributes.select { |_, attr| attr.initialized? }
79
+ end
80
+ end
81
+ end