activerecord 1.0.0 → 4.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 (255) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +213 -0
  5. data/examples/performance.rb +172 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +180 -84
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +248 -0
  10. data/lib/active_record/associations/association_scope.rb +135 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +92 -0
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
  13. data/lib/active_record/associations/builder/association.rb +108 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  17. data/lib/active_record/associations/builder/has_many.rb +15 -0
  18. data/lib/active_record/associations/builder/has_one.rb +25 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +608 -0
  21. data/lib/active_record/associations/collection_proxy.rb +986 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
  23. data/lib/active_record/associations/has_many_association.rb +116 -85
  24. data/lib/active_record/associations/has_many_through_association.rb +197 -0
  25. data/lib/active_record/associations/has_one_association.rb +102 -0
  26. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +235 -0
  31. data/lib/active_record/associations/join_helper.rb +45 -0
  32. data/lib/active_record/associations/preloader/association.rb +121 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  42. data/lib/active_record/associations/preloader.rb +178 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +1437 -431
  46. data/lib/active_record/attribute_assignment.rb +201 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
  48. data/lib/active_record/attribute_methods/dirty.rb +118 -0
  49. data/lib/active_record/attribute_methods/primary_key.rb +122 -0
  50. data/lib/active_record/attribute_methods/query.rb +40 -0
  51. data/lib/active_record/attribute_methods/read.rb +107 -0
  52. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  53. data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
  54. data/lib/active_record/attribute_methods/write.rb +63 -0
  55. data/lib/active_record/attribute_methods.rb +393 -0
  56. data/lib/active_record/autosave_association.rb +426 -0
  57. data/lib/active_record/base.rb +268 -930
  58. data/lib/active_record/callbacks.rb +203 -230
  59. data/lib/active_record/coders/yaml_column.rb +38 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  71. data/lib/active_record/connection_adapters/column.rb +318 -0
  72. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
  75. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  76. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  77. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  81. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  82. data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
  83. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  84. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
  85. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  86. data/lib/active_record/connection_handling.rb +98 -0
  87. data/lib/active_record/core.rb +463 -0
  88. data/lib/active_record/counter_cache.rb +122 -0
  89. data/lib/active_record/dynamic_matchers.rb +131 -0
  90. data/lib/active_record/errors.rb +213 -0
  91. data/lib/active_record/explain.rb +38 -0
  92. data/lib/active_record/explain_registry.rb +30 -0
  93. data/lib/active_record/explain_subscriber.rb +29 -0
  94. data/lib/active_record/fixture_set/file.rb +55 -0
  95. data/lib/active_record/fixtures.rb +892 -138
  96. data/lib/active_record/inheritance.rb +200 -0
  97. data/lib/active_record/integration.rb +60 -0
  98. data/lib/active_record/locale/en.yml +47 -0
  99. data/lib/active_record/locking/optimistic.rb +181 -0
  100. data/lib/active_record/locking/pessimistic.rb +77 -0
  101. data/lib/active_record/log_subscriber.rb +82 -0
  102. data/lib/active_record/migration/command_recorder.rb +164 -0
  103. data/lib/active_record/migration/join_table.rb +15 -0
  104. data/lib/active_record/migration.rb +1015 -0
  105. data/lib/active_record/model_schema.rb +345 -0
  106. data/lib/active_record/nested_attributes.rb +546 -0
  107. data/lib/active_record/null_relation.rb +65 -0
  108. data/lib/active_record/persistence.rb +509 -0
  109. data/lib/active_record/query_cache.rb +56 -0
  110. data/lib/active_record/querying.rb +62 -0
  111. data/lib/active_record/railtie.rb +205 -0
  112. data/lib/active_record/railties/console_sandbox.rb +5 -0
  113. data/lib/active_record/railties/controller_runtime.rb +50 -0
  114. data/lib/active_record/railties/databases.rake +402 -0
  115. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  116. data/lib/active_record/readonly_attributes.rb +30 -0
  117. data/lib/active_record/reflection.rb +544 -87
  118. data/lib/active_record/relation/batches.rb +93 -0
  119. data/lib/active_record/relation/calculations.rb +399 -0
  120. data/lib/active_record/relation/delegation.rb +125 -0
  121. data/lib/active_record/relation/finder_methods.rb +349 -0
  122. data/lib/active_record/relation/merger.rb +161 -0
  123. data/lib/active_record/relation/predicate_builder.rb +106 -0
  124. data/lib/active_record/relation/query_methods.rb +1044 -0
  125. data/lib/active_record/relation/spawn_methods.rb +73 -0
  126. data/lib/active_record/relation.rb +655 -0
  127. data/lib/active_record/result.rb +67 -0
  128. data/lib/active_record/runtime_registry.rb +17 -0
  129. data/lib/active_record/sanitization.rb +168 -0
  130. data/lib/active_record/schema.rb +65 -0
  131. data/lib/active_record/schema_dumper.rb +204 -0
  132. data/lib/active_record/schema_migration.rb +39 -0
  133. data/lib/active_record/scoping/default.rb +146 -0
  134. data/lib/active_record/scoping/named.rb +175 -0
  135. data/lib/active_record/scoping.rb +82 -0
  136. data/lib/active_record/serialization.rb +22 -0
  137. data/lib/active_record/serializers/xml_serializer.rb +197 -0
  138. data/lib/active_record/statement_cache.rb +26 -0
  139. data/lib/active_record/store.rb +156 -0
  140. data/lib/active_record/tasks/database_tasks.rb +203 -0
  141. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  142. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  143. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  146. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  147. data/lib/active_record/test_case.rb +96 -0
  148. data/lib/active_record/timestamp.rb +119 -0
  149. data/lib/active_record/transactions.rb +366 -69
  150. data/lib/active_record/translation.rb +22 -0
  151. data/lib/active_record/validations/associated.rb +49 -0
  152. data/lib/active_record/validations/presence.rb +65 -0
  153. data/lib/active_record/validations/uniqueness.rb +225 -0
  154. data/lib/active_record/validations.rb +64 -185
  155. data/lib/active_record/version.rb +11 -0
  156. data/lib/active_record.rb +149 -24
  157. data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
  158. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  159. data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
  160. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  161. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  162. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  163. data/lib/rails/generators/active_record.rb +23 -0
  164. metadata +261 -161
  165. data/CHANGELOG +0 -581
  166. data/README +0 -361
  167. data/RUNNING_UNIT_TESTS +0 -36
  168. data/dev-utils/eval_debugger.rb +0 -9
  169. data/examples/associations.png +0 -0
  170. data/examples/associations.rb +0 -87
  171. data/examples/shared_setup.rb +0 -15
  172. data/examples/validation.rb +0 -88
  173. data/install.rb +0 -60
  174. data/lib/active_record/associations/association_collection.rb +0 -70
  175. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
  176. data/lib/active_record/deprecated_associations.rb +0 -70
  177. data/lib/active_record/observer.rb +0 -71
  178. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  179. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  180. data/lib/active_record/support/clean_logger.rb +0 -10
  181. data/lib/active_record/support/inflector.rb +0 -70
  182. data/lib/active_record/vendor/mysql.rb +0 -1117
  183. data/lib/active_record/vendor/simple.rb +0 -702
  184. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  185. data/lib/active_record/wrappings.rb +0 -59
  186. data/rakefile +0 -122
  187. data/test/abstract_unit.rb +0 -16
  188. data/test/aggregations_test.rb +0 -34
  189. data/test/all.sh +0 -8
  190. data/test/associations_test.rb +0 -477
  191. data/test/base_test.rb +0 -513
  192. data/test/class_inheritable_attributes_test.rb +0 -33
  193. data/test/connections/native_mysql/connection.rb +0 -24
  194. data/test/connections/native_postgresql/connection.rb +0 -24
  195. data/test/connections/native_sqlite/connection.rb +0 -24
  196. data/test/deprecated_associations_test.rb +0 -336
  197. data/test/finder_test.rb +0 -67
  198. data/test/fixtures/accounts/signals37 +0 -3
  199. data/test/fixtures/accounts/unknown +0 -2
  200. data/test/fixtures/auto_id.rb +0 -4
  201. data/test/fixtures/column_name.rb +0 -3
  202. data/test/fixtures/companies/first_client +0 -6
  203. data/test/fixtures/companies/first_firm +0 -4
  204. data/test/fixtures/companies/second_client +0 -6
  205. data/test/fixtures/company.rb +0 -37
  206. data/test/fixtures/company_in_module.rb +0 -33
  207. data/test/fixtures/course.rb +0 -3
  208. data/test/fixtures/courses/java +0 -2
  209. data/test/fixtures/courses/ruby +0 -2
  210. data/test/fixtures/customer.rb +0 -30
  211. data/test/fixtures/customers/david +0 -6
  212. data/test/fixtures/db_definitions/mysql.sql +0 -96
  213. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  214. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  215. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  216. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  217. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  218. data/test/fixtures/default.rb +0 -2
  219. data/test/fixtures/developer.rb +0 -8
  220. data/test/fixtures/developers/david +0 -2
  221. data/test/fixtures/developers/jamis +0 -2
  222. data/test/fixtures/developers_projects/david_action_controller +0 -2
  223. data/test/fixtures/developers_projects/david_active_record +0 -2
  224. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  225. data/test/fixtures/entrant.rb +0 -3
  226. data/test/fixtures/entrants/first +0 -3
  227. data/test/fixtures/entrants/second +0 -3
  228. data/test/fixtures/entrants/third +0 -3
  229. data/test/fixtures/fixture_database.sqlite +0 -0
  230. data/test/fixtures/fixture_database_2.sqlite +0 -0
  231. data/test/fixtures/movie.rb +0 -5
  232. data/test/fixtures/movies/first +0 -2
  233. data/test/fixtures/movies/second +0 -2
  234. data/test/fixtures/project.rb +0 -3
  235. data/test/fixtures/projects/action_controller +0 -2
  236. data/test/fixtures/projects/active_record +0 -2
  237. data/test/fixtures/reply.rb +0 -21
  238. data/test/fixtures/subscriber.rb +0 -5
  239. data/test/fixtures/subscribers/first +0 -2
  240. data/test/fixtures/subscribers/second +0 -2
  241. data/test/fixtures/topic.rb +0 -20
  242. data/test/fixtures/topics/first +0 -9
  243. data/test/fixtures/topics/second +0 -8
  244. data/test/fixtures_test.rb +0 -20
  245. data/test/inflector_test.rb +0 -104
  246. data/test/inheritance_test.rb +0 -125
  247. data/test/lifecycle_test.rb +0 -110
  248. data/test/modules_test.rb +0 -21
  249. data/test/multiple_db_test.rb +0 -46
  250. data/test/pk_test.rb +0 -57
  251. data/test/reflection_test.rb +0 -78
  252. data/test/thread_safety_test.rb +0 -33
  253. data/test/transactions_test.rb +0 -83
  254. data/test/unconnected_test.rb +0 -24
  255. data/test/validations_test.rb +0 -126
