activerecord_authorails 1.0.0

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 (270) hide show
  1. data/CHANGELOG +3043 -0
  2. data/README +360 -0
  3. data/RUNNING_UNIT_TESTS +64 -0
  4. data/Rakefile +226 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/associations.rb +87 -0
  7. data/examples/shared_setup.rb +15 -0
  8. data/examples/validation.rb +85 -0
  9. data/install.rb +30 -0
  10. data/lib/active_record.rb +85 -0
  11. data/lib/active_record/acts/list.rb +244 -0
  12. data/lib/active_record/acts/nested_set.rb +211 -0
  13. data/lib/active_record/acts/tree.rb +89 -0
  14. data/lib/active_record/aggregations.rb +191 -0
  15. data/lib/active_record/associations.rb +1637 -0
  16. data/lib/active_record/associations/association_collection.rb +190 -0
  17. data/lib/active_record/associations/association_proxy.rb +158 -0
  18. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  19. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  20. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +169 -0
  21. data/lib/active_record/associations/has_many_association.rb +210 -0
  22. data/lib/active_record/associations/has_many_through_association.rb +247 -0
  23. data/lib/active_record/associations/has_one_association.rb +80 -0
  24. data/lib/active_record/attribute_methods.rb +75 -0
  25. data/lib/active_record/base.rb +2164 -0
  26. data/lib/active_record/calculations.rb +270 -0
  27. data/lib/active_record/callbacks.rb +367 -0
  28. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +279 -0
  29. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +58 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +343 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +161 -0
  34. data/lib/active_record/connection_adapters/db2_adapter.rb +228 -0
  35. data/lib/active_record/connection_adapters/firebird_adapter.rb +728 -0
  36. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  37. data/lib/active_record/connection_adapters/mysql_adapter.rb +414 -0
  38. data/lib/active_record/connection_adapters/openbase_adapter.rb +350 -0
  39. data/lib/active_record/connection_adapters/oracle_adapter.rb +689 -0
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +584 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +407 -0
  42. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +591 -0
  43. data/lib/active_record/connection_adapters/sybase_adapter.rb +662 -0
  44. data/lib/active_record/deprecated_associations.rb +104 -0
  45. data/lib/active_record/deprecated_finders.rb +44 -0
  46. data/lib/active_record/fixtures.rb +628 -0
  47. data/lib/active_record/locking/optimistic.rb +106 -0
  48. data/lib/active_record/locking/pessimistic.rb +77 -0
  49. data/lib/active_record/migration.rb +394 -0
  50. data/lib/active_record/observer.rb +178 -0
  51. data/lib/active_record/query_cache.rb +64 -0
  52. data/lib/active_record/reflection.rb +222 -0
  53. data/lib/active_record/schema.rb +58 -0
  54. data/lib/active_record/schema_dumper.rb +149 -0
  55. data/lib/active_record/timestamp.rb +51 -0
  56. data/lib/active_record/transactions.rb +136 -0
  57. data/lib/active_record/validations.rb +843 -0
  58. data/lib/active_record/vendor/db2.rb +362 -0
  59. data/lib/active_record/vendor/mysql.rb +1214 -0
  60. data/lib/active_record/vendor/simple.rb +693 -0
  61. data/lib/active_record/version.rb +9 -0
  62. data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
  63. data/lib/active_record/wrappings.rb +58 -0
  64. data/lib/active_record/xml_serialization.rb +308 -0
  65. data/test/aaa_create_tables_test.rb +59 -0
  66. data/test/abstract_unit.rb +77 -0
  67. data/test/active_schema_test_mysql.rb +31 -0
  68. data/test/adapter_test.rb +87 -0
  69. data/test/adapter_test_sqlserver.rb +81 -0
  70. data/test/aggregations_test.rb +95 -0
  71. data/test/all.sh +8 -0
  72. data/test/ar_schema_test.rb +33 -0
  73. data/test/association_inheritance_reload.rb +14 -0
  74. data/test/associations/callbacks_test.rb +126 -0
  75. data/test/associations/cascaded_eager_loading_test.rb +138 -0
  76. data/test/associations/eager_test.rb +393 -0
  77. data/test/associations/extension_test.rb +42 -0
  78. data/test/associations/join_model_test.rb +497 -0
  79. data/test/associations_test.rb +1809 -0
  80. data/test/attribute_methods_test.rb +49 -0
  81. data/test/base_test.rb +1586 -0
  82. data/test/binary_test.rb +37 -0
  83. data/test/calculations_test.rb +219 -0
  84. data/test/callbacks_test.rb +377 -0
  85. data/test/class_inheritable_attributes_test.rb +32 -0
  86. data/test/column_alias_test.rb +17 -0
  87. data/test/connection_test_firebird.rb +8 -0
  88. data/test/connections/native_db2/connection.rb +25 -0
  89. data/test/connections/native_firebird/connection.rb +26 -0
  90. data/test/connections/native_frontbase/connection.rb +27 -0
  91. data/test/connections/native_mysql/connection.rb +24 -0
  92. data/test/connections/native_openbase/connection.rb +21 -0
  93. data/test/connections/native_oracle/connection.rb +27 -0
  94. data/test/connections/native_postgresql/connection.rb +23 -0
  95. data/test/connections/native_sqlite/connection.rb +34 -0
  96. data/test/connections/native_sqlite3/connection.rb +34 -0
  97. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  98. data/test/connections/native_sqlserver/connection.rb +23 -0
  99. data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
  100. data/test/connections/native_sybase/connection.rb +23 -0
  101. data/test/copy_table_sqlite.rb +64 -0
  102. data/test/datatype_test_postgresql.rb +52 -0
  103. data/test/default_test_firebird.rb +16 -0
  104. data/test/defaults_test.rb +60 -0
  105. data/test/deprecated_associations_test.rb +396 -0
  106. data/test/deprecated_finder_test.rb +151 -0
  107. data/test/empty_date_time_test.rb +25 -0
  108. data/test/finder_test.rb +504 -0
  109. data/test/fixtures/accounts.yml +28 -0
  110. data/test/fixtures/author.rb +99 -0
  111. data/test/fixtures/author_favorites.yml +4 -0
  112. data/test/fixtures/authors.yml +7 -0
  113. data/test/fixtures/auto_id.rb +4 -0
  114. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  115. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  116. data/test/fixtures/bad_fixtures/blank_line +3 -0
  117. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  118. data/test/fixtures/bad_fixtures/missing_value +1 -0
  119. data/test/fixtures/binary.rb +2 -0
  120. data/test/fixtures/categories.yml +14 -0
  121. data/test/fixtures/categories/special_categories.yml +9 -0
  122. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  123. data/test/fixtures/categories_ordered.yml +7 -0
  124. data/test/fixtures/categories_posts.yml +23 -0
  125. data/test/fixtures/categorization.rb +5 -0
  126. data/test/fixtures/categorizations.yml +17 -0
  127. data/test/fixtures/category.rb +20 -0
  128. data/test/fixtures/column_name.rb +3 -0
  129. data/test/fixtures/comment.rb +23 -0
  130. data/test/fixtures/comments.yml +59 -0
  131. data/test/fixtures/companies.yml +55 -0
  132. data/test/fixtures/company.rb +107 -0
  133. data/test/fixtures/company_in_module.rb +59 -0
  134. data/test/fixtures/computer.rb +3 -0
  135. data/test/fixtures/computers.yml +4 -0
  136. data/test/fixtures/course.rb +3 -0
  137. data/test/fixtures/courses.yml +7 -0
  138. data/test/fixtures/customer.rb +55 -0
  139. data/test/fixtures/customers.yml +17 -0
  140. data/test/fixtures/db_definitions/db2.drop.sql +32 -0
  141. data/test/fixtures/db_definitions/db2.sql +231 -0
  142. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  143. data/test/fixtures/db_definitions/db22.sql +5 -0
  144. data/test/fixtures/db_definitions/firebird.drop.sql +63 -0
  145. data/test/fixtures/db_definitions/firebird.sql +304 -0
  146. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  148. data/test/fixtures/db_definitions/frontbase.drop.sql +32 -0
  149. data/test/fixtures/db_definitions/frontbase.sql +268 -0
  150. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  151. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  152. data/test/fixtures/db_definitions/mysql.drop.sql +32 -0
  153. data/test/fixtures/db_definitions/mysql.sql +234 -0
  154. data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/mysql2.sql +5 -0
  156. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  157. data/test/fixtures/db_definitions/openbase.sql +302 -0
  158. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  159. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  160. data/test/fixtures/db_definitions/oracle.drop.sql +65 -0
  161. data/test/fixtures/db_definitions/oracle.sql +325 -0
  162. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  163. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  164. data/test/fixtures/db_definitions/postgresql.drop.sql +37 -0
  165. data/test/fixtures/db_definitions/postgresql.sql +263 -0
  166. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  167. data/test/fixtures/db_definitions/postgresql2.sql +5 -0
  168. data/test/fixtures/db_definitions/schema.rb +60 -0
  169. data/test/fixtures/db_definitions/sqlite.drop.sql +32 -0
  170. data/test/fixtures/db_definitions/sqlite.sql +215 -0
  171. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  172. data/test/fixtures/db_definitions/sqlite2.sql +5 -0
  173. data/test/fixtures/db_definitions/sqlserver.drop.sql +34 -0
  174. data/test/fixtures/db_definitions/sqlserver.sql +243 -0
  175. data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
  176. data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
  177. data/test/fixtures/db_definitions/sybase.drop.sql +34 -0
  178. data/test/fixtures/db_definitions/sybase.sql +218 -0
  179. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  180. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  181. data/test/fixtures/default.rb +2 -0
  182. data/test/fixtures/developer.rb +52 -0
  183. data/test/fixtures/developers.yml +21 -0
  184. data/test/fixtures/developers_projects.yml +17 -0
  185. data/test/fixtures/developers_projects/david_action_controller +3 -0
  186. data/test/fixtures/developers_projects/david_active_record +3 -0
  187. data/test/fixtures/developers_projects/jamis_active_record +2 -0
  188. data/test/fixtures/edge.rb +5 -0
  189. data/test/fixtures/edges.yml +6 -0
  190. data/test/fixtures/entrant.rb +3 -0
  191. data/test/fixtures/entrants.yml +14 -0
  192. data/test/fixtures/fk_test_has_fk.yml +3 -0
  193. data/test/fixtures/fk_test_has_pk.yml +2 -0
  194. data/test/fixtures/flowers.jpg +0 -0
  195. data/test/fixtures/funny_jokes.yml +10 -0
  196. data/test/fixtures/joke.rb +6 -0
  197. data/test/fixtures/keyboard.rb +3 -0
  198. data/test/fixtures/legacy_thing.rb +3 -0
  199. data/test/fixtures/legacy_things.yml +3 -0
  200. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  201. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  202. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  203. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  204. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  205. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  206. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  207. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  208. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  209. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  210. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  211. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  212. data/test/fixtures/mixed_case_monkey.rb +3 -0
  213. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  214. data/test/fixtures/mixin.rb +63 -0
  215. data/test/fixtures/mixins.yml +127 -0
  216. data/test/fixtures/movie.rb +5 -0
  217. data/test/fixtures/movies.yml +7 -0
  218. data/test/fixtures/naked/csv/accounts.csv +1 -0
  219. data/test/fixtures/naked/yml/accounts.yml +1 -0
  220. data/test/fixtures/naked/yml/companies.yml +1 -0
  221. data/test/fixtures/naked/yml/courses.yml +1 -0
  222. data/test/fixtures/order.rb +4 -0
  223. data/test/fixtures/people.yml +3 -0
  224. data/test/fixtures/person.rb +4 -0
  225. data/test/fixtures/post.rb +58 -0
  226. data/test/fixtures/posts.yml +48 -0
  227. data/test/fixtures/project.rb +27 -0
  228. data/test/fixtures/projects.yml +7 -0
  229. data/test/fixtures/reader.rb +4 -0
  230. data/test/fixtures/readers.yml +4 -0
  231. data/test/fixtures/reply.rb +37 -0
  232. data/test/fixtures/subject.rb +4 -0
  233. data/test/fixtures/subscriber.rb +6 -0
  234. data/test/fixtures/subscribers/first +2 -0
  235. data/test/fixtures/subscribers/second +2 -0
  236. data/test/fixtures/tag.rb +7 -0
  237. data/test/fixtures/tagging.rb +6 -0
  238. data/test/fixtures/taggings.yml +18 -0
  239. data/test/fixtures/tags.yml +7 -0
  240. data/test/fixtures/task.rb +3 -0
  241. data/test/fixtures/tasks.yml +7 -0
  242. data/test/fixtures/topic.rb +25 -0
  243. data/test/fixtures/topics.yml +22 -0
  244. data/test/fixtures/vertex.rb +9 -0
  245. data/test/fixtures/vertices.yml +4 -0
  246. data/test/fixtures_test.rb +401 -0
  247. data/test/inheritance_test.rb +205 -0
  248. data/test/lifecycle_test.rb +137 -0
  249. data/test/locking_test.rb +190 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +768 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_nested_set_test.rb +196 -0
  254. data/test/mixin_test.rb +550 -0
  255. data/test/modules_test.rb +34 -0
  256. data/test/multiple_db_test.rb +60 -0
  257. data/test/pk_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +159 -0
  260. data/test/schema_authorization_test_postgresql.rb +75 -0
  261. data/test/schema_dumper_test.rb +96 -0
  262. data/test/schema_test_postgresql.rb +64 -0
  263. data/test/synonym_test_oracle.rb +17 -0
  264. data/test/table_name_test_sqlserver.rb +23 -0
  265. data/test/threaded_connections_test.rb +48 -0
  266. data/test/transactions_test.rb +230 -0
  267. data/test/unconnected_test.rb +32 -0
  268. data/test/validations_test.rb +1097 -0
  269. data/test/xml_serialization_test.rb +125 -0
  270. metadata +365 -0
