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
@@ -1,38 +1,105 @@
1
1
  require 'singleton'
2
+ require 'set'
2
3
 
3
4
  module ActiveRecord
4
- # Observers can be programmed to react to lifecycle callbacks in another class to implement
5
- # trigger-like behavior outside the original class. This is a great way to reduce the clutter that
6
- # normally comes when the model class is burdened with excess responsibility that doesn't pertain to
7
- # the core and nature of the class. Example:
5
+ module Observing # :nodoc:
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ # Activates the observers assigned. Examples:
12
+ #
13
+ # # Calls PersonObserver.instance
14
+ # ActiveRecord::Base.observers = :person_observer
15
+ #
16
+ # # Calls Cacher.instance and GarbageCollector.instance
17
+ # ActiveRecord::Base.observers = :cacher, :garbage_collector
18
+ #
19
+ # # Same as above, just using explicit class references
20
+ # ActiveRecord::Base.observers = Cacher, GarbageCollector
21
+ #
22
+ # Note: Setting this does not instantiate the observers yet. #instantiate_observers is
23
+ # called during startup, and before each development request.
24
+ def observers=(*observers)
25
+ @observers = observers.flatten
26
+ end
27
+
28
+ # Gets the current observers.
29
+ def observers
30
+ @observers ||= []
31
+ end
32
+
33
+ # Instantiate the global ActiveRecord observers
34
+ def instantiate_observers
35
+ return if @observers.blank?
36
+ @observers.each do |observer|
37
+ if observer.respond_to?(:to_sym) # Symbol or String
38
+ observer.to_s.camelize.constantize.instance
39
+ elsif observer.respond_to?(:instance)
40
+ observer.instance
41
+ else
42
+ raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
43
+ end
44
+ end
45
+ end
46
+
47
+ protected
48
+ # Notify observers when the observed class is subclassed.
49
+ def inherited(subclass)
50
+ super
51
+ changed
52
+ notify_observers :observed_class_inherited, subclass
53
+ end
54
+ end
55
+ end
56
+
57
+ # Observer classes respond to lifecycle callbacks to implement trigger-like
58
+ # behavior outside the original class. This is a great way to reduce the
59
+ # clutter that normally comes when the model class is burdened with
60
+ # functionality that doesn't pertain to the core responsibility of the
61
+ # class. Example:
8
62
  #
9
63
  # class CommentObserver < ActiveRecord::Observer
10
64
  # def after_save(comment)
11
- # NotificationServer.send_email("admin@do.com", "New comment was posted", comment)
65
+ # Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
12
66
  # end
13
67
  # end
14
68
  #
15
- # This Observer is triggered when a Comment#save is finished and sends a notification about it to the administrator.
69
+ # This Observer sends an email when a Comment#save is finished.
16
70
  #
17
- # == Observing a class that can't be infered
71
+ # class ContactObserver < ActiveRecord::Observer
72
+ # def after_create(contact)
73
+ # contact.logger.info('New contact added!')
74
+ # end
75
+ #
76
+ # def after_destroy(contact)
77
+ # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
78
+ # end
79
+ # end
80
+ #
81
+ # This Observer uses logger to log when specific callbacks are triggered.
82
+ #
83
+ # == Observing a class that can't be inferred
18
84
  #
19
85
  # Observers will by default be mapped to the class with which they share a name. So CommentObserver will
20
86
  # be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
21
- # something else than the class you're interested in observing, you can implement the observed_class class method. Like this:
87
+ # differently than the class you're interested in observing, you can use the Observer.observe class method which takes
88
+ # either the concrete class (Product) or a symbol for that class (:product):
22
89
  #
23
90
  # class AuditObserver < ActiveRecord::Observer
24
- # def self.observed_class() Account end
91
+ # observe :account
92
+ #
25
93
  # def after_update(account)
26
94
  # AuditTrail.new(account, "UPDATED")
27
95
  # end
28
96
  # end
29
97
  #
30
- # == Observing multiple classes at once
31
- #
32
- # If the audit observer needs to watch more than one kind of object, this can be specified in an array, like this:
98
+ # If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
33
99
  #
