activerecord 3.2.19 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,28 @@
1
+ require 'active_record/attribute'
2
+
3
+ module ActiveRecord
4
+ class Attribute # :nodoc:
5
+ class UserProvidedDefault < FromUser # :nodoc:
6
+ def initialize(name, value, type, database_default)
7
+ @user_provided_value = value
8
+ super(name, value, type, database_default)
9
+ end
10
+
11
+ def value_before_type_cast
12
+ if user_provided_value.is_a?(Proc)
13
+ @memoized_value_before_type_cast ||= user_provided_value.call
14
+ else
15
+ @user_provided_value
16
+ end
17
+ end
18
+
19
+ def with_type(type)
20
+ self.class.new(name, user_provided_value, type, original_attribute)
21
+ end
22
+
23
+ protected
24
+
25
+ attr_reader :user_provided_value
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,213 @@
1
+ module ActiveRecord
2
+ class Attribute # :nodoc:
3
+ class << self
4
+ def from_database(name, value, type)
5
+ FromDatabase.new(name, value, type)
6
+ end
7
+
8
+ def from_user(name, value, type, original_attribute = nil)
9
+ FromUser.new(name, value, type, original_attribute)
10
+ end
11
+
12
+ def with_cast_value(name, value, type)
13
+ WithCastValue.new(name, value, type)
14
+ end
15
+
16
+ def null(name)
17
+ Null.new(name)
18
+ end
19
+
20
+ def uninitialized(name, type)
21
+ Uninitialized.new(name, type)
22
+ end
23
+ end
24
+
25
+ attr_reader :name, :value_before_type_cast, :type
26
+
27
+ # This method should not be called directly.
28
+ # Use #from_database or #from_user
29
+ def initialize(name, value_before_type_cast, type, original_attribute = nil)
30
+ @name = name
31
+ @value_before_type_cast = value_before_type_cast
32
+ @type = type
33
+ @original_attribute = original_attribute
34
+ end
35
+
36
+ def value
37
+ # `defined?` is cheaper than `||=` when we get back falsy values
38
+ @value = type_cast(value_before_type_cast) unless defined?(@value)
39
+ @value
40
+ end
41
+
42
+ def original_value
43
+ if assigned?
44
+ original_attribute.original_value
45
+ else
46
+ type_cast(value_before_type_cast)
47
+ end
48
+ end
49
+
50
+ def value_for_database
51
+ type.serialize(value)
52
+ end
53
+
54
+ def changed?
55
+ changed_from_assignment? || changed_in_place?
56
+ end
57
+
58
+ def changed_in_place?
59
+ has_been_read? && type.changed_in_place?(original_value_for_database, value)
60
+ end
61
+
62
+ def forgetting_assignment
63
+ with_value_from_database(value_for_database)
64
+ end
65
+
66
+ def with_value_from_user(value)
67
+ type.assert_valid_value(value)
68
+ self.class.from_user(name, value, type, self)
69
+ end
70
+
71
+ def with_value_from_database(value)
72
+ self.class.from_database(name, value, type)
73
+ end
74
+
75
+ def with_cast_value(value)
76
+ self.class.with_cast_value(name, value, type)
77
+ end
78
+
79
+ def with_type(type)
80
+ self.class.new(name, value_before_type_cast, type, original_attribute)
81
+ end
82
+
83
+ def type_cast(*)
84
+ raise NotImplementedError
85
+ end
86
+
87
+ def initialized?
88
+ true
89
+ end
90
+
91
+ def came_from_user?
92
+ false
93
+ end
94
+
95
+ def has_been_read?
96
+ defined?(@value)
97
+ end
98
+
99
+ def ==(other)
100
+ self.class == other.class &&
101
+ name == other.name &&
102
+ value_before_type_cast == other.value_before_type_cast &&
103
+ type == other.type
104
+ end
105
+ alias eql? ==
106
+
107
+ def hash
108
+ [self.class, name, value_before_type_cast, type].hash
109
+ end
110
+
111
+ protected
112
+
113
+ attr_reader :original_attribute
114
+ alias_method :assigned?, :original_attribute
115
+
116
+ def initialize_dup(other)
117
+ if defined?(@value) && @value.duplicable?
118
+ @value = @value.dup
119
+ end
120
+ end
121
+
122
+ def changed_from_assignment?
123
+ assigned? && type.changed?(original_value, value, value_before_type_cast)
124
+ end
125
+
126
+ def original_value_for_database
127
+ if assigned?
128
+ original_attribute.original_value_for_database
129
+ else
130
+ _original_value_for_database
131
+ end
132
+ end
133
+
134
+ def _original_value_for_database
135
+ value_for_database
136
+ end
137
+
138
+ class FromDatabase < Attribute # :nodoc:
139
+ def type_cast(value)
140
+ type.deserialize(value)
141
+ end
142
+
143
+ def _original_value_for_database
144
+ value_before_type_cast
145
+ end
146
+ end
147
+
148
+ class FromUser < Attribute # :nodoc:
149
+ def type_cast(value)
150
+ type.cast(value)
151
+ end
152
+
153
+ def came_from_user?
154
+ true
155
+ end
156
+ end
157
+
158
+ class WithCastValue < Attribute # :nodoc:
159
+ def type_cast(value)
160
+ value
161
+ end
162
+
163
+ def changed_in_place_from?(old_value)
164
+ false
165
+ end
166
+ end
167
+
168
+ class Null < Attribute # :nodoc:
169
+ def initialize(name)
170
+ super(name, nil, Type::Value.new)
171
+ end
172
+
173
+ def type_cast(*)
174
+ nil
175
+ end
176
+
177
+ def with_type(type)
178
+ self.class.with_cast_value(name, nil, type)
179
+ end
180
+
181
+ def with_value_from_database(value)
182
+ raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
183
+ end
184
+ alias_method :with_value_from_user, :with_value_from_database
185
+ end
186
+
187
+ class Uninitialized < Attribute # :nodoc:
188
+ UNINITIALIZED_ORIGINAL_VALUE = Object.new
189
+
190
+ def initialize(name, type)
191
+ super(name, nil, type)
192
+ end
193
+
194
+ def value
195
+ if block_given?
196
+ yield name
197
+ end
198
+ end
199
+
200
+ def original_value
201
+ UNINITIALIZED_ORIGINAL_VALUE
202
+ end
203
+
204
+ def value_for_database
205
+ end
206
+
207
+ def initialized?
208
+ false
209
+ end
210
+ end
211
+ private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
212
+ end
213
+ end
@@ -1,206 +1,77 @@
1
- require 'active_support/concern'
1
+ require 'active_model/forbidden_attributes_protection'
2
2
 