@@ -0,0 +1,1637 @@
1
+ require 'active_record/associations/association_proxy'
2
+ require 'active_record/associations/association_collection'
3
+ require 'active_record/associations/belongs_to_association'
4
+ require 'active_record/associations/belongs_to_polymorphic_association'
5
+ require 'active_record/associations/has_one_association'
6
+ require 'active_record/associations/has_many_association'
7
+ require 'active_record/associations/has_many_through_association'
8
+ require 'active_record/associations/has_and_belongs_to_many_association'
9
+ require 'active_record/deprecated_associations'
10
+
11
+ module ActiveRecord
12
+ class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
13
+ def initialize(owner_class_name, reflection)
14
+ super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
15
+ end
16
+ end
17
+
18
+ class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc:
19
+ def initialize(owner_class_name, reflection, source_reflection)
20
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
21
+ end
22
+ end
23
+
24
+ class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
25
+ def initialize(owner_class_name, reflection, source_reflection)
26
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
27
+ end
28
+ end
29
+
30
+ class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
31
+ def initialize(reflection)
32
+ through_reflection = reflection.through_reflection
33
+ source_reflection_names = reflection.source_reflection_names
34
+ source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
35
+ super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :connector => 'or'}?")
36
+ end
37
+ end
38
+
39
+ class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc:
40
+ def initialize(reflection)
41
+ through_reflection = reflection.through_reflection
42
+ source_reflection = reflection.source_reflection
43
+ super("Invalid source reflection macro :#{source_reflection.macro}#{" :through" if source_reflection.options[:through]} for has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}. Use :source to specify the source reflection.")
44
+ end
45
+ end
46
+
47
+ class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
48
+ def initialize(owner, reflection)
49
+ super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
50
+ end
51
+ end
52
+
53
+ class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
54
+ def initialize(reflection)
55
+ super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
56
+ end
57
+ end
58
+
59
+ class ReadOnlyAssociation < ActiveRecordError #:nodoc:
60
+ def initialize(reflection)
61
+ super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
62
+ end
63
+ end
64
+
65
+ #added
66
+ class ReferentialIntegrityProtectionError < ActiveRecordError #:nodoc:
67
+ end
68
+
69
+ module Associations # :nodoc:
70
+ def self.included(base)
71
+ base.extend(ClassMethods)
72
+ end
73
+
74
+ # Clears out the association cache
75
+ def clear_association_cache #:nodoc:
76
+ self.class.reflect_on_all_associations.to_a.each do |assoc|
77
+ instance_variable_set "@#{assoc.name}", nil
78
+ end unless self.new_record?
79
+ end
80
+
81
+ # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
82
+ # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
83
+ # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own attr*
84
+ # methods. Example:
85
+ #
86
+ # class Project < ActiveRecord::Base
87
+ # belongs_to :portfolio
88
+ # has_one :project_manager
89
+ # has_many :milestones
90
+ # has_and_belongs_to_many :categories
91
+ # end
92
+ #
93
+ # The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
94
+ # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
95
+ # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
96
+ # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
97
+ # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find(:all, options),</tt>
98
+ # <tt>Project#milestones.build, Project#milestones.create</tt>
99
+ # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
100
+ # <tt>Project#categories.delete(category1)</tt>
101
+ #
102
+ # == Example
103
+ #
104
+ # link:files/examples/associations.png
105
+ #
106
+ # == Is it belongs_to or has_one?
107
+ #
108
+ # Both express a 1-1 relationship, the difference is mostly where to place the foreign key, which goes on the table for the class
109
+ # saying belongs_to. Example:
110
+ #
111
+ # class User < ActiveRecord::Base
112
+ # # I reference an account.
113
+ # belongs_to :account
114
+ # end
115
+ #
116
+ # class Account < ActiveRecord::Base
117
+ # # One user references me.
118
+ # has_one :user
119
+ # end
120
+ #
121
+ # The tables for these classes could look something like:
122
+ #
123
+ # CREATE TABLE users (
124
+ # id int(11) NOT NULL auto_increment,
125
+ # account_id int(11) default NULL,
126
+ # name varchar default NULL,
127
+ # PRIMARY KEY (id)
128
+ # )
129
+ #
130
+ # CREATE TABLE accounts (
131
+ # id int(11) NOT NULL auto_increment,
132
+ # name varchar default NULL,
133
+ # PRIMARY KEY (id)
134
+ # )
135
+ #
136
+ # == Unsaved objects and associations
137
+ #
138
+ # You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
139
+ # aware of, mostly involving the saving of associated objects.
140
+ #
141
+ # === One-to-one associations
142
+ #
143
+ # * Assigning an object to a has_one association automatically saves that object and the object being replaced (if there is one), in
144
+ # order to update their primary keys - except if the parent object is unsaved (new_record? == true).
145
+ # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
146
+ # is cancelled.
147
+ # * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
148
+ # * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
149
+ # not save the parent either.
150
+ #
151
+ # === Collections
152
+ #
153
+ # * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
154
+ # (the owner of the collection) is not yet stored in the database.
155
+ # * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
156
+ # * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
157
+ # * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
158
+ #
159
+ # === Association callbacks
160
+ #
161
+ # Similiar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get
162
+ # trigged when you add an object to or removing an object from a association collection. Example:
163
+ #
164
+ # class Project
165
+ # has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
166
+ #
167
+ # def evaluate_velocity(developer)
168
+ # ...
169
+ # end
170
+ # end
171
+ #
172
+ # It's possible to stack callbacks by passing them as an array. Example:
173
+ #
174
+ # class Project
175
+ # has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
176
+ # end
177
+ #
178
+ # Possible callbacks are: before_add, after_add, before_remove and after_remove.
179
+ #
180
+ # Should any of the before_add callbacks throw an exception, the object does not get added to the collection. Same with
181
+ # the before_remove callbacks, if an exception is thrown the object doesn't get removed.
182
+ #
183
+ # === Association extensions
184
+ #
185
+ # The proxy objects that controls the access to associations can be extended through anonymous modules. This is especially
186
+ # beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association.
187
+ # Example:
188
+ #
189
+ # class Account < ActiveRecord::Base
190
+ # has_many :people do
191
+ # def find_or_create_by_name(name)
192
+ # first_name, last_name = name.split(" ", 2)
193
+ # find_or_create_by_first_name_and_last_name(first_name, last_name)
194
+ # end
195
+ # end
196
+ # end
197
+ #
198
+ # person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
199
+ # person.first_name # => "David"
200
+ # person.last_name # => "Heinemeier Hansson"
201
+ #
202
+ # If you need to share the same extensions between many associations, you can use a named extension module. Example:
203
+ #
204
+ # module FindOrCreateByNameExtension
205
+ # def find_or_create_by_name(name)
206
+ # first_name, last_name = name.split(" ", 2)
207
+ # find_or_create_by_first_name_and_last_name(first_name, last_name)
208
+ # end
209
+ # end
210
+ #
211
+ # class Account < ActiveRecord::Base
212
+ # has_many :people, :extend => FindOrCreateByNameExtension
213
+ # end
214
+ #
215
+ # class Company < ActiveRecord::Base
216
+ # has_many :people, :extend => FindOrCreateByNameExtension
217
+ # end
218
+ #
219
+ # If you need to use multiple named extension modules, you can specify an array of modules with the :extend option.
220
+ # In the case of name conflicts between methods in the modules, methods in modules later in the array supercede
221
+ # those earlier in the array. Example:
222
+ #
223
+ # class Account < ActiveRecord::Base
224
+ # has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
225
+ # end
226
+ #
227
+ # Some extensions can only be made to work with knowledge of the association proxy's internals.
228
+ # Extensions can access relevant state using accessors on the association proxy:
229
+ #
230
+ # * +proxy_owner+ - Returns the object the association is part of.
231
+ # * +proxy_reflection+ - Returns the reflection object that describes the association.
232
+ # * +proxy_target+ - Returns the associated object for belongs_to and has_one, or the collection of associated objects for has_many and has_and_belongs_to_many.
233
+ #
234
+ # === Association Join Models
235
+ #
236
+ # Has Many associations can be configured with the :through option to use an explicit join model to retrieve the data. This
237
+ # operates similarly to a <tt>has_and_belongs_to_many</tt> association. The advantage is that you're able to add validations,
238
+ # callbacks, and extra attributes on the join model. Consider the following schema:
239
+ #
240
+ # class Author < ActiveRecord::Base
241
+ # has_many :authorships
242
+ # has_many :books, :through => :authorships
243
+ # end
244
+ #
245
+ # class Authorship < ActiveRecord::Base
246
+ # belongs_to :author
247
+ # belongs_to :book
248
+ # end
249
+ #
250
+ # @author = Author.find :first
251
+ # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to.
252
+ # @author.books # selects all books by using the Authorship join model
253
+ #
254
+ # You can also go through a has_many association on the join model:
255
+ #
256
+ # class Firm < ActiveRecord::Base
257
+ # has_many :clients
258
+ # has_many :invoices, :through => :clients
259
+ # end
260
+ #
261
+ # class Client < ActiveRecord::Base
262
+ # belongs_to :firm
263
+ # has_many :invoices
264
+ # end
265
+ #
266
+ # class Invoice < ActiveRecord::Base
267
+ # belongs_to :client
268
+ # end
269
+ #
270
+ # @firm = Firm.find :first
271
+ # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
272
+ # @firm.invoices # selects all invoices by going through the Client join model.
273
+ #
274
+ # === Polymorphic Associations
275
+ #
276
+ # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
277
+ # specify an interface that a has_many association must adhere to.
278
+ #
279
+ # class Asset < ActiveRecord::Base
280
+ # belongs_to :attachable, :polymorphic => true
281
+ # end
282
+ #
283
+ # class Post < ActiveRecord::Base
284
+ # has_many :assets, :as => :attachable # The <tt>:as</tt> option specifies the polymorphic interface to use.
285
+ # end
286
+ #
287
+ # @asset.attachable = @post
288
+ #
289
+ # This works by using a type column in addition to a foreign key to specify the associated record. In the Asset example, you'd need
290
+ # an attachable_id integer column and an attachable_type string column.
291
+ #
292
+ # Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order
293
+ # for the associations to work as expected, ensure that you store the base model for the STI models in the
294
+ # type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts
295
+ # and member posts that use the posts table for STI. So there will be an additional 'type' column in the posts table.
296
+ #
297
+ # class Asset < ActiveRecord::Base
298
+ # belongs_to :attachable, :polymorphic => true
299
+ #
300
+ # def attachable_type=(sType)
301
+ # super(sType.to_s.classify.constantize.base_class.to_s)
302
+ # end
303
+ # end
304
+ #
305
+ # class Post < ActiveRecord::Base
306
+ # # because we store "Post" in attachable_type now :dependent => :destroy will work
307
+ # has_many :assets, :as => :attachable, :dependent => :destroy
308
+ # end
309
+ #
310
+ # class GuestPost < ActiveRecord::Base
311
+ # end
312
+ #
313
+ # class MemberPost < ActiveRecord::Base
314
+ # end
315
+ #
316
+ # == Caching
317
+ #
318
+ # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
319
+ # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without
320
+ # worrying too much about performance at the first go. Example:
321
+ #
322
+ # project.milestones # fetches milestones from the database
323
+ # project.milestones.size # uses the milestone cache
324
+ # project.milestones.empty? # uses the milestone cache
325
+ # project.milestones(true).size # fetches milestones from the database
326
+ # project.milestones # uses the milestone cache
327
+ #
328
+ # == Eager loading of associations
329
+ #
330
+ # Eager loading is a way to find objects of a certain class and a number of named associations along with it in a single SQL call. This is
331
+ # one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each needs to display their author
332
+ # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 1. Example:
333
+ #
334
+ # class Post < ActiveRecord::Base
335
+ # belongs_to :author
336
+ # has_many :comments
337
+ # end
338
+ #
339
+ # Consider the following loop using the class above:
340
+ #
341
+ # for post in Post.find(:all)
342
+ # puts "Post: " + post.title
343
+ # puts "Written by: " + post.author.name
344
+ # puts "Last comment on: " + post.comments.first.created_on
345
+ # end
346
+ #
347
+ # To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author:
348
+ #
349
+ # for post in Post.find(:all, :include => :author)
350
+ #
351
+ # This references the name of the belongs_to association that also used the :author symbol, so the find will now weave in a join something
352
+ # like this: LEFT OUTER JOIN authors ON authors.id = posts.author_id. Doing so will cut down the number of queries from 201 to 101.
353
+ #
354
+ # We can improve upon the situation further by referencing both associations in the finder with:
355
+ #
356
+ # for post in Post.find(:all, :include => [ :author, :comments ])
357
+ #
358
+ # That'll add another join along the lines of: LEFT OUTER JOIN comments ON comments.post_id = posts.id. And we'll be down to 1 query.
359
+ # But that shouldn't fool you to think that you can pull out huge amounts of data with no performance penalty just because you've reduced
360
+ # the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
361
+ # catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
362
+ #
363
+ # Since the eager loading pulls from multiple tables, you'll have to disambiguate any column references in both conditions and orders. So
364
+ # :order => "posts.id DESC" will work while :order => "id DESC" will not. Because eager loading generates the SELECT statement too, the
365
+ # :select option is ignored.
366
+ #
367
+ # You can use eager loading on multiple associations from the same table, but you cannot use those associations in orders and conditions
368
+ # as there is currently not any way to disambiguate them. Eager loading will not pull additional attributes on join tables, so "rich
369
+ # associations" with has_and_belongs_to_many are not a good fit for eager loading.
370
+ #
371
+ # When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated
372
+ # before the actual model exists.
373
+ #
374
+ # == Table Aliasing
375
+ #
376
+ # ActiveRecord uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
377
+ # the standard table name is used. The second time, the table is aliased as #{reflection_name}_#{parent_table_name}. Indexes are appended
378
+ # for any more successive uses of the table name.
379
+ #
380
+ # Post.find :all, :include => :comments
381
+ # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ...
382
+ # Post.find :all, :include => :special_comments # STI
383
+ # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... AND comments.type = 'SpecialComment'
384
+ # Post.find :all, :include => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
385
+ # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... LEFT OUTER JOIN comments special_comments_posts
386
+ #
387
+ # Acts as tree example:
388
+ #
389
+ # TreeMixin.find :all, :include => :children
390
+ # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
391
+ # TreeMixin.find :all, :include => {:children => :parent} # using cascading eager includes
392
+ # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
393
+ # LEFT OUTER JOIN parents_mixins ...
394
+ # TreeMixin.find :all, :include => {:children => {:parent => :children}}
395
+ # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
396
+ # LEFT OUTER JOIN parents_mixins ...
397
+ # LEFT OUTER JOIN mixins childrens_mixins_2
398
+ #
399
+ # Has and Belongs to Many join tables use the same idea, but add a _join suffix:
400
+ #
401
+ # Post.find :all, :include => :categories
402
+ # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
403
+ # Post.find :all, :include => {:categories => :posts}
404
+ # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
405
+ # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
406
+ # Post.find :all, :include => {:categories => {:posts => :categories}}
407
+ # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
408
+ # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
409
+ # LEFT OUTER JOIN categories_posts categories_posts_join LEFT OUTER JOIN categories categories_posts
410
+ #
411
+ # If you wish to specify your own custom joins using a :joins option, those table names will take precedence over the eager associations..
412
+ #
413
+ # Post.find :all, :include => :comments, :joins => "inner join comments ..."
414
+ # # => SELECT ... FROM posts LEFT OUTER JOIN comments_posts ON ... INNER JOIN comments ...
415
+ # Post.find :all, :include => [:comments, :special_comments], :joins => "inner join comments ..."
416
+ # # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ...
417
+ # LEFT OUTER JOIN comments special_comments_posts ...
418
+ # INNER JOIN comments ...
419
+ #
420
+ # Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database.
421
+ #
422
+ # == Modules
423
+ #
424
+ # By default, associations will look for objects within the current module scope. Consider:
425
+ #
426
+ # module MyApplication
427
+ # module Business
428
+ # class Firm < ActiveRecord::Base
429
+ # has_many :clients
430
+ # end
431
+ #
432
+ # class Company < ActiveRecord::Base; end
433
+ # end
434
+ # end
435
+ #
436
+ # When Firm#clients is called, it'll in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
437
+ # with a class in another module scope this can be done by specifying the complete class name, such as:
438
+ #
439
+ # module MyApplication
440
+ # module Business
441
+ # class Firm < ActiveRecord::Base; end
442
+ # end
443
+ #
444
+ # module Billing
445
+ # class Account < ActiveRecord::Base
446
+ # belongs_to :firm, :class_name => "MyApplication::Business::Firm"
447
+ # end
448
+ # end
449
+ # end
450
+ #
451
+ # == Type safety with ActiveRecord::AssociationTypeMismatch
452
+ #
453
+ # If you attempt to assign an object to an association that doesn't match the inferred or specified <tt>:class_name</tt>, you'll
454
+ # get a ActiveRecord::AssociationTypeMismatch.
455
+ #
456
+ # == Options
457
+ #
458
+ # All of the association macros can be specialized through options which makes more complex cases than the simple and guessable ones
459
+ # possible.
460
+ module ClassMethods
461
+ # Adds the following methods for retrieval and query of collections of associated objects.
462
+ # +collection+ is replaced with the symbol passed as the first argument, so
463
+ # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
464
+ # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
465
+ # An empty array is returned if none are found.
466
+ # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
467
+ # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.
468
+ # This will also destroy the objects if they're declared as belongs_to and dependent on this model.
469
+ # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
470
+ # * <tt>collection_singular_ids</tt> - returns an array of the associated objects ids
471
+ # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
472
+ # * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they
473
+ # are <tt>:dependent</tt>, deletes them directly from the database if they are <tt>:dependent => :delete_all</tt>,
474
+ # and sets their foreign keys to NULL otherwise.
475
+ # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
476
+ # * <tt>collection.size</tt> - returns the number of associated objects.
477
+ # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find.
478
+ # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
479
+ # with +attributes+ and linked to this object through a foreign key but has not yet been saved. *Note:* This only works if an
480
+ # associated object already exists, not if it's nil!
481
+ # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
482
+ # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
483
+ # *Note:* This only works if an associated object already exists, not if it's nil!
484
+ #
485
+ # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
486
+ # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
487
+ # * <tt>Firm#clients<<</tt>
488
+ # * <tt>Firm#clients.delete</tt>
489
+ # * <tt>Firm#clients=</tt>
490
+ # * <tt>Firm#client_ids</tt>
491
+ # * <tt>Firm#client_ids=</tt>
492
+ # * <tt>Firm#clients.clear</tt>
493
+ # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
494
+ # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
495
+ # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
496
+ # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
497
+ # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
498
+ # The declaration can also include an options hash to specialize the behavior of the association.
499
+ #
500
+ # Options are:
501
+ # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
502
+ # from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but
503
+ # if the real class name is +SpecialProduct+, you'll have to specify it with this option.
504
+ # * <tt>:conditions</tt> - specify the conditions that the associated objects must meet in order to be included as a "WHERE"
505
+ # sql fragment, such as "price > 5 AND name LIKE 'B%'".
506
+ # * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment,
507
+ # such as "last_name, first_name DESC"
508
+ # * <tt>:group</tt> - specify the attribute by which the associated objects are returned as a "GROUP BY" sql fragment,
509
+ # such as "category"
510
+ # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
511
+ # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_many association will use "person_id"
512
+ # as the default foreign_key.
513
+ # * <tt>:dependent</tt> - if set to :destroy all the associated objects are destroyed
514
+ # alongside this object by calling their destroy method. If set to :delete_all all associated
515
+ # objects are deleted *without* calling their destroy method. If set to :nullify all associated
516
+ # objects' foreign keys are set to NULL *without* calling their save callbacks.
517
+ # NOTE: :dependent => true is deprecated and has been replaced with :dependent => :destroy.
518
+ # May not be set if :exclusively_dependent is also set.
519
+ # * <tt>:exclusively_dependent</tt> - Deprecated; equivalent to :dependent => :delete_all. If set to true all
520
+ # the associated object are deleted in one SQL statement without having their
521
+ # before_destroy callback run. This should only be used on associations that depend solely on this class and don't need to do any
522
+ # clean-up in before_destroy. The upside is that it's much faster, especially if there's a counter_cache involved.
523
+ # May not be set if :dependent is also set.
524
+ # * <tt>:finder_sql</tt> - specify a complete SQL statement to fetch the association. This is a good way to go for complex
525
+ # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
526
+ # * <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is
527
+ # specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
528
+ # * <tt>:extend</tt> - specify a named module for extending the proxy, see "Association extensions".
529
+ # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
530
+ # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
531
+ # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
532
+ # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
533
+ # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
534
+ # include the joined columns.
535
+ # * <tt>:as</tt>: Specifies a polymorphic interface (See #belongs_to).
536
+ # * <tt>:through</tt>: Specifies a Join Model to perform the query through. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
537
+ # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
538
+ # or <tt>has_many</tt> association.
539
+ # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
540
+ # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either +:subscribers+ or
541
+ # +:subscriber+ on +Subscription+, unless a +:source+ is given.
542
+ # * <tt>:source_type</tt>: Specifies type of the source association used by <tt>has_many :through</tt> queries where the source association
543
+ # is a polymorphic belongs_to.
544
+ # * <tt>:uniq</tt> - if set to true, duplicates will be omitted from the collection. Useful in conjunction with :through.
545
+ #
546
+ # Option examples:
547
+ # has_many :comments, :order => "posted_on"
548
+ # has_many :comments, :include => :author
549
+ # has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
550
+ # has_many :tracks, :order => "position", :dependent => :destroy
551
+ # has_many :comments, :dependent => :nullify
552
+ # has_many :tags, :as => :taggable
553
+ # has_many :subscribers, :through => :subscriptions, :source => :user
554
+ # has_many :subscribers, :class_name => "Person", :finder_sql =>
555
+ # 'SELECT DISTINCT people.* ' +
556
+ # 'FROM people p, post_subscriptions ps ' +
557
+ # 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
558
+ # 'ORDER BY p.first_name'
559
+ def has_many(association_id, options = {}, &extension)
560
+ reflection = create_has_many_reflection(association_id, options, &extension)
561
+
562
+ configure_dependency_for_has_many(reflection)
563
+
564
+ if options[:through]
565
+ collection_reader_method(reflection, HasManyThroughAssociation)
566
+ else
567
+ add_multiple_associated_save_callbacks(reflection.name)
568
+ add_association_callbacks(reflection.name, reflection.options)
569
+ collection_accessor_methods(reflection, HasManyAssociation)
570
+ end
571
+
572
+ add_deprecated_api_for_has_many(reflection.name)
573
+ end
574
+
575
+ # Adds the following methods for retrieval and query of a single associated object.
576
+ # +association+ is replaced with the symbol passed as the first argument, so
577
+ # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
578
+ # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
579
+ # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key,
580
+ # and saves the associate object.
581
+ # * <tt>association.nil?</tt> - returns true if there is no associated object.
582
+ # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
583
+ # with +attributes+ and linked to this object through a foreign key but has not yet been saved. Note: This ONLY works if
584
+ # an association already exists. It will NOT work if the association is nil.
585
+ # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
586
+ # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
587
+ #
588
+ # Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
589
+ # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
590
+ # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
591
+ # * <tt>Account#beneficiary.nil?</tt>
592
+ # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
593
+ # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
594
+ #
595
+ # The declaration can also include an options hash to specialize the behavior of the association.
596
+ #
597
+ # Options are:
598
+ # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
599
+ # from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but
600
+ # if the real class name is +Person+, you'll have to specify it with this option.
601
+ # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
602
+ # sql fragment, such as "rank = 5".
603
+ # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
604
+ # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
605
+ # * <tt>:dependent</tt> - if set to :destroy (or true) the associated object is destroyed when this object is. If set to
606
+ # :delete the associated object is deleted *without* calling its destroy method. If set to :nullify the associated
607
+ # object's foreign key is set to NULL. Also, association is assigned.
608
+ # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
609
+ # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id"
610
+ # as the default foreign_key.
611
+ # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
612
+ # * <tt>:as</tt>: Specifies a polymorphic interface (See #belongs_to).
613
+ #
614
+ # Option examples:
615
+ # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
616
+ # has_one :credit_card, :dependent => :nullify # updates the associated records foriegn key value to null rather than destroying it
617
+ # has_one :last_comment, :class_name => "Comment", :order => "posted_on"
618
+ # has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
619
+ # has_one :attachment, :as => :attachable
620
+ def has_one(association_id, options = {})
621
+ reflection = create_has_one_reflection(association_id, options)
622
+
623
+ module_eval do
624
+ after_save <<-EOF
625
+ association = instance_variable_get("@#{reflection.name}")
626
+ if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
627
+ association["#{reflection.primary_key_name}"] = id
628
+ association.save(true)
629
+ end
630
+ EOF
631
+ end
632
+
633
+ association_accessor_methods(reflection, HasOneAssociation)
634
+ association_constructor_method(:build, reflection, HasOneAssociation)
635
+ association_constructor_method(:create, reflection, HasOneAssociation)
636
+
637
+ configure_dependency_for_has_one(reflection)
638
+
639
+ # deprecated api
640
+ deprecated_has_association_method(reflection.name)
641
+ deprecated_association_comparison_method(reflection.name, reflection.class_name)
642
+ end
643
+
644
+ # Adds the following methods for retrieval and query for a single associated object that this object holds an id to.
645
+ # +association+ is replaced with the symbol passed as the first argument, so
646
+ # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
647
+ # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
648
+ # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key.
649
+ # * <tt>association.nil?</tt> - returns true if there is no associated object.
650
+ # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
651
+ # with +attributes+ and linked to this object through a foreign key but has not yet been saved.
652
+ # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
653
+ # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
654
+ #
655
+ # Example: A Post class declares <tt>belongs_to :author</tt>, which will add:
656
+ # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
657
+ # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
658
+ # * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
659
+ # * <tt>Post#author.nil?</tt>
660
+ # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
661
+ # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
662
+ # The declaration can also include an options hash to specialize the behavior of the association.
663
+ #
664
+ # Options are:
665
+ # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
666
+ # from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but
667
+ # if the real class name is +Person+, you'll have to specify it with this option.
668
+ # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
669
+ # sql fragment, such as "authorized = 1".
670
+ # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
671
+ # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
672
+ # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
673
+ # of the associated class in lower-case and "_id" suffixed. So a +Person+ class that makes a belongs_to association to a
674
+ # +Boss+ class will use "boss_id" as the default foreign_key.
675
+ # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through use of increment_counter
676
+ # and decrement_counter. The counter cache is incremented when an object of this class is created and decremented when it's
677
+ # destroyed. This requires that a column named "#{table_name}_count" (such as comments_count for a belonging Comment class)
678
+ # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by given that
679
+ # name instead of a true/false value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
680
+ # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
681
+ # * <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing true.
682
+ #
683
+ # Option examples:
684
+ # belongs_to :firm, :foreign_key => "client_of"
685
+ # belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
686
+ # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
687
+ # :conditions => 'discounts > #{payments_count}'
688
+ # belongs_to :attachable, :polymorphic => true
689
+ def belongs_to(association_id, options = {})
690
+ if options.include?(:class_name) && !options.include?(:foreign_key)
691
+ ::ActiveSupport::Deprecation.warn(
692
+ "The inferred foreign_key name will change in Rails 2.0 to use the association name instead of its class name when they differ. When using :class_name in belongs_to, use the :foreign_key option to explicitly set the key name to avoid problems in the transition.",
693
+ caller)
694
+ end
695
+
696
+ reflection = create_belongs_to_reflection(association_id, options)
697
+
698
+ if reflection.options[:polymorphic]
699
+ association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
700
+
701
+ module_eval do
702
+ before_save <<-EOF
703
+ association = instance_variable_get("@#{reflection.name}")
704
+ if association && association.target
705
+ if association.new_record?
706
+ association.save(true)
707
+ end
708
+
709
+ if association.updated?
710
+ self["#{reflection.primary_key_name}"] = association.id
711
+ self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
712
+ end
713
+ end
714
+ EOF
715
+ end
716
+ else
717
+ association_accessor_methods(reflection, BelongsToAssociation)
718
+ association_constructor_method(:build, reflection, BelongsToAssociation)
719
+ association_constructor_method(:create, reflection, BelongsToAssociation)
720
+
721
+ module_eval do
722
+ before_save <<-EOF
723
+ association = instance_variable_get("@#{reflection.name}")
724
+ if !association.nil?
725
+ if association.new_record?
726
+ association.save(true)
727
+ end
728
+
729
+ if association.updated?
730
+ self["#{reflection.primary_key_name}"] = association.id
731
+ end
732
+ end
733
+ EOF
734
+ end
735
+
736
+ # deprecated api
737
+ deprecated_has_association_method(reflection.name)
738
+ deprecated_association_comparison_method(reflection.name, reflection.class_name)
739
+ end
740
+
741
+ if options[:counter_cache]
742
+ cache_column = options[:counter_cache] == true ?
743
+ "#{self.to_s.underscore.pluralize}_count" :
744
+ options[:counter_cache]
745
+
746
+ module_eval(
747
+ "after_create '#{reflection.name}.class.increment_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
748
+ " unless #{reflection.name}.nil?'"
749
+ )
750
+
751
+ module_eval(
752
+ "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
753
+ " unless #{reflection.name}.nil?'"
754
+ )
755
+ end
756
+ end
757
+
758
+ # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
759
+ # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
760
+ # will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence
761
+ # is calculated using the <tt><</tt> operator for <tt>String</tt>. This means that if the strings are of different lengths,
762
+ # and the strings are equal when compared up to the shortest length, then the longer string is considered of higher
763
+ # lexical precedence than the shorter one. For example, one would expect the tables <tt>paper_boxes</tt> and <tt>papers</tt>
764
+ # to generate a join table name of <tt>papers_paper_boxes</tt> because of the length of the name <tt>paper_boxes</tt>,
765
+ # but it in fact generates a join table name of <tt>paper_boxes_papers</tt>. Be aware of this caveat, and use the
766
+ # custom <tt>join_table</tt> option if you need to.
767
+ #
768
+ # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
769
+ # has_and_belongs_to_many associations. Records returned from join tables with additional attributes will be marked as
770
+ # ReadOnly (because we can't save changes to the additional attrbutes). It's strongly recommended that you upgrade any
771
+ # associations with attributes to a real join model (see introduction).
772
+ #
773
+ # Adds the following methods for retrieval and query.
774
+ # +collection+ is replaced with the symbol passed as the first argument, so
775
+ # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
776
+ # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
777
+ # An empty array is returned if none is found.
778
+ # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table
779
+ # (collection.push and collection.concat are aliases to this method).
780
+ # * <tt>collection.push_with_attributes(object, join_attributes)</tt> - adds one to the collection by creating an association in the join table that
781
+ # also holds the attributes from <tt>join_attributes</tt> (should be a hash with the column names as keys). This can be used to have additional
782
+ # attributes on the join, which will be injected into the associated objects when they are retrieved through the collection.
783
+ # (collection.concat_with_attributes is an alias to this method). This method is now deprecated.
784
+ # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table.
785
+ # This does not destroy the objects.
786
+ # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
787
+ # * <tt>collection_singular_ids</tt> - returns an array of the associated objects ids
788
+ # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
789
+ # * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects.
790
+ # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
791
+ # * <tt>collection.size</tt> - returns the number of associated objects.
792
+ # * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that
793
+ # meets the condition that it has to be associated with this object.
794
+ # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
795
+ # with +attributes+ and linked to this object through the join table but has not yet been saved.
796
+ # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
797
+ # with +attributes+ and linked to this object through the join table and that has already been saved (if it passed the validation).
798
+ #
799
+ # Example: An Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
800
+ # * <tt>Developer#projects</tt>
801
+ # * <tt>Developer#projects<<</tt>
802
+ # * <tt>Developer#projects.delete</tt>
803
+ # * <tt>Developer#projects=</tt>
804
+ # * <tt>Developer#project_ids</tt>
805
+ # * <tt>Developer#project_ids=</tt>
806
+ # * <tt>Developer#projects.clear</tt>
807
+ # * <tt>Developer#projects.empty?</tt>
808
+ # * <tt>Developer#projects.size</tt>
809
+ # * <tt>Developer#projects.find(id)</tt>
810
+ # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("project_id" => id)</tt>)
811
+ # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>)
812
+ # The declaration may include an options hash to specialize the behavior of the association.
813
+ #
814
+ # Options are:
815
+ # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
816
+ # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
817
+ # +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option.
818
+ # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want.
819
+ # WARNING: If you're overwriting the table name of either class, the table_name method MUST be declared underneath any
820
+ # has_and_belongs_to_many declaration in order to work.
821
+ # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
822
+ # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_and_belongs_to_many association
823
+ # will use "person_id" as the default foreign_key.
824
+ # * <tt>:association_foreign_key</tt> - specify the association foreign key used for the association. By default this is
825
+ # guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is +Project+,
826
+ # the has_and_belongs_to_many association will use "project_id" as the default association foreign_key.
827
+ # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
828
+ # sql fragment, such as "authorized = 1".
829
+ # * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment, such as "last_name, first_name DESC"
830
+ # * <tt>:uniq</tt> - if set to true, duplicate associated objects will be ignored by accessors and query methods
831
+ # * <tt>:finder_sql</tt> - overwrite the default generated SQL used to fetch the association with a manual one
832
+ # * <tt>:delete_sql</tt> - overwrite the default generated SQL used to remove links between the associated
833
+ # classes with a manual one
834
+ # * <tt>:insert_sql</tt> - overwrite the default generated SQL used to add links between the associated classes
835
+ # with a manual one
836
+ # * <tt>:extend</tt> - anonymous module for extending the proxy, see "Association extensions".
837
+ # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
838
+ # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
839
+ # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
840
+ # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
841
+ # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
842
+ # include the joined columns.
843
+ #
844
+ # Option examples:
845
+ # has_and_belongs_to_many :projects
846
+ # has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
847
+ # has_and_belongs_to_many :nations, :class_name => "Country"
848
+ # has_and_belongs_to_many :categories, :join_table => "prods_cats"
849
+ # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
850
+ # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
851
+ def has_and_belongs_to_many(association_id, options = {}, &extension)
852
+ reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
853
+
854
+ add_multiple_associated_save_callbacks(reflection.name)
855
+ collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
856
+
857
+ # Don't use a before_destroy callback since users' before_destroy
858
+ # callbacks will be executed after the association is wiped out.
859
+ old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
860
+ class_eval <<-end_eval
861
+ alias_method :#{old_method}, :destroy_without_callbacks
862
+ def destroy_without_callbacks
863
+ #{reflection.name}.clear
864
+ #{old_method}
865
+ end
866
+ end_eval
867
+
868
+ add_association_callbacks(reflection.name, options)
869
+
870
+ # deprecated api
871
+ deprecated_collection_count_method(reflection.name)
872
+ deprecated_add_association_relation(reflection.name)
873
+ deprecated_remove_association_relation(reflection.name)
874
+ deprecated_has_collection_method(reflection.name)
875
+ end
876
+
877
+ private
878
+ def join_table_name(first_table_name, second_table_name)
879
+ if first_table_name < second_table_name
880
+ join_table = "#{first_table_name}_#{second_table_name}"
881
+ else
882
+ join_table = "#{second_table_name}_#{first_table_name}"
883
+ end
884
+
885
+ table_name_prefix + join_table + table_name_suffix
886
+ end
887
+
888
+ def association_accessor_methods(reflection, association_proxy_class)
889
+ define_method(reflection.name) do |*params|
890
+ force_reload = params.first unless params.empty?
891
+ association = instance_variable_get("@#{reflection.name}")
892
+
893
+ if association.nil? || force_reload
894
+ association = association_proxy_class.new(self, reflection)
895
+ retval = association.reload
896
+ if retval.nil? and association_proxy_class == BelongsToAssociation
897
+ instance_variable_set("@#{reflection.name}", nil)
898
+ return nil
899
+ end
900
+ instance_variable_set("@#{reflection.name}", association)
901
+ end
902
+
903
+ association.target.nil? ? nil : association
904
+ end
905
+
906
+ define_method("#{reflection.name}=") do |new_value|
907
+ association = instance_variable_get("@#{reflection.name}")
908
+ if association.nil?
909
+ association = association_proxy_class.new(self, reflection)
910
+ end
911
+
912
+ association.replace(new_value)
913
+
914
+ unless new_value.nil?
915
+ instance_variable_set("@#{reflection.name}", association)
916
+ else
917
+ instance_variable_set("@#{reflection.name}", nil)
918
+ return nil
919
+ end
920
+
921
+ association
922
+ end
923
+
924
+ define_method("set_#{reflection.name}_target") do |target|
925
+ return if target.nil? and association_proxy_class == BelongsToAssociation
926
+ association = association_proxy_class.new(self, reflection)
927
+ association.target = target
928
+ instance_variable_set("@#{reflection.name}", association)
929
+ end
930
+ end
931
+
932
+ def collection_reader_method(reflection, association_proxy_class)
933
+ define_method(reflection.name) do |*params|
934
+ force_reload = params.first unless params.empty?
935
+ association = instance_variable_get("@#{reflection.name}")
936
+
937
+ unless association.respond_to?(:loaded?)
938
+ association = association_proxy_class.new(self, reflection)
939
+ instance_variable_set("@#{reflection.name}", association)
940
+ end
941
+
942
+ association.reload if force_reload
943
+
944
+ association
945
+ end
946
+ end
947
+
948
+ def collection_accessor_methods(reflection, association_proxy_class)
949
+ collection_reader_method(reflection, association_proxy_class)
950
+
951
+ define_method("#{reflection.name}=") do |new_value|
952
+ # Loads proxy class instance (defined in collection_reader_method) if not already loaded
953
+ association = send(reflection.name)
954
+ association.replace(new_value)
955
+ association
956
+ end
957
+
958
+ define_method("#{reflection.name.to_s.singularize}_ids") do
959
+ send(reflection.name).map(&:id)
960
+ end
961
+
962
+ define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
963
+ ids = (new_value || []).reject { |nid| nid.blank? }
964
+ send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
965
+ end
966
+ end
967
+
968
+ def add_multiple_associated_save_callbacks(association_name)
969
+ method_name = "validate_associated_records_for_#{association_name}".to_sym
970
+ define_method(method_name) do
971
+ association = instance_variable_get("@#{association_name}")
972
+ if association.respond_to?(:loaded?)
973
+ if new_record?
974
+ association
975
+ else
976
+ association.select { |record| record.new_record? }
977
+ end.each do |record|
978
+ errors.add "#{association_name}" unless record.valid?
979
+ end
980
+ end
981
+ end
982
+
983
+ validate method_name
984
+ before_save("@new_record_before_save = new_record?; true")
985
+
986
+ after_callback = <<-end_eval
987
+ association = instance_variable_get("@#{association_name}")
988
+
989
+ if association.respond_to?(:loaded?)
990
+ if @new_record_before_save
991
+ records_to_save = association
992
+ else
993
+ records_to_save = association.select { |record| record.new_record? }
994
+ end
995
+ records_to_save.each { |record| association.send(:insert_record, record) }
996
+ association.send(:construct_sql) # reconstruct the SQL queries now that we know the owner's id
997
+ end
998
+ end_eval
999
+
1000
+ # Doesn't use after_save as that would save associations added in after_create/after_update twice
1001
+ after_create(after_callback)
1002
+ after_update(after_callback)
1003
+ end
1004
+
1005
+ def association_constructor_method(constructor, reflection, association_proxy_class)
1006
+ define_method("#{constructor}_#{reflection.name}") do |*params|
1007
+ attributees = params.first unless params.empty?
1008
+ replace_existing = params[1].nil? ? true : params[1]
1009
+ association = instance_variable_get("@#{reflection.name}")
1010
+
1011
+ if association.nil?
1012
+ association = association_proxy_class.new(self, reflection)
1013
+ instance_variable_set("@#{reflection.name}", association)
1014
+ end
1015
+
1016
+ if association_proxy_class == HasOneAssociation
1017
+ association.send(constructor, attributees, replace_existing)
1018
+ else
1019
+ association.send(constructor, attributees)
1020
+ end
1021
+ end
1022
+ end
1023
+
1024
+ def find_with_associations(options = {})
1025
+ catch :invalid_query do
1026
+ join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
1027
+ rows = select_all_rows(options, join_dependency)
1028
+ return join_dependency.instantiate(rows)
1029
+ end
1030
+ []
1031
+ end
1032
+
1033
+ def configure_dependency_for_has_many(reflection)
1034
+ if reflection.options[:dependent] == true
1035
+ ::ActiveSupport::Deprecation.warn("The :dependent => true option is deprecated and will be removed from Rails 2.0. Please use :dependent => :destroy instead. See http://www.rubyonrails.org/deprecation for details.", caller)
1036
+ end
1037
+
1038
+ if reflection.options[:dependent] && reflection.options[:exclusively_dependent]
1039
+ raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.'
1040
+ end
1041
+
1042
+ if reflection.options[:exclusively_dependent]
1043
+ reflection.options[:dependent] = :delete_all
1044
+ ::ActiveSupport::Deprecation.warn("The :exclusively_dependent option is deprecated and will be removed from Rails 2.0. Please use :dependent => :delete_all instead. See http://www.rubyonrails.org/deprecation for details.", caller)
1045
+ end
1046
+
1047
+ # See HasManyAssociation#delete_records. Dependent associations
1048
+ # delete children, otherwise foreign key is set to NULL.
1049
+
1050
+ # Add polymorphic type if the :as option is present
1051
+ dependent_conditions = %(#{reflection.primary_key_name} = \#{record.quoted_id})
1052
+ if reflection.options[:as]
1053
+ dependent_conditions += " AND #{reflection.options[:as]}_type = '#{base_class.name}'"
1054
+ end
1055
+
1056
+ case reflection.options[:dependent]
1057
+ when :destroy, true
1058
+ module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
1059
+ when :delete_all
1060
+ module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }"
1061
+ when :nullify
1062
+ module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{dependent_conditions})) }"
1063
+ #added
1064
+ when :deny
1065
+ module_eval "before_destroy 'raise ReferentialIntegrityProtectionError, \"Can\\'t destroy because there\\'s at least one #{reflection.class_name} in this #{self.class_name}\" if self.#{reflection.name}.find(:first)'"
1066
+ when nil, false
1067
+ # pass
1068
+ else #modified
1069
+ raise ArgumentError, 'The :dependent option expects either :destroy, :delete_all, :nullify, or :deny'
1070
+ end
1071
+ end
1072
+
1073
+ def configure_dependency_for_has_one(reflection)
1074
+ case reflection.options[:dependent]
1075
+ when :destroy, true
1076
+ module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
1077
+ when :delete
1078
+ module_eval "before_destroy '#{reflection.class_name}.delete(#{reflection.name}.id) unless #{reflection.name}.nil?'"
1079
+ when :nullify
1080
+ module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil) unless #{reflection.name}.nil?'"
1081
+ #added
1082
+ when :deny
1083
+ module_eval "before_destroy 'raise ReferentialIntegrityProtectionError, \"Can\\'t destroy because there\\'s one #{reflection.class_name} in this #{self.class_name}\" unless #{reflection.name}.nil?'"
1084
+ when nil, false
1085
+ # pass
1086
+ else
1087
+ raise ArgumentError, "The :dependent option expects either :destroy, :delete, :nullify, or :deny."
1088
+ end
1089
+ end
1090
+
1091
+
1092
+ def add_deprecated_api_for_has_many(association_name)
1093
+ deprecated_collection_count_method(association_name)
1094
+ deprecated_add_association_relation(association_name)
1095
+ deprecated_remove_association_relation(association_name)
1096
+ deprecated_has_collection_method(association_name)
1097
+ deprecated_find_in_collection_method(association_name)
1098
+ deprecated_find_all_in_collection_method(association_name)
1099
+ deprecated_collection_create_method(association_name)
1100
+ deprecated_collection_build_method(association_name)
1101
+ end
1102
+
1103
+ def create_has_many_reflection(association_id, options, &extension)
1104
+ options.assert_valid_keys(
1105
+ :class_name, :table_name, :foreign_key,
1106
+ :exclusively_dependent, :dependent,
1107
+ :select, :conditions, :include, :order, :group, :limit, :offset,
1108
+ :as, :through, :source, :source_type,
1109
+ :uniq,
1110
+ :finder_sql, :counter_sql,
1111
+ :before_add, :after_add, :before_remove, :after_remove,
1112
+ :extend
1113
+ )
1114
+
1115
+ options[:extend] = create_extension_module(association_id, extension) if block_given?
1116
+
1117
+ create_reflection(:has_many, association_id, options, self)
1118
+ end
1119
+
1120
+ def create_has_one_reflection(association_id, options)
1121
+ options.assert_valid_keys(
1122
+ :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as
1123
+ )
1124
+
1125
+ create_reflection(:has_one, association_id, options, self)
1126
+ end
1127
+
1128
+ def create_belongs_to_reflection(association_id, options)
1129
+ options.assert_valid_keys(
1130
+ :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,
1131
+ :counter_cache, :extend, :polymorphic
1132
+ )
1133
+
1134
+ reflection = create_reflection(:belongs_to, association_id, options, self)
1135
+
1136
+ if options[:polymorphic]
1137
+ reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
1138
+ end
1139
+
1140
+ reflection
1141
+ end
1142
+
1143
+ def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
1144
+ options.assert_valid_keys(
1145
+ :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
1146
+ :select, :conditions, :include, :order, :group, :limit, :offset,
1147
+ :uniq,
1148
+ :finder_sql, :delete_sql, :insert_sql,
1149
+ :before_add, :after_add, :before_remove, :after_remove,
1150
+ :extend
1151
+ )
1152
+
1153
+ options[:extend] = create_extension_module(association_id, extension) if block_given?
1154
+
1155
+ reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
1156
+
1157
+ reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
1158
+
1159
+ reflection
1160
+ end
1161
+
1162
+ def reflect_on_included_associations(associations)
1163
+ [ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) }
1164
+ end
1165
+
1166
+ def guard_against_unlimitable_reflections(reflections, options)
1167
+ if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections)
1168
+ raise(
1169
+ ConfigurationError,
1170
+ "You can not use offset and limit together with has_many or has_and_belongs_to_many associations"
1171
+ )
1172
+ end
1173
+ end
1174
+
1175
+ def select_all_rows(options, join_dependency)
1176
+ connection.select_all(
1177
+ construct_finder_sql_with_included_associations(options, join_dependency),
1178
+ "#{name} Load Including Associations"
1179
+ )
1180
+ end
1181
+
1182
+ def construct_finder_sql_with_included_associations(options, join_dependency)
1183
+ scope = scope(:find)
1184
+ sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || table_name} "
1185
+ sql << join_dependency.join_associations.collect{|join| join.association_join }.join
1186
+
1187
+ add_joins!(sql, options, scope)
1188
+ add_conditions!(sql, options[:conditions], scope)
1189
+ add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
1190
+
1191
+ sql << "GROUP BY #{options[:group]} " if options[:group]
1192
+
1193
+ add_order!(sql, options[:order], scope)
1194
+ add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
1195
+ add_lock!(sql, options, scope)
1196
+
1197
+ return sanitize_sql(sql)
1198
+ end
1199
+
1200
+ def add_limited_ids_condition!(sql, options, join_dependency)
1201
+ unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
1202
+ sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) "
1203
+ else
1204
+ throw :invalid_query
1205
+ end
1206
+ end
1207
+
1208
+ def select_limited_ids_list(options, join_dependency)
1209
+ connection.select_all(
1210
+ construct_finder_sql_for_association_limiting(options, join_dependency),
1211
+ "#{name} Load IDs For Limited Eager Loading"
1212
+ ).collect { |row| connection.quote(row[primary_key]) }.join(", ")
1213
+ end
1214
+
1215
+ def construct_finder_sql_for_association_limiting(options, join_dependency)
1216
+ scope = scope(:find)
1217
+ is_distinct = include_eager_conditions?(options) || include_eager_order?(options)
1218
+ sql = "SELECT "
1219
+ if is_distinct
1220
+ sql << connection.distinct("#{table_name}.#{primary_key}", options[:order])
1221
+ else
1222
+ sql << primary_key
1223
+ end
1224
+ sql << " FROM #{table_name} "
1225
+
1226
+ if is_distinct
1227
+ sql << join_dependency.join_associations.collect(&:association_join).join
1228
+ add_joins!(sql, options, scope)
1229
+ end
1230
+
1231
+ add_conditions!(sql, options[:conditions], scope)
1232
+ if options[:order]
1233
+ if is_distinct
1234
+ connection.add_order_by_for_association_limiting!(sql, options)
1235
+ else
1236
+ sql << "ORDER BY #{options[:order]}"
1237
+ end
1238
+ end
1239
+ add_limit!(sql, options, scope)
1240
+ return sanitize_sql(sql)
1241
+ end
1242
+
1243
+ # Checks if the conditions reference a table other than the current model table
1244
+ def include_eager_conditions?(options)
1245
+ # look in both sets of conditions
1246
+ conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
1247
+ case cond
1248
+ when nil then all
1249
+ when Array then all << cond.first
1250
+ else all << cond
1251
+ end
1252
+ end
1253
+ return false unless conditions.any?
1254
+ conditions.join(' ').scan(/([\.\w]+)\.\w+/).flatten.any? do |condition_table_name|
1255
+ condition_table_name != table_name
1256
+ end
1257
+ end
1258
+
1259
+ # Checks if the query order references a table other than the current model's table.
1260
+ def include_eager_order?(options)
1261
+ order = options[:order]
1262
+ return false unless order
1263
+ order.scan(/([\.\w]+)\.\w+/).flatten.any? do |order_table_name|
1264
+ order_table_name != table_name
1265
+ end
1266
+ end
1267
+
1268
+ def using_limitable_reflections?(reflections)
1269
+ reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
1270
+ end
1271
+
1272
+ def column_aliases(join_dependency)
1273
+ join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name|
1274
+ "#{join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
1275
+ end
1276
+
1277
+ def add_association_callbacks(association_name, options)
1278
+ callbacks = %w(before_add after_add before_remove after_remove)
1279
+ callbacks.each do |callback_name|
1280
+ full_callback_name = "#{callback_name}_for_#{association_name}"
1281
+ defined_callbacks = options[callback_name.to_sym]
1282
+ if options.has_key?(callback_name.to_sym)
1283
+ class_inheritable_reader full_callback_name.to_sym
1284
+ write_inheritable_array(full_callback_name.to_sym, [defined_callbacks].flatten)
1285
+ end
1286
+ end
1287
+ end
1288
+
1289
+ def condition_word(sql)
1290
+ sql =~ /where/i ? " AND " : "WHERE "
1291
+ end
1292
+
1293
+ def create_extension_module(association_id, extension)
1294
+ extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension"
1295
+
1296
+ silence_warnings do
1297
+ Object.const_set(extension_module_name, Module.new(&extension))
1298
+ end
1299
+
1300
+ extension_module_name.constantize
1301
+ end
1302
+
1303
+ class JoinDependency # :nodoc:
1304
+ attr_reader :joins, :reflections, :table_aliases
1305
+
1306
+ def initialize(base, associations, joins)
1307
+ @joins = [JoinBase.new(base, joins)]
1308
+ @associations = associations
1309
+ @reflections = []
1310
+ @base_records_hash = {}
1311
+ @base_records_in_order = []
1312
+ @table_aliases = Hash.new { |aliases, table| aliases[table] = 0 }
1313
+ @table_aliases[base.table_name] = 1
1314
+ build(associations)
1315
+ end
1316
+
1317
+ def join_associations
1318
+ @joins[1..-1].to_a
1319
+ end
1320
+
1321
+ def join_base
1322
+ @joins[0]
1323
+ end
1324
+
1325
+ def instantiate(rows)
1326
+ rows.each_with_index do |row, i|
1327
+ primary_id = join_base.record_id(row)
1328
+ unless @base_records_hash[primary_id]
1329
+ @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row))
1330
+ end
1331
+ construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
1332
+ end
1333
+ return @base_records_in_order
1334
+ end
1335
+
1336
+ def aliased_table_names_for(table_name)
1337
+ joins.select{|join| join.table_name == table_name }.collect{|join| join.aliased_table_name}
1338
+ end
1339
+
1340
+ protected
1341
+ def build(associations, parent = nil)
1342
+ parent ||= @joins.last
1343
+ case associations
1344
+ when Symbol, String
1345
+ reflection = parent.reflections[associations.to_s.intern] or
1346
+ raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
1347
+ @reflections << reflection
1348
+ @joins << JoinAssociation.new(reflection, self, parent)
1349
+ when Array
1350
+ associations.each do |association|
1351
+ build(association, parent)
1352
+ end
1353
+ when Hash
1354
+ associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
1355
+ build(name, parent)
1356
+ build(associations[name])
1357
+ end
1358
+ else
1359
+ raise ConfigurationError, associations.inspect
1360
+ end
1361
+ end
1362
+
1363
+ def construct(parent, associations, joins, row)
1364
+ case associations
1365
+ when Symbol, String
1366
+ while (join = joins.shift).reflection.name.to_s != associations.to_s
1367
+ raise ConfigurationError, "Not Enough Associations" if joins.empty?
1368
+ end
1369
+ construct_association(parent, join, row)
1370
+ when Array
1371
+ associations.each do |association|
1372
+ construct(parent, association, joins, row)
1373
+ end
1374
+ when Hash
1375
+ associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
1376
+ association = construct_association(parent, joins.shift, row)
1377
+ construct(association, associations[name], joins, row) if association
1378
+ end
1379
+ else
1380
+ raise ConfigurationError, associations.inspect
1381
+ end
1382
+ end
1383
+
1384
+ def construct_association(record, join, row)
1385
+ case join.reflection.macro
1386
+ when :has_many, :has_and_belongs_to_many
1387
+ collection = record.send(join.reflection.name)
1388
+ collection.loaded
1389
+
1390
+ return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
1391
+ association = join.instantiate(row)
1392
+ collection.target.push(association) unless collection.target.include?(association)
1393
+ when :has_one
1394
+ return if record.id.to_s != join.parent.record_id(row).to_s
1395
+ association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
1396
+ record.send("set_#{join.reflection.name}_target", association)
1397
+ when :belongs_to
1398
+ return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
1399
+ association = join.instantiate(row)
1400
+ record.send("set_#{join.reflection.name}_target", association)
1401
+ else
1402
+ raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
1403
+ end
1404
+ return association
1405
+ end
1406
+
1407
+ class JoinBase # :nodoc:
1408
+ attr_reader :active_record, :table_joins
1409
+ delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record
1410
+
1411
+ def initialize(active_record, joins = nil)
1412
+ @active_record = active_record
1413
+ @cached_record = {}
1414
+ @table_joins = joins
1415
+ end
1416
+
1417
+ def aliased_prefix
1418
+ "t0"
1419
+ end
1420
+
1421
+ def aliased_primary_key
1422
+ "#{ aliased_prefix }_r0"
1423
+ end
1424
+
1425
+ def aliased_table_name
1426
+ active_record.table_name
1427
+ end
1428
+
1429
+ def column_names_with_alias
1430
+ unless @column_names_with_alias
1431
+ @column_names_with_alias = []
1432
+ ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
1433
+ @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
1434
+ end
1435
+ end
1436
+ return @column_names_with_alias
1437
+ end
1438
+
1439
+ def extract_record(row)
1440
+ column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record}
1441
+ end
1442
+
1443
+ def record_id(row)
1444
+ row[aliased_primary_key]
1445
+ end
1446
+
1447
+ def instantiate(row)
1448
+ @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row))
1449
+ end
1450
+ end
1451
+
1452
+ class JoinAssociation < JoinBase # :nodoc:
1453
+ attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
1454
+ delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
1455
+
1456
+ def initialize(reflection, join_dependency, parent = nil)
1457
+ reflection.check_validity!
1458
+ if reflection.options[:polymorphic]
1459
+ raise EagerLoadPolymorphicError.new(reflection)
1460
+ end
1461
+
1462
+ super(reflection.klass)
1463
+ @parent = parent
1464
+ @reflection = reflection
1465
+ @aliased_prefix = "t#{ join_dependency.joins.size }"
1466
+ @aliased_table_name = table_name #.tr('.', '_') # start with the table name, sub out any .'s
1467
+ @parent_table_name = parent.active_record.table_name
1468
+
1469
+ if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{aliased_table_name.downcase}\son}
1470
+ join_dependency.table_aliases[aliased_table_name] += 1
1471
+ end
1472
+
1473
+ unless join_dependency.table_aliases[aliased_table_name].zero?
1474
+ # if the table name has been used, then use an alias
1475
+ @aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}"
1476
+ table_index = join_dependency.table_aliases[aliased_table_name]
1477
+ join_dependency.table_aliases[aliased_table_name] += 1
1478
+ @aliased_table_name = @aliased_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
1479
+ else
1480
+ join_dependency.table_aliases[aliased_table_name] += 1
1481
+ end
1482
+
1483
+ if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through])
1484
+ @aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name
1485
+ unless join_dependency.table_aliases[aliased_join_table_name].zero?
1486
+ @aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join"
1487
+ table_index = join_dependency.table_aliases[aliased_join_table_name]
1488
+ join_dependency.table_aliases[aliased_join_table_name] += 1
1489
+ @aliased_join_table_name = @aliased_join_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
1490
+ else
1491
+ join_dependency.table_aliases[aliased_join_table_name] += 1
1492
+ end
1493
+ end
1494
+ end
1495
+
1496
+ def association_join
1497
+ join = case reflection.macro
1498
+ when :has_and_belongs_to_many
1499
+ " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1500
+ table_alias_for(options[:join_table], aliased_join_table_name),
1501
+ aliased_join_table_name,
1502
+ options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
1503
+ parent.aliased_table_name, reflection.active_record.primary_key] +
1504
+ " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1505
+ table_name_and_alias, aliased_table_name, klass.primary_key,
1506
+ aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key
1507
+ ]
1508
+ when :has_many, :has_one
1509
+ case
1510
+ when reflection.macro == :has_many && reflection.options[:through]
1511
+ through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
1512
+
1513
+ jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
1514
+ first_key = second_key = as_extra = nil
1515
+
1516
+ if through_reflection.options[:as] # has_many :through against a polymorphic join
1517
+ jt_foreign_key = through_reflection.options[:as].to_s + '_id'
1518
+ jt_as_extra = " AND %s.%s = %s" % [
1519
+ aliased_join_table_name, reflection.active_record.connection.quote_column_name(through_reflection.options[:as].to_s + '_type'),
1520
+ klass.quote_value(parent.active_record.base_class.name)
1521
+ ]
1522
+ else
1523
+ jt_foreign_key = through_reflection.primary_key_name
1524
+ end
1525
+
1526
+ case source_reflection.macro
1527
+ when :has_many
1528
+ if source_reflection.options[:as]
1529
+ first_key = "#{source_reflection.options[:as]}_id"
1530
+ second_key = options[:foreign_key] || primary_key
1531
+ as_extra = " AND %s.%s = %s" % [
1532
+ aliased_table_name, reflection.active_record.connection.quote_column_name("#{source_reflection.options[:as]}_type"),
1533
+ klass.quote_value(source_reflection.active_record.base_class.name)
1534
+ ]
1535
+ else
1536
+ first_key = through_reflection.klass.base_class.to_s.classify.foreign_key
1537
+ second_key = options[:foreign_key] || primary_key
1538
+ end
1539
+
1540
+ unless through_reflection.klass.descends_from_active_record?
1541
+ jt_sti_extra = " AND %s.%s = %s" % [
1542
+ aliased_join_table_name,
1543
+ reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
1544
+ through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
1545
+ end
1546
+ when :belongs_to
1547
+ first_key = primary_key
1548
+ if reflection.options[:source_type]
1549
+ second_key = source_reflection.association_foreign_key
1550
+ jt_source_extra = " AND %s.%s = %s" % [
1551
+ aliased_join_table_name, reflection.active_record.connection.quote_column_name(reflection.source_reflection.options[:foreign_type]),
1552
+ klass.quote_value(reflection.options[:source_type])
1553
+ ]
1554
+ else
1555
+ second_key = source_reflection.options[:foreign_key] || klass.to_s.classify.foreign_key
1556
+ end
1557
+ end
1558
+
1559
+ " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s%s%s) " % [
1560
+ table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
1561
+ parent.aliased_table_name, reflection.active_record.connection.quote_column_name(parent.primary_key),
1562
+ aliased_join_table_name, reflection.active_record.connection.quote_column_name(jt_foreign_key),
1563
+ jt_as_extra, jt_source_extra, jt_sti_extra
1564
+ ] +
1565
+ " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) " % [
1566
+ table_name_and_alias,
1567
+ aliased_table_name, reflection.active_record.connection.quote_column_name(first_key),
1568
+ aliased_join_table_name, reflection.active_record.connection.quote_column_name(second_key),
1569
+ as_extra
1570
+ ]
1571
+
1572
+ when reflection.macro == :has_many && reflection.options[:as]
1573
+ " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s" % [
1574
+ table_name_and_alias,
1575
+ aliased_table_name, "#{reflection.options[:as]}_id",
1576
+ parent.aliased_table_name, parent.primary_key,
1577
+ aliased_table_name, "#{reflection.options[:as]}_type",
1578
+ klass.quote_value(parent.active_record.base_class.name)
1579
+ ]
1580
+ when reflection.macro == :has_one && reflection.options[:as]
1581
+ " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
1582
+ table_name_and_alias,
1583
+ aliased_table_name, "#{reflection.options[:as]}_id",
1584
+ parent.aliased_table_name, parent.primary_key,
1585
+ aliased_table_name, "#{reflection.options[:as]}_type",
1586
+ klass.quote_value(reflection.active_record.base_class.name)
1587
+ ]
1588
+ else
1589
+ foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
1590
+ " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1591
+ table_name_and_alias,
1592
+ aliased_table_name, foreign_key,
1593
+ parent.aliased_table_name, parent.primary_key
1594
+ ]
1595
+ end
1596
+ when :belongs_to
1597
+ " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1598
+ table_name_and_alias, aliased_table_name, reflection.klass.primary_key,
1599
+ parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key
1600
+ ]
1601
+ else
1602
+ ""
1603
+ end || ''
1604
+ join << %(AND %s.%s = %s ) % [
1605
+ aliased_table_name,
1606
+ reflection.active_record.connection.quote_column_name(klass.inheritance_column),
1607
+ klass.quote_value(klass.name.demodulize)] unless klass.descends_from_active_record?
1608
+
1609
+ [through_reflection, reflection].each do |ref|
1610
+ join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions]
1611
+ end
1612
+
1613
+ join
1614
+ end
1615
+
1616
+ protected
1617
+
1618
+ def pluralize(table_name)
1619
+ ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
1620
+ end
1621
+
1622
+ def table_alias_for(table_name, table_alias)
1623
+ "#{table_name} #{table_alias if table_name != table_alias}".strip
1624
+ end
1625
+
1626
+ def table_name_and_alias
1627
+ table_alias_for table_name, @aliased_table_name
1628
+ end
1629
+
1630
+ def interpolate_sql(sql)
1631
+ instance_eval("%@#{sql.gsub('@', '\@')}@")
1632
+ end
1633
+ end
1634
+ end
1635
+ end
1636
+ end
1637
+ end