34
100
  # class AuditObserver < ActiveRecord::Observer
35
- # def self.observed_class() [ Account, Balance ] end
101
+ # observe :account, :balance
102
+ #
36
103
  # def after_update(record)
37
104
  # AuditTrail.new(record, "UPDATED")
38
105
  # end
@@ -40,32 +107,75 @@ module ActiveRecord
40
107
  #
41
108
  # The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
42
109
  #
110
+ # == Available callback methods
111
+ #
43
112
  # The observer can implement callback methods for each of the methods described in the Callbacks module.
113
+ #
114
+ # == Storing Observers in Rails
115
+ #
116
+ # If you're using Active Record within Rails, observer classes are usually stored in app/models with the
117
+ # naming convention of app/models/audit_observer.rb.
118
+ #
119
+ # == Configuration
120
+ #
121
+ # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration setting in your
122
+ # <tt>config/environment.rb</tt> file.
123
+ #
124
+ # config.active_record.observers = :comment_observer, :signup_observer
125
+ #
126
+ # Observers will not be invoked unless you define these in your application configuration.
127
+ #
44
128
  class Observer
45
129
  include Singleton
46
-
47
- def initialize
48
- [ observed_class ].flatten.each do |klass|
49
- klass.add_observer(self)
50
- klass.send(:define_method, :after_find) unless klass.respond_to?(:after_find)
130
+
131
+ class << self
132
+ # Attaches the observer to the supplied model classes.
133
+ def observe(*models)
134
+ models.flatten!
135
+ models.collect! { |model| model.is_a?(Symbol) ? model.to_s.camelize.constantize : model }
136
+ define_method(:observed_classes) { Set.new(models) }
51
137
  end
52
- end
53
-
54
- def update(callback_method, object)
55
- send(callback_method, object) if respond_to?(callback_method)
56
- end
57
-
58
- private
138
+
139
+ # The class observed by default is inferred from the observer's class name:
140
+ # assert_equal [Person], PersonObserver.observed_class
59
141
  def observed_class
60
- if self.class.respond_to? "observed_class"
61
- self.class.observed_class
142
+ if observed_class_name = name.scan(/(.*)Observer/)[0]
143
+ observed_class_name[0].constantize
62
144
  else
63
- Object.const_get(infer_observed_class_name)
145
+ nil
64
146
  end
65
147
  end
66
-
67
- def infer_observed_class_name
68
- self.class.name.scan(/(.*)Observer/)[0][0]
148
+ end
149
+
150
+ # Start observing the declared classes and their subclasses.
151
+ def initialize
152
+ Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass }
153
+ end
154
+
155
+ # Send observed_method(object) if the method exists.
156
+ def update(observed_method, object) #:nodoc:
157
+ send(observed_method, object) if respond_to?(observed_method)
158
+ end
159
+
160
+ # Special method sent by the observed class when it is inherited.
161
+ # Passes the new subclass.
162
+ def observed_class_inherited(subclass) #:nodoc:
163
+ self.class.observe(observed_classes + [subclass])
164
+ add_observer!(subclass)
165
+ end
166
+
167
+ protected
168
+ def observed_classes
169
+ Set.new([self.class.observed_class].compact.flatten)
170
+ end
171
+
172
+ def observed_subclasses
173
+ observed_classes.collect(&:subclasses).flatten
174
+ end
175
+
176
+ def add_observer!(klass)
177
+ klass.add_observer(self)
178
+ klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find)
69
179
  end
70
180
  end
71
- end
181
+ end
@@ -0,0 +1,23 @@
1
+ module ActiveRecord
2
+ module QueryCache
3
+ # Enable the query cache within the block if Active Record is configured.
4
+ def cache(&block)
5
+ if ActiveRecord::Base.configurations.blank?
6
+ yield
7
+ else
8
+ connection.cache(&block)
9
+ end
10
+ rescue
11
+ yield # if the database is not present, don't let the cache spoil the party
12
+ end
13
+
14
+ # Disable the query cache within the block if Active Record is configured.
15
+ def uncached(&block)
16
+ if ActiveRecord::Base.configurations.blank?
17
+ yield
18
+ else
19
+ connection.uncached(&block)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,126 +1,219 @@
1
1
  module ActiveRecord
