activerecord 4.2.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 (221) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1372 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +218 -0
  5. data/examples/performance.rb +184 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +173 -0
  8. data/lib/active_record/aggregations.rb +266 -0
  9. data/lib/active_record/association_relation.rb +22 -0
  10. data/lib/active_record/associations.rb +1724 -0
  11. data/lib/active_record/associations/alias_tracker.rb +87 -0
  12. data/lib/active_record/associations/association.rb +253 -0
  13. data/lib/active_record/associations/association_scope.rb +194 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +111 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +149 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +116 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +91 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
  20. data/lib/active_record/associations/builder/has_many.rb +15 -0
  21. data/lib/active_record/associations/builder/has_one.rb +23 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +38 -0
  23. data/lib/active_record/associations/collection_association.rb +634 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1027 -0
  25. data/lib/active_record/associations/has_many_association.rb +184 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +238 -0
  27. data/lib/active_record/associations/has_one_association.rb +105 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +282 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/preloader.rb +203 -0
  34. data/lib/active_record/associations/preloader/association.rb +162 -0
  35. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  36. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  37. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  38. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  39. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  40. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  41. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  42. data/lib/active_record/associations/preloader/through_association.rb +96 -0
  43. data/lib/active_record/associations/singular_association.rb +86 -0
  44. data/lib/active_record/associations/through_association.rb +96 -0
  45. data/lib/active_record/attribute.rb +149 -0
  46. data/lib/active_record/attribute_assignment.rb +212 -0
  47. data/lib/active_record/attribute_decorators.rb +66 -0
  48. data/lib/active_record/attribute_methods.rb +439 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
  50. data/lib/active_record/attribute_methods/dirty.rb +181 -0
  51. data/lib/active_record/attribute_methods/primary_key.rb +128 -0
  52. data/lib/active_record/attribute_methods/query.rb +40 -0
  53. data/lib/active_record/attribute_methods/read.rb +103 -0
  54. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  55. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
  56. data/lib/active_record/attribute_methods/write.rb +83 -0
  57. data/lib/active_record/attribute_set.rb +77 -0
  58. data/lib/active_record/attribute_set/builder.rb +86 -0
  59. data/lib/active_record/attributes.rb +139 -0
  60. data/lib/active_record/autosave_association.rb +439 -0
  61. data/lib/active_record/base.rb +317 -0
  62. data/lib/active_record/callbacks.rb +313 -0
  63. data/lib/active_record/coders/json.rb +13 -0
  64. data/lib/active_record/coders/yaml_column.rb +38 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
  78. data/lib/active_record/connection_adapters/column.rb +82 -0
  79. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
  81. data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
  82. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  111. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  112. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
  117. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
  119. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  120. data/lib/active_record/connection_handling.rb +132 -0
  121. data/lib/active_record/core.rb +566 -0
  122. data/lib/active_record/counter_cache.rb +175 -0
  123. data/lib/active_record/dynamic_matchers.rb +140 -0
  124. data/lib/active_record/enum.rb +198 -0
  125. data/lib/active_record/errors.rb +252 -0
  126. data/lib/active_record/explain.rb +38 -0
  127. data/lib/active_record/explain_registry.rb +30 -0
  128. data/lib/active_record/explain_subscriber.rb +29 -0
  129. data/lib/active_record/fixture_set/file.rb +56 -0
  130. data/lib/active_record/fixtures.rb +1007 -0
  131. data/lib/active_record/gem_version.rb +15 -0
  132. data/lib/active_record/inheritance.rb +247 -0
  133. data/lib/active_record/integration.rb +113 -0
  134. data/lib/active_record/locale/en.yml +47 -0
  135. data/lib/active_record/locking/optimistic.rb +204 -0
  136. data/lib/active_record/locking/pessimistic.rb +77 -0
  137. data/lib/active_record/log_subscriber.rb +75 -0
  138. data/lib/active_record/migration.rb +1051 -0
  139. data/lib/active_record/migration/command_recorder.rb +197 -0
  140. data/lib/active_record/migration/join_table.rb +15 -0
  141. data/lib/active_record/model_schema.rb +340 -0
  142. data/lib/active_record/nested_attributes.rb +548 -0
  143. data/lib/active_record/no_touching.rb +52 -0
  144. data/lib/active_record/null_relation.rb +81 -0
  145. data/lib/active_record/persistence.rb +532 -0
  146. data/lib/active_record/query_cache.rb +56 -0
  147. data/lib/active_record/querying.rb +68 -0
  148. data/lib/active_record/railtie.rb +162 -0
  149. data/lib/active_record/railties/console_sandbox.rb +5 -0
  150. data/lib/active_record/railties/controller_runtime.rb +50 -0
  151. data/lib/active_record/railties/databases.rake +391 -0
  152. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  153. data/lib/active_record/readonly_attributes.rb +23 -0
  154. data/lib/active_record/reflection.rb +881 -0
  155. data/lib/active_record/relation.rb +681 -0
  156. data/lib/active_record/relation/batches.rb +138 -0
  157. data/lib/active_record/relation/calculations.rb +403 -0
  158. data/lib/active_record/relation/delegation.rb +140 -0
  159. data/lib/active_record/relation/finder_methods.rb +528 -0
  160. data/lib/active_record/relation/merger.rb +170 -0
  161. data/lib/active_record/relation/predicate_builder.rb +126 -0
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  164. data/lib/active_record/relation/query_methods.rb +1176 -0
  165. data/lib/active_record/relation/spawn_methods.rb +75 -0
  166. data/lib/active_record/result.rb +131 -0
  167. data/lib/active_record/runtime_registry.rb +22 -0
  168. data/lib/active_record/sanitization.rb +191 -0
  169. data/lib/active_record/schema.rb +64 -0
  170. data/lib/active_record/schema_dumper.rb +251 -0
  171. data/lib/active_record/schema_migration.rb +56 -0
  172. data/lib/active_record/scoping.rb +87 -0
  173. data/lib/active_record/scoping/default.rb +134 -0
  174. data/lib/active_record/scoping/named.rb +164 -0
  175. data/lib/active_record/serialization.rb +22 -0
  176. data/lib/active_record/serializers/xml_serializer.rb +193 -0
  177. data/lib/active_record/statement_cache.rb +111 -0
  178. data/lib/active_record/store.rb +205 -0
  179. data/lib/active_record/tasks/database_tasks.rb +296 -0
  180. data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
  181. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  182. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  183. data/lib/active_record/timestamp.rb +121 -0
  184. data/lib/active_record/transactions.rb +417 -0
  185. data/lib/active_record/translation.rb +22 -0
  186. data/lib/active_record/type.rb +23 -0
  187. data/lib/active_record/type/big_integer.rb +13 -0
  188. data/lib/active_record/type/binary.rb +50 -0
  189. data/lib/active_record/type/boolean.rb +30 -0
  190. data/lib/active_record/type/date.rb +46 -0
  191. data/lib/active_record/type/date_time.rb +43 -0
  192. data/lib/active_record/type/decimal.rb +40 -0
  193. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  194. data/lib/active_record/type/decorator.rb +14 -0
  195. data/lib/active_record/type/float.rb +19 -0
  196. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  197. data/lib/active_record/type/integer.rb +55 -0
  198. data/lib/active_record/type/mutable.rb +16 -0
  199. data/lib/active_record/type/numeric.rb +36 -0
  200. data/lib/active_record/type/serialized.rb +56 -0
  201. data/lib/active_record/type/string.rb +36 -0
  202. data/lib/active_record/type/text.rb +11 -0
  203. data/lib/active_record/type/time.rb +26 -0
  204. data/lib/active_record/type/time_value.rb +38 -0
  205. data/lib/active_record/type/type_map.rb +64 -0
  206. data/lib/active_record/type/unsigned_integer.rb +15 -0
  207. data/lib/active_record/type/value.rb +101 -0
  208. data/lib/active_record/validations.rb +90 -0
  209. data/lib/active_record/validations/associated.rb +51 -0
  210. data/lib/active_record/validations/presence.rb +67 -0
  211. data/lib/active_record/validations/uniqueness.rb +229 -0
  212. data/lib/active_record/version.rb +8 -0
  213. data/lib/rails/generators/active_record.rb +17 -0
  214. data/lib/rails/generators/active_record/migration.rb +18 -0
  215. data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
  216. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
  217. data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
  218. data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
  219. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  220. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  221. metadata +309 -0
