activerecord_csi 2.3.5.p6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. data/CHANGELOG +5858 -0
  2. data/README +351 -0
  3. data/RUNNING_UNIT_TESTS +36 -0
  4. data/Rakefile +270 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/performance.rb +162 -0
  7. data/install.rb +30 -0
  8. data/lib/active_record/aggregations.rb +261 -0
  9. data/lib/active_record/association_preload.rb +389 -0
  10. data/lib/active_record/associations/association_collection.rb +475 -0
  11. data/lib/active_record/associations/association_proxy.rb +278 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +76 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +53 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +122 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +266 -0
  17. data/lib/active_record/associations/has_one_association.rb +133 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +37 -0
  19. data/lib/active_record/associations.rb +2241 -0
  20. data/lib/active_record/attribute_methods.rb +388 -0
  21. data/lib/active_record/autosave_association.rb +364 -0
  22. data/lib/active_record/base.rb +3171 -0
  23. data/lib/active_record/batches.rb +81 -0
  24. data/lib/active_record/calculations.rb +311 -0
  25. data/lib/active_record/callbacks.rb +360 -0
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +371 -0
  27. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +139 -0
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +289 -0
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +94 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +722 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +434 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +241 -0
  34. data/lib/active_record/connection_adapters/mysql_adapter.rb +630 -0
  35. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1113 -0
  36. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  37. data/lib/active_record/connection_adapters/sqlite_adapter.rb +453 -0
  38. data/lib/active_record/dirty.rb +183 -0
  39. data/lib/active_record/dynamic_finder_match.rb +41 -0
  40. data/lib/active_record/dynamic_scope_match.rb +25 -0
  41. data/lib/active_record/fixtures.rb +996 -0
  42. data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
  43. data/lib/active_record/locale/en.yml +58 -0
  44. data/lib/active_record/locking/optimistic.rb +148 -0
  45. data/lib/active_record/locking/pessimistic.rb +55 -0
  46. data/lib/active_record/migration.rb +566 -0
  47. data/lib/active_record/named_scope.rb +192 -0
  48. data/lib/active_record/nested_attributes.rb +392 -0
  49. data/lib/active_record/observer.rb +197 -0
  50. data/lib/active_record/query_cache.rb +33 -0
  51. data/lib/active_record/reflection.rb +320 -0
  52. data/lib/active_record/schema.rb +51 -0
  53. data/lib/active_record/schema_dumper.rb +182 -0
  54. data/lib/active_record/serialization.rb +101 -0
  55. data/lib/active_record/serializers/json_serializer.rb +91 -0
  56. data/lib/active_record/serializers/xml_serializer.rb +357 -0
  57. data/lib/active_record/session_store.rb +326 -0
  58. data/lib/active_record/test_case.rb +66 -0
  59. data/lib/active_record/timestamp.rb +71 -0
  60. data/lib/active_record/transactions.rb +235 -0
  61. data/lib/active_record/validations.rb +1135 -0
  62. data/lib/active_record/version.rb +9 -0
  63. data/lib/active_record.rb +84 -0
  64. data/lib/activerecord.rb +2 -0
  65. data/test/assets/example.log +1 -0
  66. data/test/assets/flowers.jpg +0 -0
  67. data/test/cases/aaa_create_tables_test.rb +24 -0
  68. data/test/cases/active_schema_test_mysql.rb +100 -0
  69. data/test/cases/active_schema_test_postgresql.rb +24 -0
  70. data/test/cases/adapter_test.rb +145 -0
  71. data/test/cases/aggregations_test.rb +167 -0
  72. data/test/cases/ar_schema_test.rb +32 -0
  73. data/test/cases/associations/belongs_to_associations_test.rb +425 -0
  74. data/test/cases/associations/callbacks_test.rb +161 -0
  75. data/test/cases/associations/cascaded_eager_loading_test.rb +131 -0
  76. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -0
  77. data/test/cases/associations/eager_load_nested_include_test.rb +130 -0
  78. data/test/cases/associations/eager_singularization_test.rb +145 -0
  79. data/test/cases/associations/eager_test.rb +834 -0
  80. data/test/cases/associations/extension_test.rb +62 -0
  81. data/test/cases/associations/habtm_join_table_test.rb +56 -0
  82. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +822 -0
  83. data/test/cases/associations/has_many_associations_test.rb +1134 -0
  84. data/test/cases/associations/has_many_through_associations_test.rb +346 -0
  85. data/test/cases/associations/has_one_associations_test.rb +330 -0
  86. data/test/cases/associations/has_one_through_associations_test.rb +209 -0
  87. data/test/cases/associations/inner_join_association_test.rb +93 -0
  88. data/test/cases/associations/join_model_test.rb +712 -0
  89. data/test/cases/associations_test.rb +262 -0
  90. data/test/cases/attribute_methods_test.rb +305 -0
  91. data/test/cases/autosave_association_test.rb +1142 -0
  92. data/test/cases/base_test.rb +2154 -0
  93. data/test/cases/batches_test.rb +61 -0
  94. data/test/cases/binary_test.rb +30 -0
  95. data/test/cases/calculations_test.rb +348 -0
  96. data/test/cases/callbacks_observers_test.rb +38 -0
  97. data/test/cases/callbacks_test.rb +438 -0
  98. data/test/cases/class_inheritable_attributes_test.rb +32 -0
  99. data/test/cases/column_alias_test.rb +17 -0
  100. data/test/cases/column_definition_test.rb +70 -0
  101. data/test/cases/connection_pool_test.rb +25 -0
  102. data/test/cases/connection_test_firebird.rb +8 -0
  103. data/test/cases/connection_test_mysql.rb +64 -0
  104. data/test/cases/copy_table_test_sqlite.rb +80 -0
  105. data/test/cases/database_statements_test.rb +12 -0
  106. data/test/cases/datatype_test_postgresql.rb +204 -0
  107. data/test/cases/date_time_test.rb +37 -0
  108. data/test/cases/default_test_firebird.rb +16 -0
  109. data/test/cases/defaults_test.rb +111 -0
  110. data/test/cases/deprecated_finder_test.rb +30 -0
  111. data/test/cases/dirty_test.rb +316 -0
  112. data/test/cases/finder_respond_to_test.rb +76 -0
  113. data/test/cases/finder_test.rb +1066 -0
  114. data/test/cases/fixtures_test.rb +656 -0
  115. data/test/cases/helper.rb +68 -0
  116. data/test/cases/i18n_test.rb +46 -0
  117. data/test/cases/inheritance_test.rb +262 -0
  118. data/test/cases/invalid_date_test.rb +24 -0
  119. data/test/cases/json_serialization_test.rb +205 -0
  120. data/test/cases/lifecycle_test.rb +193 -0
  121. data/test/cases/locking_test.rb +304 -0
  122. data/test/cases/method_scoping_test.rb +704 -0
  123. data/test/cases/migration_test.rb +1523 -0
  124. data/test/cases/migration_test_firebird.rb +124 -0
  125. data/test/cases/mixin_test.rb +96 -0
  126. data/test/cases/modules_test.rb +81 -0
  127. data/test/cases/multiple_db_test.rb +85 -0
  128. data/test/cases/named_scope_test.rb +361 -0
  129. data/test/cases/nested_attributes_test.rb +581 -0
  130. data/test/cases/pk_test.rb +119 -0
  131. data/test/cases/pooled_connections_test.rb +103 -0
  132. data/test/cases/query_cache_test.rb +123 -0
  133. data/test/cases/readonly_test.rb +107 -0
  134. data/test/cases/reflection_test.rb +194 -0
  135. data/test/cases/reload_models_test.rb +22 -0
  136. data/test/cases/repair_helper.rb +50 -0
  137. data/test/cases/reserved_word_test_mysql.rb +176 -0
  138. data/test/cases/sanitize_test.rb +25 -0
  139. data/test/cases/schema_authorization_test_postgresql.rb +75 -0
  140. data/test/cases/schema_dumper_test.rb +211 -0
  141. data/test/cases/schema_test_postgresql.rb +178 -0
  142. data/test/cases/serialization_test.rb +47 -0
  143. data/test/cases/synonym_test_oracle.rb +17 -0
  144. data/test/cases/timestamp_test.rb +75 -0
  145. data/test/cases/transactions_test.rb +522 -0
  146. data/test/cases/unconnected_test.rb +32 -0
  147. data/test/cases/validations_i18n_test.rb +955 -0
  148. data/test/cases/validations_test.rb +1640 -0
  149. data/test/cases/xml_serialization_test.rb +240 -0
  150. data/test/config.rb +5 -0
  151. data/test/connections/jdbc_jdbcderby/connection.rb +18 -0
  152. data/test/connections/jdbc_jdbch2/connection.rb +18 -0
  153. data/test/connections/jdbc_jdbchsqldb/connection.rb +18 -0
  154. data/test/connections/jdbc_jdbcmysql/connection.rb +26 -0
  155. data/test/connections/jdbc_jdbcpostgresql/connection.rb +26 -0
  156. data/test/connections/jdbc_jdbcsqlite3/connection.rb +25 -0
  157. data/test/connections/native_db2/connection.rb +25 -0
  158. data/test/connections/native_firebird/connection.rb +26 -0
  159. data/test/connections/native_frontbase/connection.rb +27 -0
  160. data/test/connections/native_mysql/connection.rb +25 -0
  161. data/test/connections/native_openbase/connection.rb +21 -0
  162. data/test/connections/native_oracle/connection.rb +27 -0
  163. data/test/connections/native_postgresql/connection.rb +25 -0
  164. data/test/connections/native_sqlite/connection.rb +25 -0
  165. data/test/connections/native_sqlite3/connection.rb +25 -0
  166. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  167. data/test/connections/native_sybase/connection.rb +23 -0
  168. data/test/fixtures/accounts.yml +29 -0
  169. data/test/fixtures/all/developers.yml +0 -0
  170. data/test/fixtures/all/people.csv +0 -0
  171. data/test/fixtures/all/tasks.yml +0 -0
  172. data/test/fixtures/author_addresses.yml +5 -0
  173. data/test/fixtures/author_favorites.yml +4 -0
  174. data/test/fixtures/authors.yml +9 -0
  175. data/test/fixtures/binaries.yml +132 -0
  176. data/test/fixtures/books.yml +7 -0
  177. data/test/fixtures/categories/special_categories.yml +9 -0
  178. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  179. data/test/fixtures/categories.yml +14 -0
  180. data/test/fixtures/categories_ordered.yml +7 -0
  181. data/test/fixtures/categories_posts.yml +23 -0
  182. data/test/fixtures/categorizations.yml +17 -0
  183. data/test/fixtures/clubs.yml +6 -0
  184. data/test/fixtures/comments.yml +59 -0
  185. data/test/fixtures/companies.yml +56 -0
  186. data/test/fixtures/computers.yml +4 -0
  187. data/test/fixtures/courses.yml +7 -0
  188. data/test/fixtures/customers.yml +26 -0
  189. data/test/fixtures/developers.yml +21 -0
  190. data/test/fixtures/developers_projects.yml +17 -0
  191. data/test/fixtures/edges.yml +6 -0
  192. data/test/fixtures/entrants.yml +14 -0
  193. data/test/fixtures/fixture_database.sqlite3 +0 -0
  194. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  195. data/test/fixtures/fk_test_has_fk.yml +3 -0
  196. data/test/fixtures/fk_test_has_pk.yml +2 -0
  197. data/test/fixtures/funny_jokes.yml +10 -0
  198. data/test/fixtures/items.yml +4 -0
  199. data/test/fixtures/jobs.yml +7 -0
  200. data/test/fixtures/legacy_things.yml +3 -0
  201. data/test/fixtures/mateys.yml +4 -0
  202. data/test/fixtures/member_types.yml +6 -0
  203. data/test/fixtures/members.yml +6 -0
  204. data/test/fixtures/memberships.yml +20 -0
  205. data/test/fixtures/minimalistics.yml +2 -0
  206. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  207. data/test/fixtures/mixins.yml +29 -0
  208. data/test/fixtures/movies.yml +7 -0
  209. data/test/fixtures/naked/csv/accounts.csv +1 -0
  210. data/test/fixtures/naked/yml/accounts.yml +1 -0
  211. data/test/fixtures/naked/yml/companies.yml +1 -0
  212. data/test/fixtures/naked/yml/courses.yml +1 -0
  213. data/test/fixtures/organizations.yml +5 -0
  214. data/test/fixtures/owners.yml +7 -0
  215. data/test/fixtures/parrots.yml +27 -0
  216. data/test/fixtures/parrots_pirates.yml +7 -0
  217. data/test/fixtures/people.yml +15 -0
  218. data/test/fixtures/pets.yml +14 -0
  219. data/test/fixtures/pirates.yml +9 -0
  220. data/test/fixtures/posts.yml +52 -0
  221. data/test/fixtures/price_estimates.yml +7 -0
  222. data/test/fixtures/projects.yml +7 -0
  223. data/test/fixtures/readers.yml +9 -0
  224. data/test/fixtures/references.yml +17 -0
  225. data/test/fixtures/reserved_words/distinct.yml +5 -0
  226. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  227. data/test/fixtures/reserved_words/group.yml +14 -0
  228. data/test/fixtures/reserved_words/select.yml +8 -0
  229. data/test/fixtures/reserved_words/values.yml +7 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/sponsors.yml +9 -0
  232. data/test/fixtures/subscribers.yml +7 -0
  233. data/test/fixtures/subscriptions.yml +12 -0
  234. data/test/fixtures/taggings.yml +28 -0
  235. data/test/fixtures/tags.yml +7 -0
  236. data/test/fixtures/tasks.yml +7 -0
  237. data/test/fixtures/topics.yml +42 -0
  238. data/test/fixtures/toys.yml +4 -0
  239. data/test/fixtures/treasures.yml +10 -0
  240. data/test/fixtures/vertices.yml +4 -0
  241. data/test/fixtures/warehouse-things.yml +3 -0
  242. data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
  243. data/test/migrations/decimal/1_give_me_big_numbers.rb +15 -0
  244. data/test/migrations/duplicate/1_people_have_last_names.rb +9 -0
  245. data/test/migrations/duplicate/2_we_need_reminders.rb +12 -0
  246. data/test/migrations/duplicate/3_foo.rb +7 -0
  247. data/test/migrations/duplicate/3_innocent_jointable.rb +12 -0
  248. data/test/migrations/duplicate_names/20080507052938_chunky.rb +7 -0
  249. data/test/migrations/duplicate_names/20080507053028_chunky.rb +7 -0
  250. data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +12 -0
  251. data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +9 -0
  252. data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +12 -0
  253. data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +9 -0
  254. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +8 -0
  255. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +12 -0
  256. data/test/migrations/missing/1000_people_have_middle_names.rb +9 -0
  257. data/test/migrations/missing/1_people_have_last_names.rb +9 -0
  258. data/test/migrations/missing/3_we_need_reminders.rb +12 -0
  259. data/test/migrations/missing/4_innocent_jointable.rb +12 -0
  260. data/test/migrations/valid/1_people_have_last_names.rb +9 -0
  261. data/test/migrations/valid/2_we_need_reminders.rb +12 -0
  262. data/test/migrations/valid/3_innocent_jointable.rb +12 -0
  263. data/test/models/author.rb +146 -0
  264. data/test/models/auto_id.rb +4 -0
  265. data/test/models/binary.rb +2 -0
  266. data/test/models/bird.rb +3 -0
  267. data/test/models/book.rb +4 -0
  268. data/test/models/categorization.rb +5 -0
  269. data/test/models/category.rb +34 -0
  270. data/test/models/citation.rb +6 -0
  271. data/test/models/club.rb +13 -0
  272. data/test/models/column_name.rb +3 -0
  273. data/test/models/comment.rb +29 -0
  274. data/test/models/company.rb +171 -0
  275. data/test/models/company_in_module.rb +61 -0
  276. data/test/models/computer.rb +3 -0
  277. data/test/models/contact.rb +16 -0
  278. data/test/models/contract.rb +5 -0
  279. data/test/models/course.rb +3 -0
  280. data/test/models/customer.rb +73 -0
  281. data/test/models/default.rb +2 -0
  282. data/test/models/developer.rb +101 -0
  283. data/test/models/edge.rb +5 -0
  284. data/test/models/entrant.rb +3 -0
  285. data/test/models/essay.rb +3 -0
  286. data/test/models/event.rb +3 -0
  287. data/test/models/guid.rb +2 -0
  288. data/test/models/item.rb +7 -0
  289. data/test/models/job.rb +5 -0
  290. data/test/models/joke.rb +3 -0
  291. data/test/models/keyboard.rb +3 -0
  292. data/test/models/legacy_thing.rb +3 -0
  293. data/test/models/matey.rb +4 -0
  294. data/test/models/member.rb +12 -0
  295. data/test/models/member_detail.rb +5 -0
  296. data/test/models/member_type.rb +3 -0
  297. data/test/models/membership.rb +9 -0
  298. data/test/models/minimalistic.rb +2 -0
  299. data/test/models/mixed_case_monkey.rb +3 -0
  300. data/test/models/movie.rb +5 -0
  301. data/test/models/order.rb +4 -0
  302. data/test/models/organization.rb +6 -0
  303. data/test/models/owner.rb +5 -0
  304. data/test/models/parrot.rb +16 -0
  305. data/test/models/person.rb +16 -0
  306. data/test/models/pet.rb +5 -0
  307. data/test/models/pirate.rb +70 -0
  308. data/test/models/post.rb +100 -0
  309. data/test/models/price_estimate.rb +3 -0
  310. data/test/models/project.rb +30 -0
  311. data/test/models/reader.rb +4 -0
  312. data/test/models/reference.rb +4 -0
  313. data/test/models/reply.rb +46 -0
  314. data/test/models/ship.rb +10 -0
  315. data/test/models/ship_part.rb +5 -0
  316. data/test/models/sponsor.rb +4 -0
  317. data/test/models/subject.rb +4 -0
  318. data/test/models/subscriber.rb +8 -0
  319. data/test/models/subscription.rb +4 -0
  320. data/test/models/tag.rb +7 -0
  321. data/test/models/tagging.rb +10 -0
  322. data/test/models/task.rb +3 -0
  323. data/test/models/topic.rb +80 -0
  324. data/test/models/toy.rb +6 -0
  325. data/test/models/treasure.rb +8 -0
  326. data/test/models/vertex.rb +9 -0
  327. data/test/models/warehouse_thing.rb +5 -0
  328. data/test/schema/mysql_specific_schema.rb +24 -0
  329. data/test/schema/postgresql_specific_schema.rb +114 -0
  330. data/test/schema/schema.rb +493 -0
  331. data/test/schema/schema2.rb +6 -0
  332. data/test/schema/sqlite_specific_schema.rb +25 -0
  333. metadata +420 -0