3
3
  module ActiveRecord
4
4
  module AttributeAssignment
5
5
  extend ActiveSupport::Concern
6
- include ActiveModel::MassAssignmentSecurity
6
+ include ActiveModel::AttributeAssignment
7
7
 
8
- module ClassMethods
9
- private
10
-
11
- # The primary key and inheritance column can never be set by mass-assignment for security reasons.
12
- def attributes_protected_by_default
13
- default = [ primary_key, inheritance_column ]
14
- default << 'id' unless primary_key.eql? 'id'
15
- default
16
- end
17
- end
18
-
19
- # Allows you to set all the attributes at once by passing in a hash with keys
20
- # matching the attribute names (which again matches the column names).
21
- #
22
- # If any attributes are protected by either +attr_protected+ or
23
- # +attr_accessible+ then only settable attributes will be assigned.
24
- #
25
- # class User < ActiveRecord::Base
26
- # attr_protected :is_admin
27
- # end
28
- #
29
- # user = User.new
30
- # user.attributes = { :username => 'Phusion', :is_admin => true }
31
- # user.username # => "Phusion"
32
- # user.is_admin? # => false
33
- def attributes=(new_attributes)
34
- return unless new_attributes.is_a?(Hash)
35
-
36
- assign_attributes(new_attributes)
8
+ # Alias for ActiveModel::AttributeAssignment#assign_attributes. See ActiveModel::AttributeAssignment.
9
+ def attributes=(attributes)
10
+ assign_attributes(attributes)
37
11
  end