@@ -0,0 +1,70 @@
1
+ require 'active_support/core_ext/string/filters'
2
+
3
+ module ActiveRecord
4
+ module AttributeMethods
5
+ module Serialization
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ # If you have an attribute that needs to be saved to the database as an
10
+ # object, and retrieved as the same object, then specify the name of that
11
+ # attribute using this method and it will be handled automatically. The
12
+ # serialization is done through YAML. If +class_name+ is specified, the
13
+ # serialized object must be of that class on assignment and retrieval.
14
+ # Otherwise <tt>SerializationTypeMismatch</tt> will be raised.
15
+ #
16
+ # ==== Parameters
17
+ #
18
+ # * +attr_name+ - The field name that should be serialized.
19
+ # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
20
+ # or a class name that the object type should be equal to.
21
+ #
22
+ # ==== Example
23
+ #
24
+ # # Serialize a preferences attribute.
25
+ # class User < ActiveRecord::Base
26
+ # serialize :preferences
27
+ # end
28
+ #
29
+ # # Serialize preferences using JSON as coder.
30
+ # class User < ActiveRecord::Base
31
+ # serialize :preferences, JSON
32
+ # end
33
+ #
34
+ # # Serialize preferences as Hash using YAML coder.
35
+ # class User < ActiveRecord::Base
36
+ # serialize :preferences, Hash
37
+ # end
38
+ def serialize(attr_name, class_name_or_coder = Object)
39
+ # When ::JSON is used, force it to go through the Active Support JSON encoder
40
+ # to ensure special objects (e.g. Active Record models) are dumped correctly
41
+ # using the #as_json hook.
42
+ coder = if class_name_or_coder == ::JSON
43
+ Coders::JSON
44
+ elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
45
+ class_name_or_coder
46
+ else
47
+ Coders::YAMLColumn.new(class_name_or_coder)
48
+ end
49
+
50
+ decorate_attribute_type(attr_name, :serialize) do |type|
51
+ Type::Serialized.new(type, coder)
52
+ end
53
+ end
54
+
55
+ def serialized_attributes
56
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
57
+ `serialized_attributes` is deprecated without replacement, and will
58
+ be removed in Rails 5.0.
59
+ MSG
60
+
61
+ @serialized_attributes ||= Hash[
62
+ columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c|
63
+ [c.name, c.cast_type.coder]
64
+ }
65
+ ]
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,65 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module TimeZoneConversion
4
+ class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
5
+ include Type::Decorator
6
+
7
+ def type_cast_from_database(value)
8
+ convert_time_to_time_zone(super)
9
+ end
10
+
11
+ def type_cast_from_user(value)
12
+ if value.is_a?(Array)
13
+ value.map { |v| type_cast_from_user(v) }
14
+ elsif value.respond_to?(:in_time_zone)
15
+ value.in_time_zone || super
16
+ end
17
+ end
18
+
19
+ def convert_time_to_time_zone(value)
20
+ if value.is_a?(Array)
21
+ value.map { |v| convert_time_to_time_zone(v) }
22
+ elsif value.acts_like?(:time)
23
+ value.in_time_zone
24
+ else
25
+ value
26
+ end
27
+ end
28
+ end
29
+
30
+ extend ActiveSupport::Concern
31
+
32
+ included do
33
+ mattr_accessor :time_zone_aware_attributes, instance_writer: false
34
+ self.time_zone_aware_attributes = false
35
+
36
+ class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
37
+ self.skip_time_zone_conversion_for_attributes = []
38
+ end
39
+
40
+ module ClassMethods
41
+ private
42
+
43
+ def inherited(subclass)
44
+ # We need to apply this decorator here, rather than on module inclusion. The closure
45
+ # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
46
+ # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
47
+ # `skip_time_zone_conversion_for_attributes` would not be picked up.
48
+ subclass.class_eval do
49
+ matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
50
+ decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type|
51
+ TimeZoneConverter.new(type)
52
+ end
53
+ end
54
+ super
55
+ end
56
+
57
+ def create_time_zone_conversion_attribute?(name, cast_type)
58
+ time_zone_aware_attributes &&
59
+ !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
60
+ (:datetime == cast_type.type)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,83 @@
1
+ require 'active_support/core_ext/module/method_transplanting'
2
+
3
+ module ActiveRecord
4
+ module AttributeMethods
5
+ module Write
6
+ WriterMethodCache = Class.new(AttributeMethodCache) {
7
+ private
8
+
9
+ def method_body(method_name, const_name)
10
+ <<-EOMETHOD
11
+ def #{method_name}(value)
12
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
13
+ write_attribute(name, value)
14
+ end
15
+ EOMETHOD
16
+ end
17
+ }.new
18
+
19
+ extend ActiveSupport::Concern
20
+
21
+ included do
22
+ attribute_method_suffix "="
23
+ end
24
+
25
+ module ClassMethods
26
+ protected
27
+
28
+ if Module.methods_transplantable?
29
+ def define_method_attribute=(name)
30
+ method = WriterMethodCache[name]
31
+ generated_attribute_methods.module_eval {
32
+ define_method "#{name}=", method
33
+ }
34
+ end
35
+ else
36
+ def define_method_attribute=(name)
37
+ safe_name = name.unpack('h*').first
38
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
39
+
40
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
41
+ def __temp__#{safe_name}=(value)
42
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
43
+ write_attribute(name, value)
44
+ end
45
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
46
+ undef_method :__temp__#{safe_name}=
47
+ STR
48
+ end
49
+ end
50
+ end
51
+
52
+ # Updates the attribute identified by <tt>attr_name</tt> with the
53
+ # specified +value+. Empty strings for fixnum and float columns are
54
+ # turned into +nil+.
55
+ def write_attribute(attr_name, value)
56
+ write_attribute_with_type_cast(attr_name, value, true)
57
+ end
58
+
59
+ def raw_write_attribute(attr_name, value)
60
+ write_attribute_with_type_cast(attr_name, value, false)
61
+ end
62
+
63
+ private
64
+ # Handle *= for method_missing.
65
+ def attribute=(attribute_name, value)
66
+ write_attribute(attribute_name, value)
67
+ end
68
+
69
+ def write_attribute_with_type_cast(attr_name, value, should_type_cast)
70
+ attr_name = attr_name.to_s
71
+ attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
72
+
73
+ if should_type_cast
74
+ @attributes.write_from_user(attr_name, value)
75
+ else
76
+ @attributes.write_cast_value(attr_name, value)
77
+ end
78
+
79
+ value
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,77 @@
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
+ protected
68
+
69
+ attr_reader :attributes
70
+
71
+ private
72
+
73
+ def initialized_attributes
74
+ attributes.select { |_, attr| attr.initialized? }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,86 @@
1
+ module ActiveRecord
2
+ class AttributeSet # :nodoc:
3
+ class Builder # :nodoc:
4
+ attr_reader :types, :always_initialized
5
+
6
+ def initialize(types, always_initialized = nil)
7
+ @types = types
8
+ @always_initialized = always_initialized
9
+ end
10
+
11
+ def build_from_database(values = {}, additional_types = {})
12
+ if always_initialized && !values.key?(always_initialized)
13
+ values[always_initialized] = nil
14
+ end
15
+
16
+ attributes = LazyAttributeHash.new(types, values, additional_types)
17
+ AttributeSet.new(attributes)
18
+ end
19
+ end
20
+ end
21
+
22
+ class LazyAttributeHash # :nodoc:
23
+ delegate :select, :transform_values, to: :materialize
24
+
25
+ def initialize(types, values, additional_types)
26
+ @types = types
27
+ @values = values
28
+ @additional_types = additional_types
29
+ @materialized = false
30
+ @delegate_hash = {}
31
+ end
32
+
33
+ def key?(key)
34
+ delegate_hash.key?(key) || values.key?(key) || types.key?(key)
35
+ end
36
+
37
+ def [](key)
38
+ delegate_hash[key] || assign_default_value(key)
39
+ end
40
+
41
+ def []=(key, value)
42
+ if frozen?
43
+ raise RuntimeError, "Can't modify frozen hash"
44
+ end
45
+ delegate_hash[key] = value
46
+ end
47
+
48
+ def initialized_keys
49
+ delegate_hash.keys | values.keys
50
+ end
51
+
52
+ def initialize_dup(_)
53
+ @delegate_hash = delegate_hash.transform_values(&:dup)
54
+ super
55
+ end
56
+
57
+ protected
58
+
59
+ attr_reader :types, :values, :additional_types, :delegate_hash
60
+
61
+ private
62
+
63
+ def assign_default_value(name)
64
+ type = additional_types.fetch(name, types[name])
65
+ value_present = true
66
+ value = values.fetch(name) { value_present = false }
67
+
68
+ if value_present
69
+ delegate_hash[name] = Attribute.from_database(name, value, type)
70
+ elsif types.key?(name)
71
+ delegate_hash[name] = Attribute.uninitialized(name, type)
72
+ end
73
+ end
74
+
75
+ def materialize
76
+ unless @materialized
77
+ values.each_key { |key| self[key] }
78
+ types.each_key { |key| self[key] }
79
+ unless frozen?
80
+ @materialized = true
81
+ end
82
+ end
83
+ delegate_hash
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,139 @@
1
+ module ActiveRecord
2
+ module Attributes # :nodoc:
3
+ extend ActiveSupport::Concern
4
+
5
+ Type = ActiveRecord::Type
6
+
7
+ included do
8
+ class_attribute :user_provided_columns, instance_accessor: false # :internal:
9
+ class_attribute :user_provided_defaults, instance_accessor: false # :internal:
10
+ self.user_provided_columns = {}
11
+ self.user_provided_defaults = {}
12
+ end
13
+
14
+ module ClassMethods # :nodoc:
15
+ # Defines or overrides a attribute on this model. This allows customization of
16
+ # Active Record's type casting behavior, as well as adding support for user defined
17
+ # types.
18
+ #
19
+ # +name+ The name of the methods to define attribute methods for, and the column which
20
+ # this will persist to.
21
+ #
22
+ # +cast_type+ A type object that contains information about how to type cast the value.
23
+ # See the examples section for more information.
24
+ #
25
+ # ==== Options
26
+ # The options hash accepts the following options:
27
+ #
28
+ # +default+ is the default value that the column should use on a new record.
29
+ #
30
+ # ==== Examples
31
+ #
32
+ # The type detected by Active Record can be overridden.
33
+ #
34
+ # # db/schema.rb
35
+ # create_table :store_listings, force: true do |t|
36
+ # t.decimal :price_in_cents
37
+ # end
38
+ #
39
+ # # app/models/store_listing.rb
40
+ # class StoreListing < ActiveRecord::Base
41
+ # end
42
+ #
43
+ # store_listing = StoreListing.new(price_in_cents: '10.1')
44
+ #
45
+ # # before
46
+ # store_listing.price_in_cents # => BigDecimal.new(10.1)
47
+ #
48
+ # class StoreListing < ActiveRecord::Base
49
+ # attribute :price_in_cents, Type::Integer.new
50
+ # end
51
+ #
52
+ # # after
53
+ # store_listing.price_in_cents # => 10
54
+ #
55
+ # Users may also define their own custom types, as long as they respond to the methods
56
+ # defined on the value type. The `type_cast` method on your type object will be called
57
+ # with values both from the database, and from your controllers. See
58
+ # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your
59
+ # type objects inherit from an existing type, or the base value type.
60
+ #
61
+ # class MoneyType < ActiveRecord::Type::Integer
62
+ # def type_cast(value)
63
+ # if value.include?('$')
64
+ # price_in_dollars = value.gsub(/\$/, '').to_f
65
+ # price_in_dollars * 100
66
+ # else
67
+ # value.to_i
68
+ # end
69
+ # end
70
+ # end
71
+ #
72
+ # class StoreListing < ActiveRecord::Base
73
+ # attribute :price_in_cents, MoneyType.new
74
+ # end
75
+ #
76
+ # store_listing = StoreListing.new(price_in_cents: '$10.00')
77
+ # store_listing.price_in_cents # => 1000
78
+ def attribute(name, cast_type, options = {})
79
+ name = name.to_s
80
+ clear_caches_calculated_from_columns
81
+ # Assign a new hash to ensure that subclasses do not share a hash
82
+ self.user_provided_columns = user_provided_columns.merge(name => cast_type)
83
+
84
+ if options.key?(:default)
85
+ self.user_provided_defaults = user_provided_defaults.merge(name => options[:default])
86
+ end
87
+ end
88
+
89
+ # Returns an array of column objects for the table associated with this class.
90
+ def columns
91
+ @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
92
+ end
93
+
94
+ # Returns a hash of column objects for the table associated with this class.
95
+ def columns_hash
96
+ @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
97
+ end
98
+
99
+ def reset_column_information # :nodoc:
100
+ super
101
+ clear_caches_calculated_from_columns
102
+ end
103
+
104
+ private
105
+
106
+ def add_user_provided_columns(schema_columns)
107
+ existing_columns = schema_columns.map do |column|
108
+ new_type = user_provided_columns[column.name]
109
+ if new_type
110
+ column.with_type(new_type)
111
+ else
112
+ column
113
+ end
114
+ end
115
+
116
+ existing_column_names = existing_columns.map(&:name)
117
+ new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)|
118
+ connection.new_column(name, nil, type)
119
+ end
120
+
121
+ existing_columns + new_columns
122
+ end
123
+
124
+ def clear_caches_calculated_from_columns
125
+ @attributes_builder = nil
126
+ @column_names = nil
127
+ @column_types = nil
128
+ @columns = nil
129
+ @columns_hash = nil
130
+ @content_columns = nil
131
+ @default_attributes = nil
132
+ end
133
+
134
+ def raw_default_values
135
+ super.merge(user_provided_defaults)
136
+ end
137
+ end
138
+ end
139
+ end