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,162 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Serialization
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # Returns a hash of all the attributes that have been specified for
8
+ # serialization as keys and their class restriction as values.
9
+ class_attribute :serialized_attributes, instance_accessor: false
10
+ self.serialized_attributes = {}
11
+ end
12
+
13
+ module ClassMethods
14
+ ##
15
+ # :method: serialized_attributes
16
+ #
17
+ # Returns a hash of all the attributes that have been specified for
18
+ # serialization as keys and their class restriction as values.
19
+
20
+ # If you have an attribute that needs to be saved to the database as an
21
+ # object, and retrieved as the same object, then specify the name of that
22
+ # attribute using this method and it will be handled automatically. The
23
+ # serialization is done through YAML. If +class_name+ is specified, the
24
+ # serialized object must be of that class on retrieval or
25
+ # <tt>SerializationTypeMismatch</tt> will be raised.
26
+ #
27
+ # ==== Parameters
28
+ #
29
+ # * +attr_name+ - The field name that should be serialized.
30
+ # * +class_name+ - Optional, class name that the object type should be equal to.
31
+ #
32
+ # ==== Example
33
+ #
34
+ # # Serialize a preferences attribute.
35
+ # class User < ActiveRecord::Base
36
+ # serialize :preferences
37
+ # end
38
+ def serialize(attr_name, class_name = Object)
39
+ include Behavior
40
+
41
+ coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
42
+ class_name
43
+ else
44
+ Coders::YAMLColumn.new(class_name)
45
+ end
46
+
47
+ # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
48
+ # has its own hash of own serialized attributes
49
+ self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
50
+ end
51
+ end
52
+
53
+ # *DEPRECATED*: Use ActiveRecord::AttributeMethods::Serialization::ClassMethods#serialized_attributes class level method instead.
54
+ def serialized_attributes
55
+ message = "Instance level serialized_attributes method is deprecated, please use class level method."
56
+ ActiveSupport::Deprecation.warn message
57
+ defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
58
+ end
59
+
60
+ class Type # :nodoc:
61
+ def initialize(column)
62
+ @column = column
63
+ end
64
+
65
+ def type_cast(value)
66
+ if value.state == :serialized
67
+ value.unserialized_value @column.type_cast value.value
68
+ else
69
+ value.unserialized_value
70
+ end
71
+ end
72
+
73
+ def type
74
+ @column.type
75
+ end
76
+ end
77
+
78
+ class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
79
+ def unserialized_value(v = value)
80
+ state == :serialized ? unserialize(v) : value
81
+ end
82
+
83
+ def serialized_value
84
+ state == :unserialized ? serialize : value
85
+ end
86
+
87
+ def unserialize(v)
88
+ self.state = :unserialized
89
+ self.value = coder.load(v)
90
+ end
91
+
92
+ def serialize
93
+ self.state = :serialized
94
+ self.value = coder.dump(value)
95
+ end
96
+ end
97
+
98
+ # This is only added to the model when serialize is called, which
99
+ # ensures we do not make things slower when serialization is not used.
100
+ module Behavior # :nodoc:
101
+ extend ActiveSupport::Concern
102
+
103
+ module ClassMethods # :nodoc:
104
+ def initialize_attributes(attributes, options = {})
105
+ serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
106
+ super(attributes, options)
107
+
108
+ serialized_attributes.each do |key, coder|
109
+ if attributes.key?(key)
110
+ attributes[key] = Attribute.new(coder, attributes[key], serialized)
111
+ end
112
+ end
113
+
114
+ attributes
115
+ end
116
+ end
117
+
118
+ def type_cast_attribute_for_write(column, value)
119
+ if column && coder = self.class.serialized_attributes[column.name]
120
+ Attribute.new(coder, value, :unserialized)
121
+ else
122
+ super
123
+ end
124
+ end
125
+
126
+ def _field_changed?(attr, old, value)
127
+ if self.class.serialized_attributes.include?(attr)
128
+ old != value
129
+ else
130
+ super
131
+ end
132
+ end
133
+
134
+ def read_attribute_before_type_cast(attr_name)
135
+ if self.class.serialized_attributes.include?(attr_name)
136
+ super.unserialized_value
137
+ else
138
+ super
139
+ end
140
+ end
141
+
142
+ def attributes_before_type_cast
143
+ super.dup.tap do |attributes|
144
+ self.class.serialized_attributes.each_key do |key|
145
+ if attributes.key?(key)
146
+ attributes[key] = attributes[key].unserialized_value
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ def typecasted_attribute_value(name)
153
+ if self.class.serialized_attributes.include?(name)
154
+ @attributes[name].serialized_value
155
+ else
156
+ super
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,59 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module TimeZoneConversion
4
+ class Type # :nodoc:
5
+ def initialize(column)
6
+ @column = column
7
+ end
8
+
9
+ def type_cast(value)
10
+ value = @column.type_cast(value)
11
+ value.acts_like?(:time) ? value.in_time_zone : value
12
+ end
13
+
14
+ def type
15
+ @column.type
16
+ end
17
+ end
18
+
19
+ extend ActiveSupport::Concern
20
+
21
+ included do
22
+ mattr_accessor :time_zone_aware_attributes, instance_writer: false
23
+ self.time_zone_aware_attributes = false
24
+
25
+ class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
26
+ self.skip_time_zone_conversion_for_attributes = []
27
+ end
28
+
29
+ module ClassMethods
30
+ protected
31
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
32
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
33
+ def define_method_attribute=(attr_name)
34
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
35
+ method_body, line = <<-EOV, __LINE__ + 1
36
+ def #{attr_name}=(time)
37
+ time_with_zone = time.respond_to?(:in_time_zone) ? time.in_time_zone : nil
38
+ previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
39
+ write_attribute(:#{attr_name}, time)
40
+ #{attr_name}_will_change! if previous_time != time_with_zone
41
+ @attributes_cache["#{attr_name}"] = time_with_zone
42
+ end
43
+ EOV
44
+ generated_attribute_methods.module_eval(method_body, __FILE__, line)
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ private
51
+ def create_time_zone_conversion_attribute?(name, column)
52
+ time_zone_aware_attributes &&
53
+ !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
54
+ [:datetime, :timestamp].include?(column.type)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,63 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Write
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attribute_method_suffix "="
8
+ end
9
+
10
+ module ClassMethods
11
+ protected
12
+
13
+ # See define_method_attribute in read.rb for an explanation of
14
+ # this code.
15
+ def define_method_attribute=(name)
16
+ safe_name = name.unpack('h*').first
17
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
18
+ def __temp__#{safe_name}=(value)
19
+ write_attribute(AttrNames::ATTR_#{safe_name}, value)
20
+ end
21
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
22
+ undef_method :__temp__#{safe_name}=
23
+ STR
24
+ end
25
+ end
26
+
27
+ # Updates the attribute identified by <tt>attr_name</tt> with the
28
+ # specified +value+. Empty strings for fixnum and float columns are
29
+ # turned into +nil+.
30
+ def write_attribute(attr_name, value)
31
+ attr_name = attr_name.to_s
32
+ attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
33
+ @attributes_cache.delete(attr_name)
34
+ column = column_for_attribute(attr_name)
35
+
36
+ # If we're dealing with a binary column, write the data to the cache
37
+ # so we don't attempt to typecast multiple times.
38
+ if column && column.binary?
39
+ @attributes_cache[attr_name] = value
40
+ end
41
+
42
+ if column || @attributes.has_key?(attr_name)
43
+ @attributes[attr_name] = type_cast_attribute_for_write(column, value)
44
+ else
45
+ raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
46
+ end
47
+ end
48
+ alias_method :raw_write_attribute, :write_attribute
49
+
50
+ private
51
+ # Handle *= for method_missing.
52
+ def attribute=(attribute_name, value)
53
+ write_attribute(attribute_name, value)
54
+ end
55
+
56
+ def type_cast_attribute_for_write(column, value)
57
+ return value unless column
58
+
59
+ column.type_cast_for_write value
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,393 @@
1
+ require 'active_support/core_ext/enumerable'
2
+
3
+ module ActiveRecord
4
+ # = Active Record Attribute Methods
5
+ module AttributeMethods
6
+ extend ActiveSupport::Concern
7
+ include ActiveModel::AttributeMethods
8
+
9
+ included do
10
+ include Read
11
+ include Write
12
+ include BeforeTypeCast
13
+ include Query
14
+ include PrimaryKey
15
+ include TimeZoneConversion
16
+ include Dirty
17
+ include Serialization
18
+ end
19
+
20
+ module ClassMethods
21
+ # Generates all the attribute related methods for columns in the database
22
+ # accessors, mutators and query methods.
23
+ def define_attribute_methods # :nodoc:
24
+ # Use a mutex; we don't want two thread simultaneously trying to define
25
+ # attribute methods.
26
+ @attribute_methods_mutex.synchronize do
27
+ return if attribute_methods_generated?
28
+ superclass.define_attribute_methods unless self == base_class
29
+ super(column_names)
30
+ @attribute_methods_generated = true
31
+ end
32
+ end
33
+
34
+ def attribute_methods_generated? # :nodoc:
35
+ @attribute_methods_generated ||= false
36
+ end
37
+
38
+ def undefine_attribute_methods # :nodoc:
39
+ super if attribute_methods_generated?
40
+ @attribute_methods_generated = false
41
+ end
42
+
43
+ # Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
44
+ # \Active \Record method is defined in the model, otherwise +false+.
45
+ #
46
+ # class Person < ActiveRecord::Base
47
+ # def save
48
+ # 'already defined by Active Record'
49
+ # end
50
+ # end
51
+ #
52
+ # Person.instance_method_already_implemented?(:save)
53
+ # # => ActiveRecord::DangerousAttributeError: save is defined by ActiveRecord
54
+ #
55
+ # Person.instance_method_already_implemented?(:name)
56
+ # # => false
57
+ def instance_method_already_implemented?(method_name)
58
+ if dangerous_attribute_method?(method_name)
59
+ raise DangerousAttributeError, "#{method_name} is defined by Active Record"
60
+ end
61
+
62
+ if superclass == Base
63
+ super
64
+ else
65
+ # If B < A and A defines its own attribute method, then we don't want to overwrite that.
66
+ defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
67
+ defined && !ActiveRecord::Base.method_defined?(method_name) || super
68
+ end
69
+ end
70
+
71
+ # A method name is 'dangerous' if it is already defined by Active Record, but
72
+ # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
73
+ def dangerous_attribute_method?(name) # :nodoc:
74
+ method_defined_within?(name, Base)
75
+ end
76
+
77
+ def method_defined_within?(name, klass, sup = klass.superclass) # :nodoc:
78
+ if klass.method_defined?(name) || klass.private_method_defined?(name)
79
+ if sup.method_defined?(name) || sup.private_method_defined?(name)
80
+ klass.instance_method(name).owner != sup.instance_method(name).owner
81
+ else
82
+ true
83
+ end
84
+ else
85
+ false
86
+ end
87
+ end
88
+
89
+ # Returns +true+ if +attribute+ is an attribute method and table exists,
90
+ # +false+ otherwise.
91
+ #
92
+ # class Person < ActiveRecord::Base
93
+ # end
94
+ #
95
+ # Person.attribute_method?('name') # => true
96
+ # Person.attribute_method?(:age=) # => true
97
+ # Person.attribute_method?(:nothing) # => false
98
+ def attribute_method?(attribute)
99
+ super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
100
+ end
101
+
102
+ # Returns an array of column names as strings if it's not an abstract class and
103
+ # table exists. Otherwise it returns an empty array.
104
+ #
105
+ # class Person < ActiveRecord::Base
106
+ # end
107
+ #
108
+ # Person.attribute_names
109
+ # # => ["id", "created_at", "updated_at", "name", "age"]
110
+ def attribute_names
111
+ @attribute_names ||= if !abstract_class? && table_exists?
112
+ column_names
113
+ else
114
+ []
115
+ end
116
+ end
117
+ end
118
+
119
+ # If we haven't generated any methods yet, generate them, then
120
+ # see if we've created the method we're looking for.
121
+ def method_missing(method, *args, &block) # :nodoc:
122
+ unless self.class.attribute_methods_generated?
123
+ self.class.define_attribute_methods
124
+
125
+ if respond_to_without_attributes?(method)
126
+ send(method, *args, &block)
127
+ else
128
+ super
129
+ end
130
+ else
131
+ super
132
+ end
133
+ end
134
+
135
+ def attribute_missing(match, *args, &block) # :nodoc:
136
+ if self.class.columns_hash[match.attr_name]
137
+ ActiveSupport::Deprecation.warn(
138
+ "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
139
+ "dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
140
+ "is a column of the table. If this error has happened through normal usage of Active " \
141
+ "Record (rather than through your own code or external libraries), please report it as " \
142
+ "a bug."
143
+ )
144
+ end
145
+
146
+ super
147
+ end
148
+
149
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
150
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
151
+ # which will all return +true+. It also define the attribute methods if they have
152
+ # not been generated.
153
+ #
154
+ # class Person < ActiveRecord::Base
155
+ # end
156
+ #
157
+ # person = Person.new
158
+ # person.respond_to(:name) # => true
159
+ # person.respond_to(:name=) # => true
160
+ # person.respond_to(:name?) # => true
161
+ # person.respond_to('age') # => true
162
+ # person.respond_to('age=') # => true
163
+ # person.respond_to('age?') # => true
164
+ # person.respond_to(:nothing) # => false
165
+ def respond_to?(name, include_private = false)
166
+ name = name.to_s
167
+ self.class.define_attribute_methods unless self.class.attribute_methods_generated?
168
+ result = super
169
+
170
+ # If the result is false the answer is false.
171
+ return false unless result
172
+
173
+ # If the result is true then check for the select case.
174
+ # For queries selecting a subset of columns, return false for unselected columns.
175
+ # We check defined?(@attributes) not to issue warnings if called on objects that
176
+ # have been allocated but not yet initialized.
177
+ if defined?(@attributes) && @attributes.present? && self.class.column_names.include?(name)
178
+ return has_attribute?(name)
179
+ end
180
+
181
+ return true
182
+ end
183
+
184
+ # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
185
+ #
186
+ # class Person < ActiveRecord::Base
187
+ # end
188
+ #
189
+ # person = Person.new
190
+ # person.has_attribute?(:name) # => true
191
+ # person.has_attribute?('age') # => true
192
+ # person.has_attribute?(:nothing) # => false
193
+ def has_attribute?(attr_name)
194
+ @attributes.has_key?(attr_name.to_s)
195
+ end
196
+
197
+ # Returns an array of names for the attributes available on this object.
198
+ #
199
+ # class Person < ActiveRecord::Base
200
+ # end
201
+ #
202
+ # person = Person.new
203
+ # person.attribute_names
204
+ # # => ["id", "created_at", "updated_at", "name", "age"]
205
+ def attribute_names
206
+ @attributes.keys
207
+ end
208
+
209
+ # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
210
+ #
211
+ # class Person < ActiveRecord::Base
212
+ # end
213
+ #
214
+ # person = Person.create(name: 'Francesco', age: 22)
215
+ # person.attributes
216
+ # # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
217
+ def attributes
218
+ attribute_names.each_with_object({}) { |name, attrs|
219
+ attrs[name] = read_attribute(name)
220
+ }
221
+ end
222
+
223
+ # Returns an <tt>#inspect</tt>-like string for the value of the
224
+ # attribute +attr_name+. String attributes are truncated upto 50
225
+ # characters, and Date and Time attributes are returned in the
226
+ # <tt>:db</tt> format. Other attributes return the value of
227
+ # <tt>#inspect</tt> without modification.
228
+ #
229
+ # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
230
+ #
231
+ # person.attribute_for_inspect(:name)
232
+ # # => "\"David Heinemeier Hansson David Heinemeier Hansson D...\""
233
+ #
234
+ # person.attribute_for_inspect(:created_at)
235
+ # # => "\"2012-10-22 00:15:07\""
236
+ def attribute_for_inspect(attr_name)
237
+ value = read_attribute(attr_name)
238
+
239
+ if value.is_a?(String) && value.length > 50
240
+ "#{value[0..50]}...".inspect
241
+ elsif value.is_a?(Date) || value.is_a?(Time)
242
+ %("#{value.to_s(:db)}")
243
+ else
244
+ value.inspect
245
+ end
246
+ end
247
+
248
+ # Returns +true+ if the specified +attribute+ has been set by the user or by a
249
+ # database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
250
+ # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
251
+ # Note that it always returns +true+ with boolean attributes.
252
+ #
253
+ # class Task < ActiveRecord::Base
254
+ # end
255
+ #
256
+ # person = Task.new(title: '', is_done: false)
257
+ # person.attribute_present?(:title) # => false
258
+ # person.attribute_present?(:is_done) # => true
259
+ # person.name = 'Francesco'
260
+ # person.is_done = true
261
+ # person.attribute_present?(:title) # => true
262
+ # person.attribute_present?(:is_done) # => true
263
+ def attribute_present?(attribute)
264
+ value = read_attribute(attribute)
265
+ !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
266
+ end
267
+
268
+ # Returns the column object for the named attribute. Returns +nil+ if the
269
+ # named attribute not exists.
270
+ #
271
+ # class Person < ActiveRecord::Base
272
+ # end
273
+ #
274
+ # person = Person.new
275
+ # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
276
+ # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
277
+ #
278
+ # person.column_for_attribute(:nothing)
279
+ # # => nil
280
+ def column_for_attribute(name)
281
+ # FIXME: should this return a null object for columns that don't exist?
282
+ self.class.columns_hash[name.to_s]
283
+ end
284
+
285
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
286
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). It raises
287
+ # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
288
+ #
289
+ # Alias for the <tt>read_attribute</tt> method.
290
+ #
291
+ # class Person < ActiveRecord::Base
292
+ # belongs_to :organization
293
+ # end
294
+ #
295
+ # person = Person.new(name: 'Francesco', age: '22')
296
+ # person[:name] # => "Francesco"
297
+ # person[:age] # => 22
298
+ #
299
+ # person = Person.select('id').first
300
+ # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
301
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
302
+ def [](attr_name)
303
+ read_attribute(attr_name) { |n| missing_attribute(n, caller) }
304
+ end
305
+
306
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
307
+ # (Alias for the protected <tt>write_attribute</tt> method).
308
+ #
309
+ # class Person < ActiveRecord::Base
310
+ # end
311
+ #
312
+ # person = Person.new
313
+ # person[:age] = '22'
314
+ # person[:age] # => 22
315
+ # person[:age] # => Fixnum
316
+ def []=(attr_name, value)
317
+ write_attribute(attr_name, value)
318
+ end
319
+
320
+ protected
321
+
322
+ def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
323
+ attribute_names.each do |name|
324
+ attributes[name] = clone_attribute_value(reader_method, name)
325
+ end
326
+ attributes
327
+ end
328
+
329
+ def clone_attribute_value(reader_method, attribute_name) # :nodoc:
330
+ value = send(reader_method, attribute_name)
331
+ value.duplicable? ? value.clone : value
332
+ rescue TypeError, NoMethodError
333
+ value
334
+ end
335
+
336
+ def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
337
+ arel_attributes_with_values(attributes_for_create(attribute_names))
338
+ end
339
+
340
+ def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
341
+ arel_attributes_with_values(attributes_for_update(attribute_names))
342
+ end
343
+
344
+ def attribute_method?(attr_name) # :nodoc:
345
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
346
+ defined?(@attributes) && @attributes.include?(attr_name)
347
+ end
348
+
349
+ private
350
+
351
+ # Returns a Hash of the Arel::Attributes and attribute values that have been
352
+ # typecasted for use in an Arel insert/update method.
353
+ def arel_attributes_with_values(attribute_names)
354
+ attrs = {}
355
+ arel_table = self.class.arel_table
356
+
357
+ attribute_names.each do |name|
358
+ attrs[arel_table[name]] = typecasted_attribute_value(name)
359
+ end
360
+ attrs
361
+ end
362
+
363
+ # Filters the primary keys and readonly attributes from the attribute names.
364
+ def attributes_for_update(attribute_names)
365
+ attribute_names.select do |name|
366
+ column_for_attribute(name) && !readonly_attribute?(name)
367
+ end
368
+ end
369
+
370
+ # Filters out the primary keys, from the attribute names, when the primary
371
+ # key is to be generated (e.g. the id attribute has no value).
372
+ def attributes_for_create(attribute_names)
373
+ attribute_names.select do |name|
374
+ column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
375
+ end
376
+ end
377
+
378
+ def readonly_attribute?(name)
379
+ self.class.readonly_attributes.include?(name)
380
+ end
381
+
382
+ def pk_attribute?(name)
383
+ column_for_attribute(name).primary
384
+ end
385
+
386
+ def typecasted_attribute_value(name)
387
+ # FIXME: we need @attributes to be used consistently.
388
+ # If the values stored in @attributes were already typecasted, this code
389
+ # could be simplified
390
+ read_attribute(name)
391
+ end
392
+ end
393
+ end