2
2
  module Reflection # :nodoc:
3
- def self.append_features(base)
4
- super
3
+ def self.included(base)
5
4
  base.extend(ClassMethods)
6
-
7
- base.class_eval do
8
- class << self
9
- alias_method :composed_of_without_reflection, :composed_of
10
-
11
- def composed_of_with_reflection(part_id, options = {})
12
- composed_of_without_reflection(part_id, options)
13
- write_inheritable_array "aggregations", [ AggregateReflection.new(part_id, options, self) ]
14
- end
15
-
16
- alias_method :composed_of, :composed_of_with_reflection
17
- end
18
- end
19
-
20
- for association_type in %w( belongs_to has_one has_many has_and_belongs_to_many )
21
- base.module_eval <<-"end_eval"
22
- class << self
23
- alias_method :#{association_type}_without_reflection, :#{association_type}
24
-
25
- def #{association_type}_with_reflection(association_id, options = {})
26
- #{association_type}_without_reflection(association_id, options)
27
- write_inheritable_array "associations", [ AssociationReflection.new(association_id, options, self) ]
28
- end
29
-
30
- alias_method :#{association_type}, :#{association_type}_with_reflection
31
- end
32
- end_eval
33
- end
34
5
  end
35
6
 
36
- # Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
37
- # This information can for example be used in a form builder that took an Active Record object and created input
7
+ # Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
8
+ # This information can, for example, be used in a form builder that took an Active Record object and created input
38
9
  # fields for all of the attributes depending on their type and displayed the associations to other objects.
39
10
  #
40
11
  # You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
41
12
  module ClassMethods
13
+ def create_reflection(macro, name, options, active_record)
14
+ case macro
15
+ when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
16
+ reflection = AssociationReflection.new(macro, name, options, active_record)
17
+ when :composed_of
18
+ reflection = AggregateReflection.new(macro, name, options, active_record)
19
+ end
20
+ write_inheritable_hash :reflections, name => reflection
21
+ reflection
22
+ end
23
+
24
+ # Returns a hash containing all AssociationReflection objects for the current class
25
+ # Example:
26
+ #
27
+ # Invoice.reflections
28
+ # Account.reflections
29
+ #
30
+ def reflections
31
+ read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
32
+ end
33
+
42
34
  # Returns an array of AggregateReflection objects for all the aggregations in the class.
43
35
  def reflect_on_all_aggregations
44
- read_inheritable_attribute "aggregations"
36
+ reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
45
37
  end
46
-
38
+
47
39
  # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). Example:
40
+ #
48
41
  # Account.reflect_on_aggregation(:balance) # returns the balance AggregateReflection
42
+ #
49
43
  def reflect_on_aggregation(aggregation)
50
- reflect_on_all_aggregations.find { |reflection| reflection.name == aggregation } unless reflect_on_all_aggregations.nil?
44
+ reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
51
45
  end
52
46
 
53
- # Returns an array of AssociationReflection objects for all the aggregations in the class.
54
- def reflect_on_all_associations
55
- read_inheritable_attribute "associations"
47
+ # Returns an array of AssociationReflection objects for all the associations in the class. If you only want to reflect on a
48
+ # certain association type, pass in the symbol (:has_many, :has_one, :belongs_to) for that as the first parameter.
49
+ # Example:
50
+ #
51
+ # Account.reflect_on_all_associations # returns an array of all associations
52
+ # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
53
+ #
54
+ def reflect_on_all_associations(macro = nil)
55
+ association_reflections = reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) }
56
+ macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
56
57
  end
57
-
58
- # Returns the AssociationReflection object for the named +aggregation+ (use the symbol). Example:
58
+
59
+ # Returns the AssociationReflection object for the named +association+ (use the symbol). Example:
60
+ #
59
61
  # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
62
+ # Invoice.reflect_on_association(:line_items).macro # returns :has_many
63
+ #
60
64
  def reflect_on_association(association)