38
12
 
39
- # Allows you to set all the attributes for a particular mass-assignment
40
- # security role by passing in a hash of attributes with keys matching
41
- # the attribute names (which again matches the column names) and the role
42
- # name using the :as option.
43
- #
44
- # To bypass mass-assignment security you can use the :without_protection => true
45
- # option.
46
- #
47
- # class User < ActiveRecord::Base
48
- # attr_accessible :name
49
- # attr_accessible :name, :is_admin, :as => :admin
50
- # end
51
- #
52
- # user = User.new
53
- # user.assign_attributes({ :name => 'Josh', :is_admin => true })
54
- # user.name # => "Josh"
55
- # user.is_admin? # => false
56
- #
57
- # user = User.new
58
- # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
59
- # user.name # => "Josh"
60
- # user.is_admin? # => true
61
- #
62
- # user = User.new
63
- # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
64
- # user.name # => "Josh"
65
- # user.is_admin? # => true
66
- def assign_attributes(new_attributes, options = {})
67
- return if new_attributes.blank?
68
-
69
- attributes = new_attributes.stringify_keys
70
- multi_parameter_attributes = []
71
- nested_parameter_attributes = []
72
- @mass_assignment_options = options
13
+ private
73
14
 
74
- unless options[:without_protection]
75
- attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
76
- end
15
+ def _assign_attributes(attributes) # :nodoc:
16
+ multi_parameter_attributes = {}
17
+ nested_parameter_attributes = {}
77
18
 
78
19
  attributes.each do |k, v|
79
20
  if k.include?("(")
80
- multi_parameter_attributes << [ k, v ]
81
- elsif respond_to?("#{k}=")
82
- if v.is_a?(Hash)
83
- nested_parameter_attributes << [ k, v ]
84
- else
85
- send("#{k}=", v)
86
- end
87
- else
88
- raise(UnknownAttributeError, "unknown attribute: #{k}")
21
+ multi_parameter_attributes[k] = attributes.delete(k)
22
+ elsif v.is_a?(Hash)
23
+ nested_parameter_attributes[k] = attributes.delete(k)
89
24
  end
90
25
  end
26
+ super(attributes)
91
27
 
92
- # assign any deferred nested attributes after the base attributes have been set
93
- nested_parameter_attributes.each do |k,v|
94
- send("#{k}=", v)
95
- end
96
-
97
- @mass_assignment_options = nil
98
- assign_multiparameter_attributes(multi_parameter_attributes)
99
- end
100
-
101
- protected
102
-
103
- def mass_assignment_options
104
- @mass_assignment_options ||= {}
28
+ assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
29
+ assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
105
30
  end
106
31
 
107
- def mass_assignment_role
108
- mass_assignment_options[:as] || :default
32
+ # Assign any deferred nested attributes after the base attributes have been set.
33
+ def assign_nested_parameter_attributes(pairs)
34
+ pairs.each { |k, v| _assign_attribute(k, v) }
109
35
  end
110
36
 
111
- private
112
-
113
37
  # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
114
38
  # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
115
39
  # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
116
40
  # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
117
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
118
- # f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
119
- # attribute will be set to nil.
41
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
42
+ # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
120
43
  def assign_multiparameter_attributes(pairs)
121
44
  execute_callstack_for_multiparameter_attributes(
122
45
  extract_callstack_for_multiparameter_attributes(pairs)
123
46
  )
124
47
  end
125
48
 
126
- def instantiate_time_object(name, values)
127
- if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
128
- Time.zone.local(*values)
129
- else
130
- Time.time_with_datetime_fallback(self.class.default_timezone, *values)
131
- end
132
- end
133
-
134
49
  def execute_callstack_for_multiparameter_attributes(callstack)
135
50
  errors = []
136
51
  callstack.each do |name, values_with_empty_parameters|
137
52
  begin