@@ -0,0 +1,388 @@
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
+ base.cattr_accessor :time_zone_aware_attributes, :instance_writer => false
12
+ base.time_zone_aware_attributes = false
13
+ base.class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
14
+ base.skip_time_zone_conversion_for_attributes = []
15
+ end
16
+
17
+ # Declare and check for suffixed attribute methods.
18
+ module ClassMethods
19
+ # Declares a method available for all attributes with the given suffix.
20
+ # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method
21
+ #
22
+ # #{attr}#{suffix}(*args, &block)
23
+ #
24
+ # to
25
+ #
26
+ # attribute#{suffix}(#{attr}, *args, &block)
27
+ #
28
+ # An <tt>attribute#{suffix}</tt> instance method must exist and accept at least
29
+ # the +attr+ argument.
30
+ #
31
+ # For example:
32
+ #
33
+ # class Person < ActiveRecord::Base
34
+ # attribute_method_suffix '_changed?'
35
+ #
36
+ # private
37
+ # def attribute_changed?(attr)
38
+ # ...
39
+ # end
40
+ # end
41
+ #
42
+ # person = Person.find(1)
43
+ # person.name_changed? # => false
44
+ # person.name = 'Hubert'
45
+ # person.name_changed? # => true
46
+ def attribute_method_suffix(*suffixes)
47
+ attribute_method_suffixes.concat suffixes
48
+ rebuild_attribute_method_regexp
49
+ end
50
+
51
+ # Returns MatchData if method_name is an attribute method.
52
+ def match_attribute_method?(method_name)
53
+ rebuild_attribute_method_regexp unless defined?(@@attribute_method_regexp) && @@attribute_method_regexp
54
+ @@attribute_method_regexp.match(method_name)
55
+ end
56
+
57
+
58
+ # Contains the names of the generated attribute methods.
59
+ def generated_methods #:nodoc:
60
+ @generated_methods ||= Set.new
61
+ end
62
+
63
+ def generated_methods?
64
+ !generated_methods.empty?
65
+ end
66
+
67
+ # Generates all the attribute related methods for columns in the database
68
+ # accessors, mutators and query methods.
69
+ def define_attribute_methods
70
+ return if generated_methods?
71
+ columns_hash.each do |name, column|
72
+ unless instance_method_already_implemented?(name)
73
+ if self.serialized_attributes[name]
74
+ define_read_method_for_serialized_attribute(name)
75
+ elsif create_time_zone_conversion_attribute?(name, column)
76
+ define_read_method_for_time_zone_conversion(name)
77
+ else
78
+ define_read_method(name.to_sym, name, column)
79
+ end
80
+ end
81
+
82
+ unless instance_method_already_implemented?("#{name}=")
83
+ if create_time_zone_conversion_attribute?(name, column)
84
+ define_write_method_for_time_zone_conversion(name)
85
+ else
86
+ define_write_method(name.to_sym)
87
+ end
88
+ end
89
+
90
+ unless instance_method_already_implemented?("#{name}?")
91
+ define_question_method(name)
92
+ end
93
+ end
94
+ end
95
+
96
+ # Checks whether the method is defined in the model or any of its subclasses
97
+ # that also derive from Active Record. Raises DangerousAttributeError if the
98
+ # method is defined by Active Record though.
99
+ def instance_method_already_implemented?(method_name)
100
+ method_name = method_name.to_s
101
+ return true if method_name =~ /^id(=$|\?$|$)/
102
+ @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map(&:to_s).to_set
103
+ @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map(&:to_s).to_set
104
+ raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
105
+ @_defined_class_methods.include?(method_name)
106
+ end
107
+
108
+ alias :define_read_methods :define_attribute_methods
109
+
110
+ # +cache_attributes+ allows you to declare which converted attribute values should
111
+ # be cached. Usually caching only pays off for attributes with expensive conversion
112
+ # methods, like time related columns (e.g. +created_at+, +updated_at+).
113
+ def cache_attributes(*attribute_names)
114
+ attribute_names.each {|attr| cached_attributes << attr.to_s}
115
+ end
116
+
117
+ # Returns the attributes which are cached. By default time related columns
118
+ # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
119
+ def cached_attributes
120
+ @cached_attributes ||=
121
+ columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map(&:name).to_set
122
+ end
123
+
124
+ # Returns +true+ if the provided attribute is being cached.
125
+ def cache_attribute?(attr_name)
126
+ cached_attributes.include?(attr_name)
127
+ end
128
+
129
+ private
130
+ # Suffixes a, ?, c become regexp /(a|\?|c)$/
131
+ def rebuild_attribute_method_regexp
132
+ suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) }
133
+ @@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze
134
+ end
135
+
136
+ # Default to =, ?, _before_type_cast
137
+ def attribute_method_suffixes
138
+ @@attribute_method_suffixes ||= []
139
+ end
140
+
141
+ def create_time_zone_conversion_attribute?(name, column)
142
+ time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
143
+ end
144
+
145
+ # Define an attribute reader method. Cope with nil column.
146
+ def define_read_method(symbol, attr_name, column)
147
+ cast_code = column.type_cast_code('v') if column
148
+ access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
149
+
150
+ unless attr_name.to_s == self.primary_key.to_s
151
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
152
+ end
153
+
154
+ if cache_attribute?(attr_name)
155
+ access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
156
+ end
157
+ evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end"
158
+ end
159
+
160
+ # Define read method for serialized attribute.
161
+ def define_read_method_for_serialized_attribute(attr_name)
162
+ evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
163
+ end
164
+
165
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
166
+ # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
167
+ def define_read_method_for_time_zone_conversion(attr_name)
168
+ method_body = <<-EOV
169
+ def #{attr_name}(reload = false)
170
+ cached = @attributes_cache['#{attr_name}']
171
+ return cached if cached && !reload
172
+ time = read_attribute('#{attr_name}')
173
+ @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
174
+ end
175
+ EOV
176
+ evaluate_attribute_method attr_name, method_body
177
+ end
178
+
179
+ # Defines a predicate method <tt>attr_name?</tt>.
180
+ def define_question_method(attr_name)
181
+ evaluate_attribute_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end", "#{attr_name}?"
182
+ end
183
+
184
+ def define_write_method(attr_name)
185
+ evaluate_attribute_method attr_name, "def #{attr_name}=(new_value);write_attribute('#{attr_name}', new_value);end", "#{attr_name}="
186
+ end
187
+
188
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
189
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
190
+ def define_write_method_for_time_zone_conversion(attr_name)
191
+ method_body = <<-EOV
192
+ def #{attr_name}=(time)
193
+ unless time.acts_like?(:time)
194
+ time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
195
+ end
196
+ time = time.in_time_zone rescue nil if time
197
+ write_attribute(:#{attr_name}, time)
198
+ end
199
+ EOV
200
+ evaluate_attribute_method attr_name, method_body, "#{attr_name}="
201
+ end
202
+
203
+ # Evaluate the definition for an attribute related method
204
+ def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name)
205
+
206
+ unless method_name.to_s == primary_key.to_s
207
+ generated_methods << method_name
208
+ end
209
+
210
+ begin
211
+ class_eval(method_definition, __FILE__, __LINE__)
212
+ rescue SyntaxError => err
213
+ generated_methods.delete(attr_name)
214
+ if logger
215
+ logger.warn "Exception occurred during reader method compilation."
216
+ logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
217
+ logger.warn err.message
218
+ end
219
+ end
220
+ end
221
+ end # ClassMethods
222
+
223
+
224
+ # Allows access to the object attributes, which are held in the <tt>@attributes</tt> hash, as though they
225
+ # were first-class methods. So a Person class with a name attribute can use Person#name and
226
+ # Person#name= and never directly use the attributes hash -- except for multiple assigns with
227
+ # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
228
+ # the completed attribute is not +nil+ or 0.
229
+ #
230
+ # It's also possible to instantiate related objects, so a Client class belonging to the clients
231
+ # table with a +master_id+ foreign key can instantiate master through Client#master.
232
+ def method_missing(method_id, *args, &block)
233
+ method_name = method_id.to_s
234
+
235
+ if self.class.private_method_defined?(method_name)
236
+ raise NoMethodError.new("Attempt to call private method", method_name, args)
237
+ end
238
+
239
+ # If we haven't generated any methods yet, generate them, then
240
+ # see if we've created the method we're looking for.
241
+ if !self.class.generated_methods?
242
+ self.class.define_attribute_methods
243
+ if self.class.generated_methods.include?(method_name)
244
+ return self.send(method_id, *args, &block)
245
+ end
246
+ end
247
+
248
+ if self.class.primary_key.to_s == method_name
249
+ id
250
+ elsif md = self.class.match_attribute_method?(method_name)
251
+ attribute_name, method_type = md.pre_match, md.to_s
252
+ if @attributes.include?(attribute_name)
253
+ __send__("attribute#{method_type}", attribute_name, *args, &block)
254
+ else
255
+ super
256
+ end
257
+ elsif @attributes.include?(method_name)
258
+ read_attribute(method_name)
259
+ else
260
+ super
261
+ end
262
+ end
263
+
264
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
265
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
266
+ def read_attribute(attr_name)
267
+ attr_name = attr_name.to_s
268
+ if !(value = @attributes[attr_name]).nil?
269
+ if column = column_for_attribute(attr_name)
270
+ if unserializable_attribute?(attr_name, column)
271
+ unserialize_attribute(attr_name)
272
+ else
273
+ column.type_cast(value)
274
+ end
275
+ else
276
+ value
277
+ end
278
+ else
279
+ nil
280
+ end
281
+ end
282
+
283
+ def read_attribute_before_type_cast(attr_name)
284
+ @attributes[attr_name]
285
+ end
286
+
287
+ # Returns true if the attribute is of a text column and marked for serialization.
288
+ def unserializable_attribute?(attr_name, column)
289
+ column.text? && self.class.serialized_attributes[attr_name]
290
+ end
291
+
292
+ # Returns the unserialized object of the attribute.
293
+ def unserialize_attribute(attr_name)
294
+ unserialized_object = object_from_yaml(@attributes[attr_name])
295
+
296
+ if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
297
+ @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
298
+ else
299
+ raise SerializationTypeMismatch,
300
+ "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
301
+ end
302
+ end
303
+
304
+
305
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
306
+ # columns are turned into +nil+.
307
+ def write_attribute(attr_name, value)
308
+ attr_name = attr_name.to_s
309
+ @attributes_cache.delete(attr_name)
310
+ if (column = column_for_attribute(attr_name)) && column.number?
311
+ @attributes[attr_name] = convert_number_column_value(value)
312
+ else
313
+ @attributes[attr_name] = value
314
+ end
315
+ end
316
+
317
+
318
+ def query_attribute(attr_name)
319
+ unless value = read_attribute(attr_name)
320
+ false
321
+ else
322
+ column = self.class.columns_hash[attr_name]
323
+ if column.nil?
324
+ if Numeric === value || value !~ /[^0-9]/
325
+ !value.to_i.zero?
326
+ else
327
+ return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
328
+ !value.blank?
329
+ end
330
+ elsif column.number?
331
+ !value.zero?
332
+ else
333
+ !value.blank?
334
+ end
335
+ end
336
+ end
337
+
338
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
339
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
340
+ # which will all return +true+.
341
+ alias :respond_to_without_attributes? :respond_to?
342
+ def respond_to?(method, include_private_methods = false)
343
+ method_name = method.to_s
344
+ if super
345
+ return true
346
+ elsif !include_private_methods && super(method, true)
347
+ # If we're here than we haven't found among non-private methods
348
+ # but found among all methods. Which means that given method is private.
349
+ return false
350
+ elsif !self.class.generated_methods?
351
+ self.class.define_attribute_methods
352
+ if self.class.generated_methods.include?(method_name)
353
+ return true
354
+ end
355
+ end
356
+
357
+ if @attributes.nil?
358
+ return super
359
+ elsif @attributes.include?(method_name)
360
+ return true
361
+ elsif md = self.class.match_attribute_method?(method_name)
362
+ return true if @attributes.include?(md.pre_match)
363
+ end
364
+ super
365
+ end
366
+
367
+ private
368
+
369
+ def missing_attribute(attr_name, stack)
370
+ raise ActiveRecord::MissingAttributeError, "missing attribute: #{attr_name}", stack
371
+ end
372
+
373
+ # Handle *? for method_missing.
374
+ def attribute?(attribute_name)
375
+ query_attribute(attribute_name)
376
+ end
377
+
378
+ # Handle *= for method_missing.
379
+ def attribute=(attribute_name, value)
380
+ write_attribute(attribute_name, value)
381
+ end
382
+
383
+ # Handle *_before_type_cast for method_missing.
384
+ def attribute_before_type_cast(attribute_name)
385
+ read_attribute_before_type_cast(attribute_name)
386
+ end
387
+ end
388
+ end