61
- reflect_on_all_associations.find { |reflection| reflection.name == association } unless reflect_on_all_associations.nil?
65
+ reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
62
66
  end
63
67
  end
64
-
65
68
 
66
- # Abstract base class for AggregateReflection and AssociationReflection that describes the interface available for both of
69
+
70
+ # Abstract base class for AggregateReflection and AssociationReflection that describes the interface available for both of
67
71
  # those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
68
72
  class MacroReflection
69
73
  attr_reader :active_record
70
- def initialize(name, options, active_record)
71
- @name, @options, @active_record = name, options, active_record
74
+
75
+ def initialize(macro, name, options, active_record)
76
+ @macro, @name, @options, @active_record = macro, name, options, active_record
72
77
  end
73
-
78
+
74
79
  # Returns the name of the macro, so it would return :balance for "composed_of :balance, :class_name => 'Money'" or
75
80
  # :clients for "has_many :clients".
76
81
  def name
77
82
  @name
78
83
  end
79
-
80
- # Returns the hash of options used for the macro, so it would return { :class_name => "Money" } for
84
+
85
+ # Returns the type of the macro, so it would return :composed_of for
86
+ # "composed_of :balance, :class_name => 'Money'" or :has_many for "has_many :clients".
87
+ def macro
88
+ @macro
89
+ end
90
+
91
+ # Returns the hash of options used for the macro, so it would return { :class_name => "Money" } for
81
92
  # "composed_of :balance, :class_name => 'Money'" or {} for "has_many :clients".
82
93
  def options
83
94
  @options
84
95
  end
85
-
86
- # Returns the class for the macro, so "composed_of :balance, :class_name => 'Money'" would return the Money class and
87
- # "has_many :clients" would return the Client class.
88
- def klass() end
89
-
96
+
97
+ # Returns the class for the macro, so "composed_of :balance, :class_name => 'Money'" returns the Money class and
98
+ # "has_many :clients" returns the Client class.
99
+ def klass
100
+ @klass ||= class_name.constantize
101
+ end
102
+
103
+ def class_name
104
+ @class_name ||= options[:class_name] || derive_class_name
105
+ end
106
+
90
107
  def ==(other_aggregation)
91
108
  name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
92
109
  end
110
+
111
+ private
112
+ def derive_class_name
113
+ name.to_s.camelize
114
+ end
93
115
  end
94
116
 
95
117
 
96
118
  # Holds all the meta-data about an aggregation as it was specified in the Active Record class.
97
119
  class AggregateReflection < MacroReflection #:nodoc:
98
- def klass
99
- Object.const_get(options[:class_name] || name_to_class_name(name.id2name))
100
- end
101
-
102
- private
103
- def name_to_class_name(name)
104
- name.capitalize.gsub(/_(.)/) { |s| $1.capitalize }
105
- end
106
120
  end
107
121
 
108
122
  # Holds all the meta-data about an association as it was specified in the Active Record class.
109
123
  class AssociationReflection < MacroReflection #:nodoc:
110
124
  def klass
111
- active_record.send(:compute_type, (name_to_class_name(name.id2name)))
125
+ @klass ||= active_record.send(:compute_type, class_name)
126
+ end
127
+
128
+ def table_name
129
+ @table_name ||= klass.table_name
130
+ end
131
+
132
+ def primary_key_name
133
+ @primary_key_name ||= options[:foreign_key] || derive_primary_key_name
134
+ end
135
+
136
+ def association_foreign_key
137
+ @association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
138
+ end
139
+
140
+ def counter_cache_column
141
+ if options[:counter_cache] == true
142
+ "#{active_record.name.underscore.pluralize}_count"
143
+ elsif options[:counter_cache]
144
+ options[:counter_cache]
145
+ end
146
+ end
147
+
148
+ def through_reflection
149
+ @through_reflection ||= options[:through] ? active_record.reflect_on_association(options[:through]) : false
150
+ end
151
+
152
+ # Gets an array of possible :through source reflection names
153
+ #
154
+ # [singularized, pluralized]
155
+ #
156
+ def source_reflection_names
157
+ @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
158
+ end
159
+
160
+ # Gets the source of the through reflection. It checks both a singularized and pluralized form for :belongs_to or :has_many.
161
+ # (The :tags association on Tagging below)
162
+ #
163
+ # class Post
164
+ # has_many :tags, :through => :taggings
165
+ # end
166
+ #
167
+ def source_reflection
168
+ return nil unless through_reflection
169
+ @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
170
+ end
171
+
172
+ def check_validity!
173
+ if options[:through]
174
+ if through_reflection.nil?
175
+ raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
176
+ end
177
+
178
+ if source_reflection.nil?
179
+ raise HasManyThroughSourceAssociationNotFoundError.new(self)
180
+ end
181
+
182
+ if options[:source_type] && source_reflection.options[:polymorphic].nil?
183
+ raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
184
+ end
185
+
186
+ if source_reflection.options[:polymorphic] && options[:source_type].nil?
187
+ raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
188
+ end
189
+
190
+ unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
191
+ raise HasManyThroughSourceAssociationMacroError.new(self)
192
+ end
193
+ end
112
194
  end
