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,212 @@
1
+ require 'active_model/forbidden_attributes_protection'
2
+
3
+ module ActiveRecord
4
+ module AttributeAssignment
5
+ extend ActiveSupport::Concern
6
+ include ActiveModel::ForbiddenAttributesProtection
7
+
8
+ # Allows you to set all the attributes by passing in a hash of attributes with
9
+ # keys matching the attribute names (which again matches the column names).
10
+ #
11
+ # If the passed hash responds to <tt>permitted?</tt> method and the return value
12
+ # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
13
+ # exception is raised.
14
+ #
15
+ # cat = Cat.new(name: "Gorby", status: "yawning")
16
+ # cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
17
+ # cat.assign_attributes(status: "sleeping")
18
+ # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
19
+ #
20
+ # New attributes will be persisted in the database when the object is saved.
21
+ #
22
+ # Aliased to <tt>attributes=</tt>.
23
+ def assign_attributes(new_attributes)
24
+ if !new_attributes.respond_to?(:stringify_keys)
25
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
26
+ end
27
+ return if new_attributes.blank?
28
+
29
+ attributes = new_attributes.stringify_keys
30
+ multi_parameter_attributes = []
31
+ nested_parameter_attributes = []
32
+
33
+ attributes = sanitize_for_mass_assignment(attributes)
34
+
35
+ attributes.each do |k, v|
36
+ if k.include?("(")
37
+ multi_parameter_attributes << [ k, v ]
38
+ elsif v.is_a?(Hash)
39
+ nested_parameter_attributes << [ k, v ]
40
+ else
41
+ _assign_attribute(k, v)
42
+ end
43
+ end
44
+
45
+ assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
46
+ assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
47
+ end
48
+
49
+ alias attributes= assign_attributes
50
+
51
+ private
52
+
53
+ def _assign_attribute(k, v)
54
+ public_send("#{k}=", v)
55
+ rescue NoMethodError
56
+ if respond_to?("#{k}=")
57
+ raise
58
+ else
59
+ raise UnknownAttributeError.new(self, k)
60
+ end
61
+ end
62
+
63
+ # Assign any deferred nested attributes after the base attributes have been set.
64
+ def assign_nested_parameter_attributes(pairs)
65
+ pairs.each { |k, v| _assign_attribute(k, v) }
66
+ end
67
+
68
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
69
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
70
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
71
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
72
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
73
+ # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
74
+ def assign_multiparameter_attributes(pairs)
75
+ execute_callstack_for_multiparameter_attributes(
76
+ extract_callstack_for_multiparameter_attributes(pairs)
77
+ )
78
+ end
79
+
80
+ def execute_callstack_for_multiparameter_attributes(callstack)
81
+ errors = []
82
+ callstack.each do |name, values_with_empty_parameters|
83
+ begin
84
+ send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
85
+ rescue => ex
86
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
87
+ end
88
+ end
89
+ unless errors.empty?
90
+ error_descriptions = errors.map { |ex| ex.message }.join(",")
91
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
92
+ end
93
+ end
94
+
95
+ def extract_callstack_for_multiparameter_attributes(pairs)
96
+ attributes = {}
97
+
98
+ pairs.each do |(multiparameter_name, value)|
99
+ attribute_name = multiparameter_name.split("(").first
100
+ attributes[attribute_name] ||= {}
101
+
102
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
103
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
104
+ end
105
+
106
+ attributes
107
+ end
108
+
109
+ def type_cast_attribute_value(multiparameter_name, value)
110
+ multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
111
+ end
112
+
113
+ def find_parameter_position(multiparameter_name)
114
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
115
+ end
116
+
117
+ class MultiparameterAttribute #:nodoc:
118
+ attr_reader :object, :name, :values, :cast_type
119
+
120
+ def initialize(object, name, values)
121
+ @object = object
122
+ @name = name
123
+ @values = values
124
+ end
125
+
126
+ def read_value
127
+ return if values.values.compact.empty?
128
+
129
+ @cast_type = object.type_for_attribute(name)
130
+ klass = cast_type.klass
131
+
132
+ if klass == Time
133
+ read_time
134
+ elsif klass == Date
135
+ read_date
136
+ else
137
+ read_other
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ def instantiate_time_object(set_values)
144
+ if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type)
145
+ Time.zone.local(*set_values)
146
+ else
147
+ Time.send(object.class.default_timezone, *set_values)
148
+ end
149
+ end
150
+
151
+ def read_time
152
+ # If column is a :time (and not :date or :datetime) there is no need to validate if
153
+ # there are year/month/day fields
154
+ if cast_type.type == :time
155
+ # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
156
+ { 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
157
+ values[key] ||= value
158
+ end
159
+ else
160
+ # else column is a timestamp, so if Date bits were not provided, error
161
+ validate_required_parameters!([1,2,3])
162
+
163
+ # If Date bits were provided but blank, then return nil
164
+ return if blank_date_parameter?
165
+ end
166
+
167
+ max_position = extract_max_param(6)
168
+ set_values = values.values_at(*(1..max_position))
169
+ # If Time bits are not there, then default to 0
170
+ (3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
171
+ instantiate_time_object(set_values)
172
+ end
173
+
174
+ def read_date
175
+ return if blank_date_parameter?
176
+ set_values = values.values_at(1,2,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(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
181
+ end
182
+ end
183
+
184
+ def read_other
185
+ max_position = extract_max_param
186
+ positions = (1..max_position)
187
+ validate_required_parameters!(positions)
188
+
189
+ values.slice(*positions)
190
+ end
191
+
192
+ # Checks whether some blank date parameter exists. Note that this is different
193
+ # than the validate_required_parameters! method, since it just checks for blank
194
+ # positions instead of missing ones, and does not raise in case one blank position
195
+ # exists. The caller is responsible to handle the case of this returning true.
196
+ def blank_date_parameter?
197
+ (1..3).any? { |position| values[position].blank? }
198
+ end
199
+
200
+ # If some position is not provided, it errors out a missing parameter exception.
201
+ def validate_required_parameters!(positions)
202
+ if missing_parameter = positions.detect { |position| !values.key?(position) }
203
+ raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
204
+ end
205
+ end
206
+
207
+ def extract_max_param(upper_cap = 100)
208
+ [values.keys.max, upper_cap].min
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,66 @@
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
+ clear_caches_calculated_from_columns
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 add_user_provided_columns(*)
28
+ super.map do |column|
29
+ decorated_type = attribute_type_decorations.apply(column.name, column.cast_type)
30
+ column.with_type(decorated_type)
31
+ end
32
+ end
33
+ end
34
+
35
+ class TypeDecorator # :nodoc:
36
+ delegate :clear, to: :@decorations
37
+
38
+ def initialize(decorations = {})
39
+ @decorations = decorations
40
+ end
41
+
42
+ def merge(*args)
43
+ TypeDecorator.new(@decorations.merge(*args))
44
+ end
45
+
46
+ def apply(name, type)
47
+ decorations = decorators_for(name, type)
48
+ decorations.inject(type) do |new_type, block|
49
+ block.call(new_type)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def decorators_for(name, type)
56
+ matching(name, type).map(&:last)
57
+ end
58
+
59
+ def matching(name, type)
60
+ @decorations.values.select do |(matcher, _)|
61
+ matcher.call(name, type)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,439 @@
1
+ require 'active_support/core_ext/enumerable'
2
+ require 'active_support/core_ext/string/filters'
3
+ require 'mutex_m'
4
+ require 'thread_safe'
5
+
6
+ module ActiveRecord
7
+ # = Active Record Attribute Methods
8
+ module AttributeMethods
9
+ extend ActiveSupport::Concern
10
+ include ActiveModel::AttributeMethods
11
+
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
51
+ end
52
+
53
+ private
54
+
55
+ # Override this method in the subclasses for method body.
56
+ def method_body(method_name, const_name)
57
+ raise NotImplementedError, "Subclasses must implement a method_body(method_name, const_name) method."
58
+ end
59
+ end
60
+
61
+ class GeneratedAttributeMethods < Module; end # :nodoc:
62
+
63
+ module ClassMethods
64
+ def inherited(child_class) #:nodoc:
65
+ child_class.initialize_generated_modules
66
+ super
67
+ end
68
+
69
+ def initialize_generated_modules # :nodoc:
70
+ @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m }
71
+ @attribute_methods_generated = false
72
+ include @generated_attribute_methods
73
+
74
+ super
75
+ end
76
+
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
113
+ def instance_method_already_implemented?(method_name)
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
216
+ end
217
+ end
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
235
+ def respond_to?(name, include_private = false)
236
+ return false unless super
237
+ name = name.to_s
238
+
239
+ # If the result is true then check for the select case.
240
+ # For queries selecting a subset of columns, return false for unselected columns.
241
+ # We check defined?(@attributes) not to issue warnings if called on objects that
242
+ # have been allocated but not yet initialized.
243
+ if defined?(@attributes) && self.class.column_names.include?(name)
244
+ return has_attribute?(name)
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, Array attributes are truncated up to 10 values.
291
+ # Other attributes return the value of <tt>#inspect</tt> without
292
+ # modification.
293
+ #
294
+ # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
295
+ #
296
+ # person.attribute_for_inspect(:name)
297
+ # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
298
+ #
299
+ # person.attribute_for_inspect(:created_at)
300
+ # # => "\"2012-10-22 00:15:07\""
301
+ #
302
+ # person.attribute_for_inspect(:tag_ids)
303
+ # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
304
+ def attribute_for_inspect(attr_name)
305
+ value = read_attribute(attr_name)
306
+
307
+ if value.is_a?(String) && value.length > 50
308
+ "#{value[0, 50]}...".inspect
309
+ elsif value.is_a?(Date) || value.is_a?(Time)
310
+ %("#{value.to_s(:db)}")
311
+ elsif value.is_a?(Array) && value.size > 10
312
+ inspected = value.first(10).inspect
313
+ %(#{inspected[0...-1]}, ...])
314
+ else
315
+ value.inspect
316
+ end
317
+ end
318
+
319
+ # Returns +true+ if the specified +attribute+ has been set by the user or by a
320
+ # database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
321
+ # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
322
+ # Note that it always returns +true+ with boolean attributes.
323
+ #
324
+ # class Task < ActiveRecord::Base
325
+ # end
326
+ #
327
+ # task = Task.new(title: '', is_done: false)
328
+ # task.attribute_present?(:title) # => false
329
+ # task.attribute_present?(:is_done) # => true
330
+ # task.title = 'Buy milk'
331
+ # task.is_done = true
332
+ # task.attribute_present?(:title) # => true
333
+ # task.attribute_present?(:is_done) # => true
334
+ def attribute_present?(attribute)
335
+ value = _read_attribute(attribute)
336
+ !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
337
+ end
338
+
339
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
340
+ # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
341
+ # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
342
+ #
343
+ # Note: +:id+ is always present.
344
+ #
345
+ # Alias for the <tt>read_attribute</tt> method.
346
+ #
347
+ # class Person < ActiveRecord::Base
348
+ # belongs_to :organization
349
+ # end
350
+ #
351
+ # person = Person.new(name: 'Francesco', age: '22')
352
+ # person[:name] # => "Francesco"
353
+ # person[:age] # => 22
354
+ #
355
+ # person = Person.select('id').first
356
+ # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
357
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
358
+ def [](attr_name)
359
+ read_attribute(attr_name) { |n| missing_attribute(n, caller) }
360
+ end
361
+
362
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
363
+ # (Alias for the protected <tt>write_attribute</tt> method).
364
+ #
365
+ # class Person < ActiveRecord::Base
366
+ # end
367
+ #
368
+ # person = Person.new
369
+ # person[:age] = '22'
370
+ # person[:age] # => 22
371
+ # person[:age] # => Fixnum
372
+ def []=(attr_name, value)
373
+ write_attribute(attr_name, value)
374
+ end
375
+
376
+ protected
377
+
378
+ def clone_attribute_value(reader_method, attribute_name) # :nodoc:
379
+ value = send(reader_method, attribute_name)
380
+ value.duplicable? ? value.clone : value
381
+ rescue TypeError, NoMethodError
382
+ value
383
+ end
384
+
385
+ def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
386
+ arel_attributes_with_values(attributes_for_create(attribute_names))
387
+ end
388
+
389
+ def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
390
+ arel_attributes_with_values(attributes_for_update(attribute_names))
391
+ end
392
+
393
+ def attribute_method?(attr_name) # :nodoc:
394
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
395
+ defined?(@attributes) && @attributes.key?(attr_name)
396
+ end
397
+
398
+ private
399
+
400
+ # Returns a Hash of the Arel::Attributes and attribute values that have been
401
+ # typecasted for use in an Arel insert/update method.
402
+ def arel_attributes_with_values(attribute_names)
403
+ attrs = {}
404
+ arel_table = self.class.arel_table
405
+
406
+ attribute_names.each do |name|
407
+ attrs[arel_table[name]] = typecasted_attribute_value(name)
408
+ end
409
+ attrs
410
+ end
411
+
412
+ # Filters the primary keys and readonly attributes from the attribute names.
413
+ def attributes_for_update(attribute_names)
414
+ attribute_names.reject do |name|
415
+ readonly_attribute?(name)
416
+ end
417
+ end
418
+
419
+ # Filters out the primary keys, from the attribute names, when the primary
420
+ # key is to be generated (e.g. the id attribute has no value).
421
+ def attributes_for_create(attribute_names)
422
+ attribute_names.reject do |name|
423
+ pk_attribute?(name) && id.nil?
424
+ end
425
+ end
426
+
427
+ def readonly_attribute?(name)
428
+ self.class.readonly_attributes.include?(name)
429
+ end
430
+
431
+ def pk_attribute?(name)
432
+ name == self.class.primary_key
433
+ end
434
+
435
+ def typecasted_attribute_value(name)
436
+ _read_attribute(name)
437
+ end
438
+ end
439
+ end