138
- send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
53
+ if values_with_empty_parameters.each_value.all?(&:nil?)
54
+ values = nil
55
+ else
56
+ values = values_with_empty_parameters
57
+ end
58
+ send("#{name}=", values)
139
59
  rescue => ex
140
- errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
60
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
141
61
  end
142
62
  end
143
63
  unless errors.empty?
144
- raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
145
- end
146
- end
147
-
148
- def read_value_from_parameter(name, values_hash_from_param)
149
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
150
- if values_hash_from_param.values.all?{|v|v.nil?}
151
- nil
152
- elsif klass == Time
153
- read_time_parameter_value(name, values_hash_from_param)
154
- elsif klass == Date
155
- read_date_parameter_value(name, values_hash_from_param)
156
- else
157
- read_other_parameter_value(klass, name, values_hash_from_param)
158
- end
159
- end
160
-
161
- def read_time_parameter_value(name, values_hash_from_param)
162
- # If Date bits were not provided, error
163
- raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
164
- max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
165
- # If Date bits were provided but blank, then return nil
166
- return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
167
-
168
- set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
169
- # If Time bits are not there, then default to 0
170
- (3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]}
171
- instantiate_time_object(name, set_values)
172
- end
173
-
174
- def read_date_parameter_value(name, values_hash_from_param)
175
- return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
176
- set_values = [values_hash_from_param[1], values_hash_from_param[2], values_hash_from_param[3]]
177
- begin
178
- Date.new(*set_values)
179
- rescue ArgumentError # if Date.new raises an exception on an invalid date
180
- instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
64
+ error_descriptions = errors.map(&:message).join(",")
65
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
181
66
  end
182
67
  end
183
68
 
184
- def read_other_parameter_value(klass, name, values_hash_from_param)
185
- max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
186
- values = (1..max_position).collect do |position|
187
- raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
188
- values_hash_from_param[position]
189
- end
190
- klass.new(*values)
191
- end
192
-
193
- def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
194
- [values_hash_from_param.keys.max,upper_cap].min
195
- end
196
-
197
69
  def extract_callstack_for_multiparameter_attributes(pairs)
198
- attributes = { }
70
+ attributes = {}
199
71
 
200
- pairs.each do |pair|
201
- multiparameter_name, value = pair
72
+ pairs.each do |(multiparameter_name, value)|
202
73
  attribute_name = multiparameter_name.split("(").first
203
- attributes[attribute_name] = {} unless attributes.include?(attribute_name)
74
+ attributes[attribute_name] ||= {}
204
75
 
205
76
  parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
206
77
  attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
@@ -216,6 +87,5 @@ module ActiveRecord
216
87
  def find_parameter_position(multiparameter_name)
217
88
  multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
218
89
  end
219
-
220
90
  end
221
91
  end
@@ -0,0 +1,67 @@
1
+ module ActiveRecord
2
+ module AttributeDecorators # :nodoc:
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :attribute_type_decorations, instance_accessor: false # :internal:
7
+ self.attribute_type_decorations = TypeDecorator.new
8
+ end
9
+
10
+ module ClassMethods # :nodoc:
11
+ def decorate_attribute_type(column_name, decorator_name, &block)
12
+ matcher = ->(name, _) { name == column_name.to_s }
13
+ key = "_#{column_name}_#{decorator_name}"
14
+ decorate_matching_attribute_types(matcher, key, &block)
15
+ end
16
+
17
+ def decorate_matching_attribute_types(matcher, decorator_name, &block)
18
+ reload_schema_from_cache
19
+ decorator_name = decorator_name.to_s
20
+
21
+ # Create new hashes so we don't modify parent classes
22
+ self.attribute_type_decorations = attribute_type_decorations.merge(decorator_name => [matcher, block])
23
+ end
24
+
25
+ private
26
+
27
+ def load_schema!
28
+ super
29
+ attribute_types.each do |name, type|
30
+ decorated_type = attribute_type_decorations.apply(name, type)
31
+ define_attribute(name, decorated_type)
32
+ end
33
+ end
34
+ end
35
+
36
+ class TypeDecorator # :nodoc:
37
+ delegate :clear, to: :@decorations
38
+
39
+ def initialize(decorations = {})
40
+ @decorations = decorations
41
+ end
42
+
43
+ def merge(*args)
44
+ TypeDecorator.new(@decorations.merge(*args))
45
+ end
46
+
47
+ def apply(name, type)
48
+ decorations = decorators_for(name, type)
49
+ decorations.inject(type) do |new_type, block|
50
+ block.call(new_type)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def decorators_for(name, type)
57
+ matching(name, type).map(&:last)
58
+ end
59
+
60
+ def matching(name, type)
61
+ @decorations.values.select do |(matcher, _)|
62
+ matcher.call(name, type)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,30 +1,75 @@
1
1
  module ActiveRecord
