activerecord 1.0.0 → 2.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 (311) hide show
  1. data/CHANGELOG +4928 -3
  2. data/README +45 -46
  3. data/RUNNING_UNIT_TESTS +8 -11
  4. data/Rakefile +247 -0
  5. data/install.rb +8 -38
  6. data/lib/active_record/aggregations.rb +64 -49
  7. data/lib/active_record/associations/association_collection.rb +217 -47
  8. data/lib/active_record/associations/association_proxy.rb +159 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +155 -37
  12. data/lib/active_record/associations/has_many_association.rb +145 -75
  13. data/lib/active_record/associations/has_many_through_association.rb +283 -0
  14. data/lib/active_record/associations/has_one_association.rb +96 -0
  15. data/lib/active_record/associations.rb +1537 -304
  16. data/lib/active_record/attribute_methods.rb +328 -0
  17. data/lib/active_record/base.rb +2001 -588
  18. data/lib/active_record/calculations.rb +269 -0
  19. data/lib/active_record/callbacks.rb +169 -165
  20. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +308 -0
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -0
  22. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  23. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +472 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -0
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +125 -279
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +442 -77
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +805 -135
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  30. data/lib/active_record/connection_adapters/sqlite_adapter.rb +353 -69
  31. data/lib/active_record/fixtures.rb +946 -100
  32. data/lib/active_record/locking/optimistic.rb +144 -0
  33. data/lib/active_record/locking/pessimistic.rb +77 -0
  34. data/lib/active_record/migration.rb +417 -0
  35. data/lib/active_record/observer.rb +142 -32
  36. data/lib/active_record/query_cache.rb +23 -0
  37. data/lib/active_record/reflection.rb +163 -70
  38. data/lib/active_record/schema.rb +58 -0
  39. data/lib/active_record/schema_dumper.rb +171 -0
  40. data/lib/active_record/serialization.rb +98 -0
  41. data/lib/active_record/serializers/json_serializer.rb +71 -0
  42. data/lib/active_record/serializers/xml_serializer.rb +315 -0
  43. data/lib/active_record/timestamp.rb +41 -0
  44. data/lib/active_record/transactions.rb +87 -57
  45. data/lib/active_record/validations.rb +909 -122
  46. data/lib/active_record/vendor/db2.rb +362 -0
  47. data/lib/active_record/vendor/mysql.rb +126 -29
  48. data/lib/active_record/version.rb +9 -0
  49. data/lib/active_record.rb +35 -7
  50. data/lib/activerecord.rb +1 -0
  51. data/test/aaa_create_tables_test.rb +72 -0
  52. data/test/abstract_unit.rb +73 -5
  53. data/test/active_schema_test_mysql.rb +43 -0
  54. data/test/adapter_test.rb +105 -0
  55. data/test/adapter_test_sqlserver.rb +95 -0
  56. data/test/aggregations_test.rb +110 -16
  57. data/test/all.sh +2 -2
  58. data/test/ar_schema_test.rb +33 -0
  59. data/test/association_inheritance_reload.rb +14 -0
  60. data/test/associations/ar_joins_test.rb +0 -0
  61. data/test/associations/callbacks_test.rb +147 -0
  62. data/test/associations/cascaded_eager_loading_test.rb +110 -0
  63. data/test/associations/eager_singularization_test.rb +145 -0
  64. data/test/associations/eager_test.rb +442 -0
  65. data/test/associations/extension_test.rb +47 -0
  66. data/test/associations/inner_join_association_test.rb +88 -0
  67. data/test/associations/join_model_test.rb +553 -0
  68. data/test/associations_test.rb +1930 -267
  69. data/test/attribute_methods_test.rb +146 -0
  70. data/test/base_test.rb +1316 -84
  71. data/test/binary_test.rb +32 -0
  72. data/test/calculations_test.rb +251 -0
  73. data/test/callbacks_test.rb +400 -0
  74. data/test/class_inheritable_attributes_test.rb +3 -4
  75. data/test/column_alias_test.rb +17 -0
  76. data/test/connection_test_firebird.rb +8 -0
  77. data/test/connection_test_mysql.rb +30 -0
  78. data/test/connections/native_db2/connection.rb +25 -0
  79. data/test/connections/native_firebird/connection.rb +26 -0
  80. data/test/connections/native_frontbase/connection.rb +27 -0
  81. data/test/connections/native_mysql/connection.rb +21 -18
  82. data/test/connections/native_openbase/connection.rb +21 -0
  83. data/test/connections/native_oracle/connection.rb +27 -0
  84. data/test/connections/native_postgresql/connection.rb +17 -18
  85. data/test/connections/native_sqlite/connection.rb +17 -16
  86. data/test/connections/native_sqlite3/connection.rb +25 -0
  87. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  88. data/test/connections/native_sybase/connection.rb +23 -0
  89. data/test/copy_table_test_sqlite.rb +69 -0
  90. data/test/datatype_test_postgresql.rb +203 -0
  91. data/test/date_time_test.rb +37 -0
  92. data/test/default_test_firebird.rb +16 -0
  93. data/test/defaults_test.rb +67 -0
  94. data/test/deprecated_finder_test.rb +30 -0
  95. data/test/finder_test.rb +607 -32
  96. data/test/fixtures/accounts.yml +28 -0
  97. data/test/fixtures/all/developers.yml +0 -0
  98. data/test/fixtures/all/people.csv +0 -0
  99. data/test/fixtures/all/tasks.yml +0 -0
  100. data/test/fixtures/author.rb +107 -0
  101. data/test/fixtures/author_favorites.yml +4 -0
  102. data/test/fixtures/authors.yml +7 -0
  103. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  104. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  105. data/test/fixtures/bad_fixtures/blank_line +3 -0
  106. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  107. data/test/fixtures/bad_fixtures/missing_value +1 -0
  108. data/test/fixtures/binaries.yml +132 -0
  109. data/test/fixtures/binary.rb +2 -0
  110. data/test/fixtures/book.rb +4 -0
  111. data/test/fixtures/books.yml +7 -0
  112. data/test/fixtures/categories/special_categories.yml +9 -0
  113. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  114. data/test/fixtures/categories.yml +14 -0
  115. data/test/fixtures/categories_ordered.yml +7 -0
  116. data/test/fixtures/categories_posts.yml +23 -0
  117. data/test/fixtures/categorization.rb +5 -0
  118. data/test/fixtures/categorizations.yml +17 -0
  119. data/test/fixtures/category.rb +26 -0
  120. data/test/fixtures/citation.rb +6 -0
  121. data/test/fixtures/comment.rb +23 -0
  122. data/test/fixtures/comments.yml +59 -0
  123. data/test/fixtures/companies.yml +55 -0
  124. data/test/fixtures/company.rb +81 -4
  125. data/test/fixtures/company_in_module.rb +32 -6
  126. data/test/fixtures/computer.rb +4 -0
  127. data/test/fixtures/computers.yml +4 -0
  128. data/test/fixtures/contact.rb +16 -0
  129. data/test/fixtures/courses.yml +7 -0
  130. data/test/fixtures/customer.rb +28 -3
  131. data/test/fixtures/customers.yml +17 -0
  132. data/test/fixtures/db_definitions/db2.drop.sql +33 -0
  133. data/test/fixtures/db_definitions/db2.sql +235 -0
  134. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  135. data/test/fixtures/db_definitions/db22.sql +5 -0
  136. data/test/fixtures/db_definitions/firebird.drop.sql +65 -0
  137. data/test/fixtures/db_definitions/firebird.sql +310 -0
  138. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  139. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  140. data/test/fixtures/db_definitions/frontbase.drop.sql +33 -0
  141. data/test/fixtures/db_definitions/frontbase.sql +273 -0
  142. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  143. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  144. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  145. data/test/fixtures/db_definitions/openbase.sql +318 -0
  146. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  148. data/test/fixtures/db_definitions/oracle.drop.sql +67 -0
  149. data/test/fixtures/db_definitions/oracle.sql +330 -0
  150. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  151. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  152. data/test/fixtures/db_definitions/postgresql.drop.sql +44 -0
  153. data/test/fixtures/db_definitions/postgresql.sql +217 -38
  154. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/postgresql2.sql +2 -2
  156. data/test/fixtures/db_definitions/schema.rb +354 -0
  157. data/test/fixtures/db_definitions/schema2.rb +11 -0
  158. data/test/fixtures/db_definitions/sqlite.drop.sql +33 -0
  159. data/test/fixtures/db_definitions/sqlite.sql +139 -5
  160. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  161. data/test/fixtures/db_definitions/sqlite2.sql +1 -0
  162. data/test/fixtures/db_definitions/sybase.drop.sql +35 -0
  163. data/test/fixtures/db_definitions/sybase.sql +222 -0
  164. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  165. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  166. data/test/fixtures/developer.rb +70 -6
  167. data/test/fixtures/developers.yml +21 -0
  168. data/test/fixtures/developers_projects/david_action_controller +2 -1
  169. data/test/fixtures/developers_projects/david_active_record +2 -1
  170. data/test/fixtures/developers_projects.yml +17 -0
  171. data/test/fixtures/edge.rb +5 -0
  172. data/test/fixtures/edges.yml +6 -0
  173. data/test/fixtures/entrants.yml +14 -0
  174. data/test/fixtures/example.log +1 -0
  175. data/test/fixtures/fk_test_has_fk.yml +3 -0
  176. data/test/fixtures/fk_test_has_pk.yml +2 -0
  177. data/test/fixtures/flowers.jpg +0 -0
  178. data/test/fixtures/funny_jokes.yml +10 -0
  179. data/test/fixtures/item.rb +7 -0
  180. data/test/fixtures/items.yml +4 -0
  181. data/test/fixtures/joke.rb +3 -0
  182. data/test/fixtures/keyboard.rb +3 -0
  183. data/test/fixtures/legacy_thing.rb +3 -0
  184. data/test/fixtures/legacy_things.yml +3 -0
  185. data/test/fixtures/matey.rb +4 -0
  186. data/test/fixtures/mateys.yml +4 -0
  187. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  188. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  189. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  190. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  191. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  192. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  193. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  194. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  195. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  196. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  197. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  198. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  199. data/test/fixtures/minimalistic.rb +2 -0
  200. data/test/fixtures/minimalistics.yml +2 -0
  201. data/test/fixtures/mixed_case_monkey.rb +3 -0
  202. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  203. data/test/fixtures/mixins.yml +29 -0
  204. data/test/fixtures/movies.yml +7 -0
  205. data/test/fixtures/naked/csv/accounts.csv +1 -0
  206. data/test/fixtures/naked/yml/accounts.yml +1 -0
  207. data/test/fixtures/naked/yml/companies.yml +1 -0
  208. data/test/fixtures/naked/yml/courses.yml +1 -0
  209. data/test/fixtures/order.rb +4 -0
  210. data/test/fixtures/parrot.rb +13 -0
  211. data/test/fixtures/parrots.yml +27 -0
  212. data/test/fixtures/parrots_pirates.yml +7 -0
  213. data/test/fixtures/people.yml +3 -0
  214. data/test/fixtures/person.rb +4 -0
  215. data/test/fixtures/pirate.rb +5 -0
  216. data/test/fixtures/pirates.yml +9 -0
  217. data/test/fixtures/post.rb +59 -0
  218. data/test/fixtures/posts.yml +48 -0
  219. data/test/fixtures/project.rb +27 -2
  220. data/test/fixtures/projects.yml +7 -0
  221. data/test/fixtures/reader.rb +4 -0
  222. data/test/fixtures/readers.yml +4 -0
  223. data/test/fixtures/reply.rb +18 -2
  224. data/test/fixtures/reserved_words/distinct.yml +5 -0
  225. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  226. data/test/fixtures/reserved_words/group.yml +14 -0
  227. data/test/fixtures/reserved_words/select.yml +8 -0
  228. data/test/fixtures/reserved_words/values.yml +7 -0
  229. data/test/fixtures/ship.rb +3 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/subject.rb +4 -0
  232. data/test/fixtures/subscriber.rb +4 -3
  233. data/test/fixtures/tag.rb +7 -0
  234. data/test/fixtures/tagging.rb +10 -0
  235. data/test/fixtures/taggings.yml +25 -0
  236. data/test/fixtures/tags.yml +7 -0
  237. data/test/fixtures/task.rb +3 -0
  238. data/test/fixtures/tasks.yml +7 -0
  239. data/test/fixtures/topic.rb +20 -3
  240. data/test/fixtures/topics.yml +22 -0
  241. data/test/fixtures/treasure.rb +4 -0
  242. data/test/fixtures/treasures.yml +10 -0
  243. data/test/fixtures/vertex.rb +9 -0
  244. data/test/fixtures/vertices.yml +4 -0
  245. data/test/fixtures_test.rb +574 -8
  246. data/test/inheritance_test.rb +113 -27
  247. data/test/json_serialization_test.rb +180 -0
  248. data/test/lifecycle_test.rb +56 -29
  249. data/test/locking_test.rb +273 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +933 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_test.rb +95 -0
  254. data/test/modules_test.rb +23 -10
  255. data/test/multiple_db_test.rb +17 -3
  256. data/test/pk_test.rb +59 -15
  257. data/test/query_cache_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +124 -27
  260. data/test/reserved_word_test_mysql.rb +177 -0
  261. data/test/schema_authorization_test_postgresql.rb +75 -0
  262. data/test/schema_dumper_test.rb +131 -0
  263. data/test/schema_test_postgresql.rb +64 -0
  264. data/test/serialization_test.rb +47 -0
  265. data/test/synonym_test_oracle.rb +17 -0
  266. data/test/table_name_test_sqlserver.rb +23 -0
  267. data/test/threaded_connections_test.rb +48 -0
  268. data/test/transactions_test.rb +227 -29
  269. data/test/unconnected_test.rb +14 -6
  270. data/test/validations_test.rb +1293 -32
  271. data/test/xml_serialization_test.rb +202 -0
  272. metadata +347 -143
  273. data/dev-utils/eval_debugger.rb +0 -9
  274. data/examples/associations.rb +0 -87
  275. data/examples/shared_setup.rb +0 -15
  276. data/examples/validation.rb +0 -88
  277. data/lib/active_record/deprecated_associations.rb +0 -70
  278. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  279. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  280. data/lib/active_record/support/clean_logger.rb +0 -10
  281. data/lib/active_record/support/inflector.rb +0 -70
  282. data/lib/active_record/vendor/simple.rb +0 -702
  283. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  284. data/lib/active_record/wrappings.rb +0 -59
  285. data/rakefile +0 -122
  286. data/test/deprecated_associations_test.rb +0 -336
  287. data/test/fixtures/accounts/signals37 +0 -3
  288. data/test/fixtures/accounts/unknown +0 -2
  289. data/test/fixtures/companies/first_client +0 -6
  290. data/test/fixtures/companies/first_firm +0 -4
  291. data/test/fixtures/companies/second_client +0 -6
  292. data/test/fixtures/courses/java +0 -2
  293. data/test/fixtures/courses/ruby +0 -2
  294. data/test/fixtures/customers/david +0 -6
  295. data/test/fixtures/db_definitions/mysql.sql +0 -96
  296. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  297. data/test/fixtures/developers/david +0 -2
  298. data/test/fixtures/developers/jamis +0 -2
  299. data/test/fixtures/entrants/first +0 -3
  300. data/test/fixtures/entrants/second +0 -3
  301. data/test/fixtures/entrants/third +0 -3
  302. data/test/fixtures/fixture_database.sqlite +0 -0
  303. data/test/fixtures/fixture_database_2.sqlite +0 -0
  304. data/test/fixtures/movies/first +0 -2
  305. data/test/fixtures/movies/second +0 -2
  306. data/test/fixtures/projects/action_controller +0 -2
  307. data/test/fixtures/projects/active_record +0 -2
  308. data/test/fixtures/topics/first +0 -9
  309. data/test/fixtures/topics/second +0 -8
  310. data/test/inflector_test.rb +0 -104
  311. data/test/thread_safety_test.rb +0 -33