@@ -0,0 +1,201 @@
1
+
2
+ module ActiveRecord
3
+ module AttributeAssignment
4
+ extend ActiveSupport::Concern
5
+ include ActiveModel::DeprecatedMassAssignmentSecurity
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
+ def assign_attributes(new_attributes)
15
+ return if new_attributes.blank?
16
+
17
+ attributes = new_attributes.stringify_keys
18
+ multi_parameter_attributes = []
19
+ nested_parameter_attributes = []
20
+
21
+ attributes = sanitize_for_mass_assignment(attributes)
22
+
23
+ attributes.each do |k, v|
24
+ if k.include?("(")
25
+ multi_parameter_attributes << [ k, v ]
26
+ elsif v.is_a?(Hash)
27
+ nested_parameter_attributes << [ k, v ]
28
+ else
29
+ _assign_attribute(k, v)
30
+ end
31
+ end
32
+
33
+ assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
34
+ assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
35
+ end
36
+
37
+ alias attributes= assign_attributes
38
+
39
+ private
40
+
41
+ def _assign_attribute(k, v)
42
+ public_send("#{k}=", v)
43
+ rescue NoMethodError
44
+ if respond_to?("#{k}=")
45
+ raise
46
+ else
47
+ raise UnknownAttributeError, "unknown attribute: #{k}"
48
+ end
49
+ end
50
+
51
+ # Assign any deferred nested attributes after the base attributes have been set.
52
+ def assign_nested_parameter_attributes(pairs)
53
+ pairs.each { |k, v| _assign_attribute(k, v) }
54
+ end
55
+
56
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
57
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
58
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
59
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
60
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
61
+ # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
62
+ def assign_multiparameter_attributes(pairs)
63
+ execute_callstack_for_multiparameter_attributes(
64
+ extract_callstack_for_multiparameter_attributes(pairs)
65
+ )
66
+ end
67
+
68
+ def execute_callstack_for_multiparameter_attributes(callstack)
69
+ errors = []
70
+ callstack.each do |name, values_with_empty_parameters|
71
+ begin
72
+ send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
73
+ rescue => ex
74
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
75
+ end
76
+ end
77
+ unless errors.empty?
78
+ error_descriptions = errors.map { |ex| ex.message }.join(",")
79
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
80
+ end
81
+ end
82
+
83
+ def extract_callstack_for_multiparameter_attributes(pairs)
84
+ attributes = {}
85
+
86
+ pairs.each do |(multiparameter_name, value)|
87
+ attribute_name = multiparameter_name.split("(").first
88
+ attributes[attribute_name] ||= {}
89
+
90
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
91
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
92
+ end
93
+
94
+ attributes
95
+ end
96
+
97
+ def type_cast_attribute_value(multiparameter_name, value)
98
+ multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
99
+ end
100
+
101
+ def find_parameter_position(multiparameter_name)
102
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
103
+ end
104
+
105
+ class MultiparameterAttribute #:nodoc:
106
+ attr_reader :object, :name, :values, :column
107
+
108
+ def initialize(object, name, values)
109
+ @object = object
110
+ @name = name
111
+ @values = values
112
+ end
113
+
114
+ def read_value
115
+ return if values.values.compact.empty?
116
+
117
+ @column = object.class.reflect_on_aggregation(name.to_sym) || object.column_for_attribute(name)
118
+ klass = column.klass
119
+
120
+ if klass == Time
121
+ read_time
122
+ elsif klass == Date
123
+ read_date
124
+ else
125
+ read_other(klass)
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ def instantiate_time_object(set_values)
132
+ if object.class.send(:create_time_zone_conversion_attribute?, name, column)
133
+ Time.zone.local(*set_values)
134
+ else
135
+ Time.send(object.class.default_timezone, *set_values)
136
+ end
137
+ end
138
+
139
+ def read_time
140
+ # If column is a :time (and not :date or :timestamp) there is no need to validate if
141
+ # there are year/month/day fields
142
+ if column.type == :time
143
+ # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
144
+ { 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
145
+ values[key] ||= value
146
+ end
147
+ else
148
+ # else column is a timestamp, so if Date bits were not provided, error
149
+ validate_required_parameters!([1,2,3])
150
+
151
+ # If Date bits were provided but blank, then return nil
152
+ return if blank_date_parameter?
153
+ end
154
+
155
+ max_position = extract_max_param(6)
156
+ set_values = values.values_at(*(1..max_position))
157
+ # If Time bits are not there, then default to 0
158
+ (3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
159
+ instantiate_time_object(set_values)
160
+ end
161
+
162
+ def read_date
163
+ return if blank_date_parameter?
164
+ set_values = values.values_at(1,2,3)
165
+ begin
166
+ Date.new(*set_values)
167
+ rescue ArgumentError # if Date.new raises an exception on an invalid date
168
+ 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
169
+ end
170
+ end
171
+
172
+ def read_other(klass)
173
+ max_position = extract_max_param
174
+ positions = (1..max_position)
175
+ validate_required_parameters!(positions)
176
+
177
+ set_values = values.values_at(*positions)
178
+ klass.new(*set_values)
179
+ end
180
+
181
+ # Checks whether some blank date parameter exists. Note that this is different
182
+ # than the validate_required_parameters! method, since it just checks for blank
183
+ # positions instead of missing ones, and does not raise in case one blank position
184
+ # exists. The caller is responsible to handle the case of this returning true.
185
+ def blank_date_parameter?
186
+ (1..3).any? { |position| values[position].blank? }
187
+ end
188
+
189
+ # If some position is not provided, it errors out a missing parameter exception.
190
+ def validate_required_parameters!(positions)
191
+ if missing_parameter = positions.detect { |position| !values.key?(position) }
192
+ raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
193
+ end
194
+ end
195
+
196
+ def extract_max_param(upper_cap = 100)
197
+ [values.keys.max, upper_cap].min
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,70 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ # = Active Record Attribute Methods Before Type Cast
4
+ #
5
+ # <tt>ActiveRecord::AttributeMethods::BeforeTypeCast</tt> 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"
26
+ module BeforeTypeCast
27
+ extend ActiveSupport::Concern
28
+
29
+ included do
30
+ attribute_method_suffix "_before_type_cast"
31
+ end
32
+
33
+ # Returns the value of the attribute identified by +attr_name+ before
34
+ # typecasting and deserialization.
35
+ #
36
+ # class Task < ActiveRecord::Base
37
+ # end
38
+ #
39
+ # task = Task.new(id: '1', completed_on: '2012-10-21')
40
+ # task.read_attribute('id') # => 1
41
+ # task.read_attribute_before_type_cast('id') # => '1'
42
+ # task.read_attribute('completed_on') # => Sun, 21 Oct 2012
43
+ # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
44
+ def read_attribute_before_type_cast(attr_name)
45
+ @attributes[attr_name]
46
+ end
47
+
48
+ # Returns a hash of attributes before typecasting and deserialization.
49
+ #
50
+ # class Task < ActiveRecord::Base
51
+ # end
52
+ #
53
+ # task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
54
+ # task.attributes
55
+ # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
56
+ # task.attributes_before_type_cast
57
+ # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
58
+ def attributes_before_type_cast
59
+ @attributes
60
+ end
61
+
62
+ private
63
+
64
+ # Handle *_before_type_cast for method_missing.
65
+ def attribute_before_type_cast(attribute_name)
66
+ read_attribute_before_type_cast(attribute_name)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,118 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+
3
+ module ActiveRecord
4
+ module AttributeMethods
5
+ module Dirty # :nodoc:
6
+ extend ActiveSupport::Concern
7
+
8
+ include ActiveModel::Dirty
9
+
10
+ included do
11
+ if self < ::ActiveRecord::Timestamp
12
+ raise "You cannot include Dirty after Timestamp"
13
+ end
14
+
15
+ class_attribute :partial_writes, instance_writer: false
16
+ self.partial_writes = true
17
+
18
+ def self.partial_updates=(v); self.partial_writes = v; end
19
+ def self.partial_updates?; partial_writes?; end
20
+ def self.partial_updates; partial_writes; end
21
+
22
+ ActiveSupport::Deprecation.deprecate_methods(
23
+ singleton_class,
24
+ :partial_updates= => :partial_writes=,
25
+ :partial_updates? => :partial_writes?,
26
+ :partial_updates => :partial_writes
27
+ )
28
+ end
29
+
30
+ # Attempts to +save+ the record and clears changed attributes if successful.
31
+ def save(*)
32
+ if status = super
33
+ @previously_changed = changes
34
+ @changed_attributes.clear
35
+ end
36
+ status
37
+ end
38
+
39
+ # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
40
+ def save!(*)
41
+ super.tap do
42
+ @previously_changed = changes
43
+ @changed_attributes.clear
44
+ end
45
+ end
46
+
47
+ # <tt>reload</tt> the record and clears changed attributes.
48
+ def reload(*)
49
+ super.tap do
50
+ @previously_changed.clear
51
+ @changed_attributes.clear
52
+ end
53
+ end
54
+
55
+ private
56
+ # Wrap write_attribute to remember original attribute value.
57
+ def write_attribute(attr, value)
58
+ attr = attr.to_s
59
+
60
+ # The attribute already has an unsaved change.
61
+ if attribute_changed?(attr)
62
+ old = @changed_attributes[attr]
63
+ @changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
64
+ else
65
+ old = clone_attribute_value(:read_attribute, attr)
66
+ @changed_attributes[attr] = old if _field_changed?(attr, old, value)
67
+ end
68
+
69
+ # Carry on.
70
+ super(attr, value)
71
+ end
72
+
73
+ def update_record(*)
74
+ partial_writes? ? super(keys_for_partial_write) : super
75
+ end
76
+
77
+ def create_record(*)
78
+ partial_writes? ? super(keys_for_partial_write) : super
79
+ end
80
+
81
+ # Serialized attributes should always be written in case they've been
82
+ # changed in place.
83
+ def keys_for_partial_write
84
+ changed | (attributes.keys & self.class.serialized_attributes.keys)
85
+ end
86
+
87
+ def _field_changed?(attr, old, value)
88
+ if column = column_for_attribute(attr)
89
+ if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
90
+ changes_from_zero_to_string?(old, value))
91
+ value = nil
92
+ else
93
+ value = column.type_cast(value)
94
+ end
95
+ end
96
+
97
+ old != value
98
+ end
99
+
100
+ def changes_from_nil_to_empty_string?(column, old, value)
101
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
102
+ # Hence we don't record it as a change if the value changes from nil to ''.
103
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
104
+ # be typecast back to 0 (''.to_i => 0)
105
+ column.null && (old.nil? || old == 0) && value.blank?
106
+ end
107
+
108
+ def changes_from_zero_to_string?(old, value)
109
+ # For columns with old 0 and value non-empty string
110
+ old == 0 && value.is_a?(String) && value.present? && non_zero?(value)
111
+ end
112
+
113
+ def non_zero?(value)
114
+ value !~ /\A0+(\.0+)?\z/
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,122 @@
1
+ require 'set'
2
+
3
+ module ActiveRecord
4
+ module AttributeMethods
5
+ module PrimaryKey
6
+ extend ActiveSupport::Concern
7
+
8
+ # Returns this record's primary key value wrapped in an Array if one is
9
+ # available.
10
+ def to_key
11
+ sync_with_transaction_state
12
+ key = self.id
13
+ [key] if key
14
+ end
15
+
16
+ # Returns the primary key value.
17
+ def id
18
+ sync_with_transaction_state
19
+ read_attribute(self.class.primary_key)
20
+ end
21
+
22
+ # Sets the primary key value.
23
+ def id=(value)
24
+ sync_with_transaction_state
25
+ write_attribute(self.class.primary_key, value) if self.class.primary_key
26
+ end
27
+
28
+ # Queries the primary key value.
29
+ def id?
30
+ sync_with_transaction_state
31
+ query_attribute(self.class.primary_key)
32
+ end
33
+
34
+ # Returns the primary key value before type cast.
35
+ def id_before_type_cast
36
+ sync_with_transaction_state
37
+ read_attribute_before_type_cast(self.class.primary_key)
38
+ end
39
+
40
+ protected
41
+
42
+ def attribute_method?(attr_name)
43
+ attr_name == 'id' || super
44
+ end
45
+
46
+ module ClassMethods
47
+ def define_method_attribute(attr_name)
48
+ super
49
+
50
+ if attr_name == primary_key && attr_name != 'id'
51
+ generated_attribute_methods.send(:alias_method, :id, primary_key)
52
+ end
53
+ end
54
+
55
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
56
+
57
+ def dangerous_attribute_method?(method_name)
58
+ super && !ID_ATTRIBUTE_METHODS.include?(method_name)
59
+ end
60
+
61
+ # Defines the primary key field -- can be overridden in subclasses.
62
+ # Overwriting will negate any effect of the +primary_key_prefix_type+
63
+ # setting, though.
64
+ def primary_key
65
+ @primary_key = reset_primary_key unless defined? @primary_key
66
+ @primary_key
67
+ end
68
+
69
+ # Returns a quoted version of the primary key name, used to construct
70
+ # SQL statements.
71
+ def quoted_primary_key
72
+ @quoted_primary_key ||= connection.quote_column_name(primary_key)
73
+ end
74
+
75
+ def reset_primary_key #:nodoc:
76
+ if self == base_class
77
+ self.primary_key = get_primary_key(base_class.name)
78
+ else
79
+ self.primary_key = base_class.primary_key
80
+ end
81
+ end
82
+
83
+ def get_primary_key(base_name) #:nodoc:
84
+ return 'id' if base_name.blank?
85
+
86
+ case primary_key_prefix_type
87
+ when :table_name
88
+ base_name.foreign_key(false)
89
+ when :table_name_with_underscore
90
+ base_name.foreign_key
91
+ else
92
+ if ActiveRecord::Base != self && table_exists?
93
+ connection.schema_cache.primary_keys(table_name)
94
+ else
95
+ 'id'
96
+ end
97
+ end
98
+ end
99
+
100
+ # Sets the name of the primary key column.
101
+ #
102
+ # class Project < ActiveRecord::Base
103
+ # self.primary_key = 'sysid'
104
+ # end
105
+ #
106
+ # You can also define the +primary_key+ method yourself:
107
+ #
108
+ # class Project < ActiveRecord::Base
109
+ # def self.primary_key
110
+ # 'foo_' + super
111
+ # end
112
+ # end
113
+ #
114
+ # Project.primary_key # => "foo_id"
115
+ def primary_key=(value)
116
+ @primary_key = value && value.to_s
117
+ @quoted_primary_key = nil
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,40 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Query
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attribute_method_suffix "?"
8
+ end
9
+
10
+ def query_attribute(attr_name)
11
+ value = read_attribute(attr_name) { |n| missing_attribute(n, caller) }
12
+
13
+ case value
14
+ when true then true
15
+ when false, nil then false
16
+ else
17
+ column = self.class.columns_hash[attr_name]
18
+ if column.nil?
19
+ if Numeric === value || value !~ /[^0-9]/
20
+ !value.to_i.zero?
21
+ else
22
+ return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
23
+ !value.blank?
24
+ end
25
+ elsif column.number?
26
+ !value.zero?
27
+ else
28
+ !value.blank?
29
+ end
30
+ end
31
+ end
32
+
33
+ private
34
+ # Handle *? for method_missing.
35
+ def attribute?(attribute_name)
36
+ query_attribute(attribute_name)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,107 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Read
4
+ extend ActiveSupport::Concern
5
+
6
+ ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
7
+
8
+ included do
9
+ class_attribute :attribute_types_cached_by_default, instance_writer: false
10
+ self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
11
+ end
12
+
13
+ module ClassMethods
14
+ # +cache_attributes+ allows you to declare which converted attribute
15
+ # values should be cached. Usually caching only pays off for attributes
16
+ # with expensive conversion methods, like time related columns (e.g.
17
+ # +created_at+, +updated_at+).
18
+ def cache_attributes(*attribute_names)
19
+ cached_attributes.merge attribute_names.map { |attr| attr.to_s }
20
+ end
21
+
22
+ # Returns the attributes which are cached. By default time related columns
23
+ # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
24
+ def cached_attributes
25
+ @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
26
+ end
27
+
28
+ # Returns +true+ if the provided attribute is being cached.
29
+ def cache_attribute?(attr_name)
30
+ cached_attributes.include?(attr_name)
31
+ end
32
+
33
+ protected
34
+
35
+ # We want to generate the methods via module_eval rather than
36
+ # define_method, because define_method is slower on dispatch and
37
+ # uses more memory (because it creates a closure).
38
+ #
39
+ # But sometimes the database might return columns with
40
+ # characters that are not allowed in normal method names (like
41
+ # 'my_column(omg)'. So to work around this we first define with
42
+ # the __temp__ identifier, and then use alias method to rename
43
+ # it to what we want.
44
+ #
45
+ # We are also defining a constant to hold the frozen string of
46
+ # the attribute name. Using a constant means that we do not have
47
+ # to allocate an object on each call to the attribute method.
48
+ # Making it frozen means that it doesn't get duped when used to
49
+ # key the @attributes_cache in read_attribute.
50
+ def define_method_attribute(name)
51
+ safe_name = name.unpack('h*').first
52
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
53
+ def __temp__#{safe_name}
54
+ read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
55
+ end
56
+ alias_method #{name.inspect}, :__temp__#{safe_name}
57
+ undef_method :__temp__#{safe_name}
58
+ STR
59
+ end
60
+
61
+ private
62
+
63
+ def cacheable_column?(column)
64
+ if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
65
+ ! serialized_attributes.include? column.name
66
+ else
67
+ attribute_types_cached_by_default.include?(column.type)
68
+ end
69
+ end
70
+ end
71
+
72
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after
73
+ # it has been typecast (for example, "2004-12-12" in a data column is cast
74
+ # to a date object, like Date.new(2004, 12, 12)).
75
+ def read_attribute(attr_name)
76
+ # If it's cached, just return it
77
+ # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
78
+ name = attr_name.to_s
79
+ @attributes_cache[name] || @attributes_cache.fetch(name) {
80
+ column = @columns_hash.fetch(name) {
81
+ return @attributes.fetch(name) {
82
+ if name == 'id' && self.class.primary_key != name
83
+ read_attribute(self.class.primary_key)
84
+ end
85
+ }
86
+ }
87
+
88
+ value = @attributes.fetch(name) {
89
+ return block_given? ? yield(name) : nil
90
+ }
91
+
92
+ if self.class.cache_attribute?(name)
93
+ @attributes_cache[name] = column.type_cast(value)
94
+ else
95
+ column.type_cast value
96
+ end
97
+ }
98
+ end
99
+
100
+ private
101
+
102
+ def attribute(attribute_name)
103
+ read_attribute(attribute_name)
104
+ end
105
+ end
106
+ end
107
+ end