2
2
  module AttributeMethods
3
+ # = Active Record Attribute Methods Before Type Cast
4
+ #
5
+ # ActiveRecord::AttributeMethods::BeforeTypeCast provides a way to
6
+ # read the value of the attributes before typecasting and deserialization.
7
+ #
8
+ # class Task < ActiveRecord::Base
9
+ # end
10
+ #
11
+ # task = Task.new(id: '1', completed_on: '2012-10-21')
12
+ # task.id # => 1
13
+ # task.completed_on # => Sun, 21 Oct 2012
14
+ #
15
+ # task.attributes_before_type_cast
16
+ # # => {"id"=>"1", "completed_on"=>"2012-10-21", ... }
17
+ # task.read_attribute_before_type_cast('id') # => "1"
18
+ # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
19
+ #
20
+ # In addition to #read_attribute_before_type_cast and #attributes_before_type_cast,
21
+ # it declares a method for all attributes with the <tt>*_before_type_cast</tt>
22
+ # suffix.
23
+ #
24
+ # task.id_before_type_cast # => "1"
25
+ # task.completed_on_before_type_cast # => "2012-10-21"
3
26
  module BeforeTypeCast
4
27
  extend ActiveSupport::Concern
5
28
 
6
29
  included do
7
30
  attribute_method_suffix "_before_type_cast"
31
+ attribute_method_suffix "_came_from_user?"
8
32
  end
9
33
 
34
+ # Returns the value of the attribute identified by +attr_name+ before
35
+ # typecasting and deserialization.
36
+ #
37
+ # class Task < ActiveRecord::Base
38
+ # end
39
+ #
40
+ # task = Task.new(id: '1', completed_on: '2012-10-21')
41
+ # task.read_attribute('id') # => 1
42
+ # task.read_attribute_before_type_cast('id') # => '1'
43
+ # task.read_attribute('completed_on') # => Sun, 21 Oct 2012
44
+ # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
45
+ # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
10
46
  def read_attribute_before_type_cast(attr_name)
11
- @attributes[attr_name]
47
+ @attributes[attr_name.to_s].value_before_type_cast
12
48
  end
13
49
 
14
50
  # Returns a hash of attributes before typecasting and deserialization.
51
+ #
52
+ # class Task < ActiveRecord::Base
53
+ # end
54
+ #
55
+ # task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
56
+ # task.attributes
57
+ # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
58
+ # task.attributes_before_type_cast
59
+ # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
15
60
  def attributes_before_type_cast
16
- @attributes
61
+ @attributes.values_before_type_cast
17
62
  end
18
63
 
19
64
  private
20
65
 
21
66
  # Handle *_before_type_cast for method_missing.
22
67
  def attribute_before_type_cast(attribute_name)
23
- if attribute_name == 'id'
24
- read_attribute_before_type_cast(self.class.primary_key)
25
- else
26
- read_attribute_before_type_cast(attribute_name)
27
- end
68
+ read_attribute_before_type_cast(attribute_name)
69
+ end
70
+
71
+ def attribute_came_from_user?(attribute_name)
72
+ @attributes[attribute_name].came_from_user?
28
73
  end
29
74
  end
30
75
  end