113
195
 
114
196
  private
115
- def name_to_class_name(name)
116
- if name !~ /::/
117
- class_name = active_record.send(
118
- :type_name_with_module,
119
- (options[:class_name] || active_record.class_name(active_record.table_name_prefix + name + active_record.table_name_suffix))
120
- )
197
+ def derive_class_name
198
+ # get the class_name of the belongs_to association of the through reflection
199
+ if through_reflection
200
+ options[:source_type] || source_reflection.class_name
201
+ else
202
+ class_name = name.to_s.camelize
203
+ class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
204
+ class_name
205
+ end
206
+ end
207
+
208
+ def derive_primary_key_name
209
+ if macro == :belongs_to
210
+ "#{name}_id"
211
+ elsif options[:as]
212
+ "#{options[:as]}_id"
213
+ else
214
+ active_record.name.foreign_key
121
215
  end
122
- return class_name || name
123
216
  end
124
217
  end
125
218
  end
126
- end
219
+ end
@@ -0,0 +1,58 @@
1
+ module ActiveRecord
2
+ # Allows programmers to programmatically define a schema in a portable
3
+ # DSL. This means you can define tables, indexes, etc. without using SQL
4
+ # directly, so your applications can more easily support multiple
5
+ # databases.
6
+ #
7
+ # Usage:
8
+ #
9
+ # ActiveRecord::Schema.define do
10
+ # create_table :authors do |t|
11
+ # t.string :name, :null => false
12
+ # end
13
+ #
14
+ # add_index :authors, :name, :unique
15
+ #
16
+ # create_table :posts do |t|
17
+ # t.integer :author_id, :null => false
18
+ # t.string :subject
19
+ # t.text :body
20
+ # t.boolean :private, :default => false
21
+ # end
22
+ #
23
+ # add_index :posts, :author_id
24
+ # end
25
+ #
26
+ # ActiveRecord::Schema is only supported by database adapters that also
27
+ # support migrations, the two features being very similar.
28
+ class Schema < Migration
29
+ private_class_method :new
30
+
31
+ # Eval the given block. All methods available to the current connection
32
+ # adapter are available within the block, so you can easily use the
33
+ # database definition DSL to build up your schema (#create_table,
34
+ # #add_index, etc.).
35
+ #
36
+ # The +info+ hash is optional, and if given is used to define metadata
37
+ # about the current schema (like the schema's version):
38
+ #
39
+ # ActiveRecord::Schema.define(:version => 15) do
40
+ # ...
41
+ # end
42
+ def self.define(info={}, &block)
43
+ instance_eval(&block)
44
+
45
+ unless info.empty?
46
+ initialize_schema_information
47
+ cols = columns('schema_info')
48
+
49
+ info = info.map do |k,v|
50
+ v = Base.connection.quote(v, cols.detect { |c| c.name == k.to_s })
51
+ "#{k} = #{v}"
52
+ end
53
+
54
+ Base.connection.update "UPDATE #{Migrator.schema_info_table_name} SET #{info.join(", ")}"
55
+ end
56
+ end
57
+ end
58
+ end