@@ -0,0 +1,328 @@
1
+ module ActiveRecord
2
+ module AttributeMethods #:nodoc:
3
+ DEFAULT_SUFFIXES = %w(= ? _before_type_cast)
4
+ ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
5
+
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ base.attribute_method_suffix *DEFAULT_SUFFIXES
9
+ base.cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
10
+ base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
11
+ end
12
+
13
+ # Declare and check for suffixed attribute methods.
14
+ module ClassMethods
15
+ # Declare a method available for all attributes with the given suffix.
16
+ # Uses method_missing and respond_to? to rewrite the method
17
+ # #{attr}#{suffix}(*args, &block)
18
+ # to
19
+ # attribute#{suffix}(#{attr}, *args, &block)
20
+ #
21
+ # An attribute#{suffix} instance method must exist and accept at least
22
+ # the attr argument.
23
+ #
24
+ # For example:
25
+ # class Person < ActiveRecord::Base
26
+ # attribute_method_suffix '_changed?'
27
+ #
28
+ # private
29
+ # def attribute_changed?(attr)
30
+ # ...
31
+ # end
32
+ # end
33
+ #
34
+ # person = Person.find(1)
35
+ # person.name_changed? # => false
36
+ # person.name = 'Hubert'
37
+ # person.name_changed? # => true
38
+ def attribute_method_suffix(*suffixes)
39
+ attribute_method_suffixes.concat suffixes
40
+ rebuild_attribute_method_regexp
41
+ end
42
+
43
+ # Returns MatchData if method_name is an attribute method.
44
+ def match_attribute_method?(method_name)
45
+ rebuild_attribute_method_regexp unless defined?(@@attribute_method_regexp) && @@attribute_method_regexp
46
+ @@attribute_method_regexp.match(method_name)
47
+ end
48
+
49
+
50
+ # Contains the names of the generated attribute methods.
51
+ def generated_methods #:nodoc:
52
+ @generated_methods ||= Set.new
53
+ end
54
+
55
+ def generated_methods?
56
+ !generated_methods.empty?
57
+ end
58
+
59
+ # generates all the attribute related methods for columns in the database
60
+ # accessors, mutators and query methods
61
+ def define_attribute_methods
62
+ return if generated_methods?
63
+ columns_hash.each do |name, column|
64
+ unless instance_method_already_implemented?(name)
65
+ if self.serialized_attributes[name]
66
+ define_read_method_for_serialized_attribute(name)
67
+ else
68
+ define_read_method(name.to_sym, name, column)
69
+ end
70
+ end
71
+
72
+ unless instance_method_already_implemented?("#{name}=")
73
+ define_write_method(name.to_sym)
74
+ end
75
+
76
+ unless instance_method_already_implemented?("#{name}?")
77
+ define_question_method(name)
78
+ end
79
+ end
80
+ end
81
+
82
+ # Check to see if the method is defined in the model or any of its subclasses that also derive from ActiveRecord.
83
+ # Raise DangerousAttributeError if the method is defined by ActiveRecord though.
84
+ def instance_method_already_implemented?(method_name)
85
+ return true if method_name =~ /^id(=$|\?$|$)/
86
+ @_defined_class_methods ||= Set.new(ancestors.first(ancestors.index(ActiveRecord::Base)).collect! { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.flatten)
87
+ @@_defined_activerecord_methods ||= Set.new(ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false))
88
+ raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
89
+ @_defined_class_methods.include?(method_name)
90
+ end
91
+
92
+ alias :define_read_methods :define_attribute_methods
93
+
94
+ # +cache_attributes+ allows you to declare which converted attribute values should
95
+ # be cached. Usually caching only pays off for attributes with expensive conversion
96
+ # methods, like date columns (e.g. created_at, updated_at).
97
+ def cache_attributes(*attribute_names)
98
+ attribute_names.each {|attr| cached_attributes << attr.to_s}
99
+ end
100
+
101
+ # returns the attributes where
102
+ def cached_attributes
103
+ @cached_attributes ||=
104
+ columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map(&:name).to_set
105
+ end
106
+
107
+ def cache_attribute?(attr_name)
108
+ cached_attributes.include?(attr_name)
109
+ end
110
+
111
+ private
112
+ # Suffixes a, ?, c become regexp /(a|\?|c)$/
113
+ def rebuild_attribute_method_regexp
114
+ suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) }
115
+ @@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze
116
+ end
117
+
118
+ # Default to =, ?, _before_type_cast
119
+ def attribute_method_suffixes
120
+ @@attribute_method_suffixes ||= []
121
+ end
122
+
123
+ # Define an attribute reader method. Cope with nil column.
124
+ def define_read_method(symbol, attr_name, column)
125
+ cast_code = column.type_cast_code('v') if column
126
+ access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
127
+
128
+ unless attr_name.to_s == self.primary_key.to_s
129
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
130
+ end
131
+
132
+ if cache_attribute?(attr_name)
133
+ access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
134
+ end
135
+ evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end"
136
+ end
137
+
138
+ # Define read method for serialized attribute.
139
+ def define_read_method_for_serialized_attribute(attr_name)
140
+ evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
141
+ end
142
+
143
+ # Define an attribute ? method.
144
+ def define_question_method(attr_name)
145
+ evaluate_attribute_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end", "#{attr_name}?"
146
+ end
147
+
148
+ def define_write_method(attr_name)
149
+ evaluate_attribute_method attr_name, "def #{attr_name}=(new_value);write_attribute('#{attr_name}', new_value);end", "#{attr_name}="
150
+ end
151
+
152
+ # Evaluate the definition for an attribute related method
153
+ def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name)
154
+
155
+ unless method_name.to_s == primary_key.to_s
156
+ generated_methods << method_name
157
+ end
158
+
159
+ begin
160
+ class_eval(method_definition, __FILE__, __LINE__)
161
+ rescue SyntaxError => err
162
+ generated_methods.delete(attr_name)
163
+ if logger
164
+ logger.warn "Exception occurred during reader method compilation."
165
+ logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
166
+ logger.warn "#{err.message}"
167
+ end
168
+ end
169
+ end
170
+ end # ClassMethods
171
+
172
+
173
+ # Allows access to the object attributes, which are held in the @attributes hash, as though they
174
+ # were first-class methods. So a Person class with a name attribute can use Person#name and
175
+ # Person#name= and never directly use the attributes hash -- except for multiple assigns with
176
+ # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
177
+ # the completed attribute is not nil or 0.
178
+ #
179
+ # It's also possible to instantiate related objects, so a Client class belonging to the clients
180
+ # table with a master_id foreign key can instantiate master through Client#master.
181
+ def method_missing(method_id, *args, &block)
182
+ method_name = method_id.to_s
183
+
184
+ # If we haven't generated any methods yet, generate them, then
185
+ # see if we've created the method we're looking for.
186
+ if !self.class.generated_methods?
187
+ self.class.define_attribute_methods
188
+ if self.class.generated_methods.include?(method_name)
189
+ return self.send(method_id, *args, &block)
190
+ end
191
+ end
192
+
193
+ if self.class.primary_key.to_s == method_name
194
+ id
195
+ elsif md = self.class.match_attribute_method?(method_name)
196
+ attribute_name, method_type = md.pre_match, md.to_s
197
+ if @attributes.include?(attribute_name)
198
+ __send__("attribute#{method_type}", attribute_name, *args, &block)
199
+ else
200
+ super
201
+ end
202
+ elsif @attributes.include?(method_name)
203
+ read_attribute(method_name)
204
+ else
205
+ super
206
+ end
207
+ end
208
+
209
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
210
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
211
+ def read_attribute(attr_name)
212
+ attr_name = attr_name.to_s
213
+ if !(value = @attributes[attr_name]).nil?
214
+ if column = column_for_attribute(attr_name)
215
+ if unserializable_attribute?(attr_name, column)
216
+ unserialize_attribute(attr_name)
217
+ else
218
+ column.type_cast(value)
219
+ end
220
+ else
221
+ value
222
+ end
223
+ else
224
+ nil
225
+ end
226
+ end
227
+
228
+ def read_attribute_before_type_cast(attr_name)
229
+ @attributes[attr_name]
230
+ end
231
+
232
+ # Returns true if the attribute is of a text column and marked for serialization.
233
+ def unserializable_attribute?(attr_name, column)
234
+ column.text? && self.class.serialized_attributes[attr_name]
235
+ end
236
+
237
+ # Returns the unserialized object of the attribute.
238
+ def unserialize_attribute(attr_name)
239
+ unserialized_object = object_from_yaml(@attributes[attr_name])
240
+
241
+ if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
242
+ @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
243
+ else
244
+ raise SerializationTypeMismatch,
245
+ "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
246
+ end
247
+ end
248
+
249
+
250
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
251
+ # columns are turned into nil.
252
+ def write_attribute(attr_name, value)
253
+ attr_name = attr_name.to_s
254
+ @attributes_cache.delete(attr_name)
255
+ if (column = column_for_attribute(attr_name)) && column.number?
256
+ @attributes[attr_name] = convert_number_column_value(value)
257
+ else
258
+ @attributes[attr_name] = value
259
+ end
260
+ end
261
+
262
+
263
+ def query_attribute(attr_name)
264
+ unless value = read_attribute(attr_name)
265
+ false
266
+ else
267
+ column = self.class.columns_hash[attr_name]
268
+ if column.nil?
269
+ if Numeric === value || value !~ /[^0-9]/
270
+ !value.to_i.zero?
271
+ else
272
+ !value.blank?
273
+ end
274
+ elsif column.number?
275
+ !value.zero?
276
+ else
277
+ !value.blank?
278
+ end
279
+ end
280
+ end
281
+
282
+ # A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
283
+ # person.respond_to?("name?") which will all return true.
284
+ alias :respond_to_without_attributes? :respond_to?
285
+ def respond_to?(method, include_priv = false)
286
+ method_name = method.to_s
287
+ if super
288
+ return true
289
+ elsif !self.class.generated_methods?
290
+ self.class.define_attribute_methods
291
+ if self.class.generated_methods.include?(method_name)
292
+ return true
293
+ end
294
+ end
295
+
296
+ if @attributes.nil?
297
+ return super
298
+ elsif @attributes.include?(method_name)
299
+ return true
300
+ elsif md = self.class.match_attribute_method?(method_name)
301
+ return true if @attributes.include?(md.pre_match)
302
+ end
303
+ super
304
+ end
305
+
306
+
307
+ private
308
+
309
+ def missing_attribute(attr_name, stack)
310
+ raise ActiveRecord::MissingAttributeError, "missing attribute: #{attr_name}", stack
311
+ end
312
+
313
+ # Handle *? for method_missing.
314
+ def attribute?(attribute_name)
315
+ query_attribute(attribute_name)
316
+ end
317
+
318
+ # Handle *= for method_missing.
319
+ def attribute=(attribute_name, value)
320
+ write_attribute(attribute_name, value)
321
+ end
322
+
323
+ # Handle *_before_type_cast for method_missing.
324
+ def attribute_before_type_cast(attribute_name)
325
+ read_attribute_before_type_cast(attribute_name)
326
+ end
327
+ end
328
+ end