activerecord 1.14.4 → 1.15.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +400 -1
- data/README +2 -2
- data/RUNNING_UNIT_TESTS +21 -3
- data/Rakefile +55 -10
- data/lib/active_record.rb +10 -4
- data/lib/active_record/acts/list.rb +15 -4
- data/lib/active_record/acts/nested_set.rb +11 -12
- data/lib/active_record/acts/tree.rb +13 -14
- data/lib/active_record/aggregations.rb +46 -22
- data/lib/active_record/associations.rb +213 -162
- data/lib/active_record/associations/association_collection.rb +45 -15
- data/lib/active_record/associations/association_proxy.rb +32 -13
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
- data/lib/active_record/associations/has_many_association.rb +37 -17
- data/lib/active_record/associations/has_many_through_association.rb +120 -30
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/attribute_methods.rb +75 -0
- data/lib/active_record/base.rb +282 -203
- data/lib/active_record/calculations.rb +95 -54
- data/lib/active_record/callbacks.rb +13 -24
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
- data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
- data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
- data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
- data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
- data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
- data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
- data/lib/active_record/deprecated_associations.rb +24 -10
- data/lib/active_record/deprecated_finders.rb +4 -1
- data/lib/active_record/fixtures.rb +37 -23
- data/lib/active_record/locking/optimistic.rb +106 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/migration.rb +8 -5
- data/lib/active_record/observer.rb +73 -34
- data/lib/active_record/reflection.rb +21 -7
- data/lib/active_record/schema_dumper.rb +33 -5
- data/lib/active_record/timestamp.rb +23 -34
- data/lib/active_record/transactions.rb +37 -30
- data/lib/active_record/validations.rb +46 -30
- data/lib/active_record/vendor/mysql.rb +20 -5
- data/lib/active_record/version.rb +2 -2
- data/lib/active_record/wrappings.rb +1 -2
- data/lib/active_record/xml_serialization.rb +308 -0
- data/test/aaa_create_tables_test.rb +5 -1
- data/test/abstract_unit.rb +18 -8
- data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
- data/test/adapter_test.rb +9 -7
- data/test/adapter_test_sqlserver.rb +81 -0
- data/test/aggregations_test.rb +29 -0
- data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
- data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
- data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
- data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
- data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
- data/test/associations_test.rb +339 -45
- data/test/attribute_methods_test.rb +49 -0
- data/test/base_test.rb +321 -67
- data/test/calculations_test.rb +48 -10
- data/test/callbacks_test.rb +13 -0
- data/test/connection_test_firebird.rb +8 -0
- data/test/connections/native_db2/connection.rb +18 -17
- data/test/connections/native_firebird/connection.rb +19 -17
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +18 -15
- data/test/connections/native_openbase/connection.rb +14 -15
- data/test/connections/native_oracle/connection.rb +16 -12
- data/test/connections/native_postgresql/connection.rb +16 -17
- data/test/connections/native_sqlite/connection.rb +3 -6
- data/test/connections/native_sqlite3/connection.rb +3 -6
- data/test/connections/native_sqlserver/connection.rb +16 -17
- data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
- data/test/connections/native_sybase/connection.rb +16 -17
- data/test/datatype_test_postgresql.rb +52 -0
- data/test/defaults_test.rb +52 -10
- data/test/deprecated_associations_test.rb +151 -107
- data/test/deprecated_finder_test.rb +83 -66
- data/test/empty_date_time_test.rb +25 -0
- data/test/finder_test.rb +118 -11
- data/test/fixtures/accounts.yml +6 -1
- data/test/fixtures/author.rb +27 -4
- data/test/fixtures/categorizations.yml +8 -2
- data/test/fixtures/category.rb +1 -2
- data/test/fixtures/comments.yml +0 -6
- data/test/fixtures/companies.yml +6 -1
- data/test/fixtures/company.rb +23 -1
- data/test/fixtures/company_in_module.rb +8 -10
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/customers.yml +9 -0
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +9 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
- data/test/fixtures/db_definitions/firebird.sql +13 -1
- data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
- data/test/fixtures/db_definitions/frontbase.sql +262 -0
- data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase2.sql +4 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
- data/test/fixtures/db_definitions/mysql.sql +23 -14
- data/test/fixtures/db_definitions/openbase.sql +13 -1
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +29 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
- data/test/fixtures/db_definitions/postgresql.sql +13 -3
- data/test/fixtures/db_definitions/schema.rb +29 -1
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +12 -3
- data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlserver.sql +35 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
- data/test/fixtures/db_definitions/sybase.sql +13 -4
- data/test/fixtures/developer.rb +12 -0
- data/test/fixtures/edge.rb +5 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/funny_jokes.yml +3 -7
- data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
- data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
- data/test/fixtures/mixin.rb +15 -0
- data/test/fixtures/mixins.yml +38 -0
- data/test/fixtures/post.rb +3 -2
- data/test/fixtures/project.rb +3 -1
- data/test/fixtures/topic.rb +6 -1
- data/test/fixtures/topics.yml +4 -4
- data/test/fixtures/vertex.rb +9 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures_test.rb +45 -0
- data/test/inheritance_test.rb +67 -6
- data/test/lifecycle_test.rb +40 -19
- data/test/locking_test.rb +170 -26
- data/test/method_scoping_test.rb +2 -2
- data/test/migration_test.rb +387 -110
- data/test/migration_test_firebird.rb +124 -0
- data/test/mixin_nested_set_test.rb +14 -2
- data/test/mixin_test.rb +56 -18
- data/test/modules_test.rb +8 -2
- data/test/multiple_db_test.rb +2 -2
- data/test/pk_test.rb +1 -0
- data/test/reflection_test.rb +8 -2
- data/test/schema_authorization_test_postgresql.rb +75 -0
- data/test/schema_dumper_test.rb +40 -4
- data/test/table_name_test_sqlserver.rb +23 -0
- data/test/threaded_connections_test.rb +19 -16
- data/test/transactions_test.rb +86 -72
- data/test/validations_test.rb +126 -56
- data/test/xml_serialization_test.rb +125 -0
- metadata +45 -11
- data/lib/active_record/locking.rb +0 -79
@@ -10,65 +10,54 @@ require 'active_record/deprecated_associations'
|
|
10
10
|
|
11
11
|
module ActiveRecord
|
12
12
|
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
|
13
|
-
def initialize(reflection)
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
def message
|
18
|
-
"Could not find the association #{@reflection.options[:through].inspect} in model #{@reflection.klass}"
|
13
|
+
def initialize(owner_class_name, reflection)
|
14
|
+
super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
|
19
15
|
end
|
20
16
|
end
|
21
17
|
|
22
18
|
class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc:
|
23
19
|
def initialize(owner_class_name, reflection, source_reflection)
|
24
|
-
|
25
|
-
@reflection = reflection
|
26
|
-
@source_reflection = source_reflection
|
27
|
-
end
|
28
|
-
|
29
|
-
def message
|
30
|
-
"Cannot have a has_many :through association '#{@owner_class_name}##{@reflection.name}' on the polymorphic object '#{@source_reflection.class_name}##{@source_reflection.name}'."
|
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}'.")
|
31
21
|
end
|
32
22
|
end
|
33
23
|
|
34
24
|
class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
|
35
25
|
def initialize(reflection)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
def message
|
43
|
-
"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'}?"
|
26
|
+
through_reflection = reflection.through_reflection
|
27
|
+
source_reflection_names = reflection.source_reflection_names
|
28
|
+
source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
|
29
|
+
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'}?")
|
44
30
|
end
|
45
31
|
end
|
46
32
|
|
47
|
-
class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc
|
33
|
+
class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc:
|
48
34
|
def initialize(reflection)
|
49
|
-
|
50
|
-
|
51
|
-
|
35
|
+
through_reflection = reflection.through_reflection
|
36
|
+
source_reflection = reflection.source_reflection
|
37
|
+
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.")
|
52
38
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
39
|
+
end
|
40
|
+
|
41
|
+
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
|
42
|
+
def initialize(owner, reflection)
|
43
|
+
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.")
|
56
44
|
end
|
57
45
|
end
|
58
46
|
|
59
47
|
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
|
60
48
|
def initialize(reflection)
|
61
|
-
|
49
|
+
super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
|
62
50
|
end
|
63
|
-
|
64
|
-
|
65
|
-
|
51
|
+
end
|
52
|
+
|
53
|
+
class ReadOnlyAssociation < ActiveRecordError #:nodoc:
|
54
|
+
def initialize(reflection)
|
55
|
+
super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
|
66
56
|
end
|
67
57
|
end
|
68
58
|
|
69
59
|
module Associations # :nodoc:
|
70
|
-
def self.
|
71
|
-
super
|
60
|
+
def self.included(base)
|
72
61
|
base.extend(ClassMethods)
|
73
62
|
end
|
74
63
|
|
@@ -95,7 +84,7 @@ module ActiveRecord
|
|
95
84
|
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
|
96
85
|
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
|
97
86
|
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
|
98
|
-
# <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.
|
87
|
+
# <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find(:all, options),</tt>
|
99
88
|
# <tt>Project#milestones.build, Project#milestones.create</tt>
|
100
89
|
# * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
|
101
90
|
# <tt>Project#categories.delete(category1)</tt>
|
@@ -109,25 +98,27 @@ module ActiveRecord
|
|
109
98
|
# Both express a 1-1 relationship, the difference is mostly where to place the foreign key, which goes on the table for the class
|
110
99
|
# saying belongs_to. Example:
|
111
100
|
#
|
112
|
-
# class
|
113
|
-
#
|
101
|
+
# class User < ActiveRecord::Base
|
102
|
+
# # I reference an account.
|
103
|
+
# belongs_to :account
|
114
104
|
# end
|
115
105
|
#
|
116
|
-
# class
|
117
|
-
#
|
106
|
+
# class Account < ActiveRecord::Base
|
107
|
+
# # One user references me.
|
108
|
+
# has_one :user
|
118
109
|
# end
|
119
110
|
#
|
120
111
|
# The tables for these classes could look something like:
|
121
112
|
#
|
122
|
-
# CREATE TABLE
|
113
|
+
# CREATE TABLE users (
|
123
114
|
# id int(11) NOT NULL auto_increment,
|
124
|
-
#
|
115
|
+
# account_id int(11) default NULL,
|
116
|
+
# name varchar default NULL,
|
125
117
|
# PRIMARY KEY (id)
|
126
118
|
# )
|
127
119
|
#
|
128
|
-
# CREATE TABLE
|
120
|
+
# CREATE TABLE accounts (
|
129
121
|
# id int(11) NOT NULL auto_increment,
|
130
|
-
# post_id int(11) default NULL,
|
131
122
|
# name varchar default NULL,
|
132
123
|
# PRIMARY KEY (id)
|
133
124
|
# )
|
@@ -215,6 +206,21 @@ module ActiveRecord
|
|
215
206
|
# has_many :people, :extend => FindOrCreateByNameExtension
|
216
207
|
# end
|
217
208
|
#
|
209
|
+
# If you need to use multiple named extension modules, you can specify an array of modules with the :extend option.
|
210
|
+
# In the case of name conflicts between methods in the modules, methods in modules later in the array supercede
|
211
|
+
# those earlier in the array. Example:
|
212
|
+
#
|
213
|
+
# class Account < ActiveRecord::Base
|
214
|
+
# has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
|
215
|
+
# end
|
216
|
+
#
|
217
|
+
# Some extensions can only be made to work with knowledge of the association proxy's internals.
|
218
|
+
# Extensions can access relevant state using accessors on the association proxy:
|
219
|
+
#
|
220
|
+
# * +proxy_owner+ - Returns the object the association is part of.
|
221
|
+
# * +proxy_reflection+ - Returns the reflection object that describes the association.
|
222
|
+
# * +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.
|
223
|
+
#
|
218
224
|
# === Association Join Models
|
219
225
|
#
|
220
226
|
# Has Many associations can be configured with the :through option to use an explicit join model to retrieve the data. This
|
@@ -273,6 +279,30 @@ module ActiveRecord
|
|
273
279
|
# 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
|
274
280
|
# an attachable_id integer column and an attachable_type string column.
|
275
281
|
#
|
282
|
+
# Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order
|
283
|
+
# for the associations to work as expected, ensure that you store the base model for the STI models in the
|
284
|
+
# type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts
|
285
|
+
# and member posts that use the posts table for STI. So there will be an additional 'type' column in the posts table.
|
286
|
+
#
|
287
|
+
# class Asset < ActiveRecord::Base
|
288
|
+
# belongs_to :attachable, :polymorphic => true
|
289
|
+
#
|
290
|
+
# def attachable_type=(sType)
|
291
|
+
# super(sType.to_s.classify.constantize.base_class.to_s)
|
292
|
+
# end
|
293
|
+
# end
|
294
|
+
#
|
295
|
+
# class Post < ActiveRecord::Base
|
296
|
+
# # because we store "Post" in attachable_type now :dependent => :destroy will work
|
297
|
+
# has_many :assets, :as => :attachable, :dependent => :destroy
|
298
|
+
# end
|
299
|
+
#
|
300
|
+
# class GuestPost < ActiveRecord::Base
|
301
|
+
# end
|
302
|
+
#
|
303
|
+
# class MemberPost < ActiveRecord::Base
|
304
|
+
# end
|
305
|
+
#
|
276
306
|
# == Caching
|
277
307
|
#
|
278
308
|
# All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
|
@@ -319,22 +349,17 @@ module ActiveRecord
|
|
319
349
|
# 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
|
320
350
|
# 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
|
321
351
|
# 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.
|
352
|
+
#
|
353
|
+
# Since the eager loading pulls from multiple tables, you'll have to disambiguate any column references in both conditions and orders. So
|
354
|
+
# :order => "posts.id DESC" will work while :order => "id DESC" will not. Because eager loading generates the SELECT statement too, the
|
355
|
+
# :select option is ignored.
|
322
356
|
#
|
323
|
-
#
|
324
|
-
#
|
325
|
-
#
|
326
|
-
#
|
327
|
-
#
|
328
|
-
#
|
329
|
-
#
|
330
|
-
# Post.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%'", :limit => 2)
|
331
|
-
#
|
332
|
-
# Also have in mind that since the eager loading is pulling from multiple tables, you'll have to disambiguate any column references
|
333
|
-
# in both conditions and orders. So :order => "posts.id DESC" will work while :order => "id DESC" will not. This may require that
|
334
|
-
# you alter the :order and :conditions on the association definitions themselves.
|
335
|
-
#
|
336
|
-
# It's currently not possible to use eager loading on multiple associations from the same table. Eager loading will not pull
|
337
|
-
# additional attributes on join tables, so "rich associations" with has_and_belongs_to_many is not a good fit for eager loading.
|
357
|
+
# You can use eager loading on multiple associations from the same table, but you cannot use those associations in orders and conditions
|
358
|
+
# as there is currently not any way to disambiguate them. Eager loading will not pull additional attributes on join tables, so "rich
|
359
|
+
# associations" with has_and_belongs_to_many are not a good fit for eager loading.
|
360
|
+
#
|
361
|
+
# When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated
|
362
|
+
# before the actual model exists.
|
338
363
|
#
|
339
364
|
# == Table Aliasing
|
340
365
|
#
|
@@ -432,6 +457,7 @@ module ActiveRecord
|
|
432
457
|
# * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.
|
433
458
|
# This will also destroy the objects if they're declared as belongs_to and dependent on this model.
|
434
459
|
# * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
|
460
|
+
# * <tt>collection_singular_ids</tt> - returns an array of the associated objects ids
|
435
461
|
# * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
|
436
462
|
# * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they
|
437
463
|
# are <tt>:dependent</tt>, deletes them directly from the database if they are <tt>:dependent => :delete_all</tt>,
|
@@ -451,6 +477,7 @@ module ActiveRecord
|
|
451
477
|
# * <tt>Firm#clients<<</tt>
|
452
478
|
# * <tt>Firm#clients.delete</tt>
|
453
479
|
# * <tt>Firm#clients=</tt>
|
480
|
+
# * <tt>Firm#client_ids</tt>
|
454
481
|
# * <tt>Firm#client_ids=</tt>
|
455
482
|
# * <tt>Firm#clients.clear</tt>
|
456
483
|
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
|
@@ -502,6 +529,7 @@ module ActiveRecord
|
|
502
529
|
# * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
|
503
530
|
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either +:subscribers+ or
|
504
531
|
# +:subscriber+ on +Subscription+, unless a +:source+ is given.
|
532
|
+
# * <tt>:uniq</tt> - if set to true, duplicates will be omitted from the collection. Useful in conjunction with :through.
|
505
533
|
#
|
506
534
|
# Option examples:
|
507
535
|
# has_many :comments, :order => "posted_on"
|
@@ -562,25 +590,28 @@ module ActiveRecord
|
|
562
590
|
# sql fragment, such as "rank = 5".
|
563
591
|
# * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
|
564
592
|
# an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
|
565
|
-
# * <tt>:dependent</tt> - if set to :destroy (or true)
|
566
|
-
#
|
593
|
+
# * <tt>:dependent</tt> - if set to :destroy (or true) the associated object is destroyed when this object is. If set to
|
594
|
+
# :delete the associated object is deleted *without* calling its destroy method. If set to :nullify the associated
|
595
|
+
# object's foreign key is set to NULL. Also, association is assigned.
|
567
596
|
# * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
|
568
597
|
# of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id"
|
569
598
|
# as the default foreign_key.
|
570
599
|
# * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
|
571
|
-
#
|
600
|
+
# * <tt>:as</tt>: Specifies a polymorphic interface (See #belongs_to).
|
601
|
+
#
|
572
602
|
# Option examples:
|
573
603
|
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
|
574
604
|
# has_one :credit_card, :dependent => :nullify # updates the associated records foriegn key value to null rather than destroying it
|
575
605
|
# has_one :last_comment, :class_name => "Comment", :order => "posted_on"
|
576
606
|
# has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
|
607
|
+
# has_one :attachment, :as => :attachable
|
577
608
|
def has_one(association_id, options = {})
|
578
609
|
reflection = create_has_one_reflection(association_id, options)
|
579
610
|
|
580
611
|
module_eval do
|
581
612
|
after_save <<-EOF
|
582
613
|
association = instance_variable_get("@#{reflection.name}")
|
583
|
-
|
614
|
+
if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
|
584
615
|
association["#{reflection.primary_key_name}"] = id
|
585
616
|
association.save(true)
|
586
617
|
end
|
@@ -644,6 +675,12 @@ module ActiveRecord
|
|
644
675
|
# :conditions => 'discounts > #{payments_count}'
|
645
676
|
# belongs_to :attachable, :polymorphic => true
|
646
677
|
def belongs_to(association_id, options = {})
|
678
|
+
if options.include?(:class_name) && !options.include?(:foreign_key)
|
679
|
+
::ActiveSupport::Deprecation.warn(
|
680
|
+
"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.",
|
681
|
+
caller)
|
682
|
+
end
|
683
|
+
|
647
684
|
reflection = create_belongs_to_reflection(association_id, options)
|
648
685
|
|
649
686
|
if reflection.options[:polymorphic]
|
@@ -652,7 +689,7 @@ module ActiveRecord
|
|
652
689
|
module_eval do
|
653
690
|
before_save <<-EOF
|
654
691
|
association = instance_variable_get("@#{reflection.name}")
|
655
|
-
if
|
692
|
+
if association && association.target
|
656
693
|
if association.new_record?
|
657
694
|
association.save(true)
|
658
695
|
end
|
@@ -708,7 +745,13 @@ module ActiveRecord
|
|
708
745
|
|
709
746
|
# Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
|
710
747
|
# an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
|
711
|
-
# will give the default join table name of "developers_projects" because "D" outranks "P".
|
748
|
+
# will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence
|
749
|
+
# is calculated using the <tt><</tt> operator for <tt>String</tt>. This means that if the strings are of different lengths,
|
750
|
+
# and the strings are equal when compared up to the shortest length, then the longer string is considered of higher
|
751
|
+
# lexical precedence than the shorter one. For example, one would expect the tables <tt>paper_boxes</tt> and <tt>papers</tt>
|
752
|
+
# to generate a join table name of <tt>papers_paper_boxes</tt> because of the length of the name <tt>paper_boxes</tt>,
|
753
|
+
# but it in fact generates a join table name of <tt>paper_boxes_papers</tt>. Be aware of this caveat, and use the
|
754
|
+
# custom <tt>join_table</tt> option if you need to.
|
712
755
|
#
|
713
756
|
# Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
|
714
757
|
# has_and_belongs_to_many associations. Records returned from join tables with additional attributes will be marked as
|
@@ -729,24 +772,31 @@ module ActiveRecord
|
|
729
772
|
# * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table.
|
730
773
|
# This does not destroy the objects.
|
731
774
|
# * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
|
775
|
+
# * <tt>collection_singular_ids</tt> - returns an array of the associated objects ids
|
732
776
|
# * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
|
733
777
|
# * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects.
|
734
778
|
# * <tt>collection.empty?</tt> - returns true if there are no associated objects.
|
735
779
|
# * <tt>collection.size</tt> - returns the number of associated objects.
|
736
780
|
# * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that
|
737
781
|
# meets the condition that it has to be associated with this object.
|
782
|
+
# * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
|
783
|
+
# with +attributes+ and linked to this object through the join table but has not yet been saved.
|
784
|
+
# * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
|
785
|
+
# with +attributes+ and linked to this object through the join table and that has already been saved (if it passed the validation).
|
738
786
|
#
|
739
787
|
# Example: An Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
|
740
788
|
# * <tt>Developer#projects</tt>
|
741
789
|
# * <tt>Developer#projects<<</tt>
|
742
|
-
# * <tt>Developer#projects.push_with_attributes</tt>
|
743
790
|
# * <tt>Developer#projects.delete</tt>
|
744
791
|
# * <tt>Developer#projects=</tt>
|
792
|
+
# * <tt>Developer#project_ids</tt>
|
745
793
|
# * <tt>Developer#project_ids=</tt>
|
746
794
|
# * <tt>Developer#projects.clear</tt>
|
747
795
|
# * <tt>Developer#projects.empty?</tt>
|
748
796
|
# * <tt>Developer#projects.size</tt>
|
749
797
|
# * <tt>Developer#projects.find(id)</tt>
|
798
|
+
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("project_id" => id)</tt>)
|
799
|
+
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>)
|
750
800
|
# The declaration may include an options hash to specialize the behavior of the association.
|
751
801
|
#
|
752
802
|
# Options are:
|
@@ -831,14 +881,14 @@ module ActiveRecord
|
|
831
881
|
if association.nil? || force_reload
|
832
882
|
association = association_proxy_class.new(self, reflection)
|
833
883
|
retval = association.reload
|
834
|
-
|
835
|
-
instance_variable_set("@#{reflection.name}", association)
|
836
|
-
else
|
884
|
+
if retval.nil? and association_proxy_class == BelongsToAssociation
|
837
885
|
instance_variable_set("@#{reflection.name}", nil)
|
838
886
|
return nil
|
839
887
|
end
|
888
|
+
instance_variable_set("@#{reflection.name}", association)
|
840
889
|
end
|
841
|
-
|
890
|
+
|
891
|
+
association.target.nil? ? nil : association
|
842
892
|
end
|
843
893
|
|
844
894
|
define_method("#{reflection.name}=") do |new_value|
|
@@ -860,7 +910,7 @@ module ActiveRecord
|
|
860
910
|
end
|
861
911
|
|
862
912
|
define_method("set_#{reflection.name}_target") do |target|
|
863
|
-
return if target.nil?
|
913
|
+
return if target.nil? and association_proxy_class == BelongsToAssociation
|
864
914
|
association = association_proxy_class.new(self, reflection)
|
865
915
|
association.target = target
|
866
916
|
instance_variable_set("@#{reflection.name}", association)
|
@@ -887,22 +937,20 @@ module ActiveRecord
|
|
887
937
|
collection_reader_method(reflection, association_proxy_class)
|
888
938
|
|
889
939
|
define_method("#{reflection.name}=") do |new_value|
|
890
|
-
|
891
|
-
|
892
|
-
association = association_proxy_class.new(self, reflection)
|
893
|
-
instance_variable_set("@#{reflection.name}", association)
|
894
|
-
end
|
940
|
+
# Loads proxy class instance (defined in collection_reader_method) if not already loaded
|
941
|
+
association = send(reflection.name)
|
895
942
|
association.replace(new_value)
|
896
943
|
association
|
897
944
|
end
|
898
945
|
|
899
|
-
define_method("#{reflection.name.to_s.singularize}_ids
|
900
|
-
send(
|
946
|
+
define_method("#{reflection.name.to_s.singularize}_ids") do
|
947
|
+
send(reflection.name).map(&:id)
|
901
948
|
end
|
902
|
-
end
|
903
949
|
|
904
|
-
|
905
|
-
|
950
|
+
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
|
951
|
+
ids = (new_value || []).reject { |nid| nid.blank? }
|
952
|
+
send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
|
953
|
+
end
|
906
954
|
end
|
907
955
|
|
908
956
|
def add_multiple_associated_save_callbacks(association_name)
|
@@ -961,14 +1009,6 @@ module ActiveRecord
|
|
961
1009
|
end
|
962
1010
|
end
|
963
1011
|
|
964
|
-
def count_with_associations(options = {})
|
965
|
-
catch :invalid_query do
|
966
|
-
join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
|
967
|
-
return count_by_sql(construct_counter_sql_with_included_associations(options, join_dependency))
|
968
|
-
end
|
969
|
-
0
|
970
|
-
end
|
971
|
-
|
972
1012
|
def find_with_associations(options = {})
|
973
1013
|
catch :invalid_query do
|
974
1014
|
join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
|
@@ -979,13 +1019,17 @@ module ActiveRecord
|
|
979
1019
|
end
|
980
1020
|
|
981
1021
|
def configure_dependency_for_has_many(reflection)
|
1022
|
+
if reflection.options[:dependent] == true
|
1023
|
+
::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)
|
1024
|
+
end
|
1025
|
+
|
982
1026
|
if reflection.options[:dependent] && reflection.options[:exclusively_dependent]
|
983
1027
|
raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.'
|
984
1028
|
end
|
985
1029
|
|
986
1030
|
if reflection.options[:exclusively_dependent]
|
987
1031
|
reflection.options[:dependent] = :delete_all
|
988
|
-
|
1032
|
+
::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)
|
989
1033
|
end
|
990
1034
|
|
991
1035
|
# See HasManyAssociation#delete_records. Dependent associations
|
@@ -998,7 +1042,7 @@ module ActiveRecord
|
|
998
1042
|
end
|
999
1043
|
|
1000
1044
|
case reflection.options[:dependent]
|
1001
|
-
when :destroy, true
|
1045
|
+
when :destroy, true
|
1002
1046
|
module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
|
1003
1047
|
when :delete_all
|
1004
1048
|
module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }"
|
@@ -1007,20 +1051,22 @@ module ActiveRecord
|
|
1007
1051
|
when nil, false
|
1008
1052
|
# pass
|
1009
1053
|
else
|
1010
|
-
raise ArgumentError, 'The :dependent option expects either :destroy, :delete_all, or :nullify'
|
1054
|
+
raise ArgumentError, 'The :dependent option expects either :destroy, :delete_all, or :nullify'
|
1011
1055
|
end
|
1012
1056
|
end
|
1013
|
-
|
1057
|
+
|
1014
1058
|
def configure_dependency_for_has_one(reflection)
|
1015
1059
|
case reflection.options[:dependent]
|
1016
1060
|
when :destroy, true
|
1017
1061
|
module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
|
1062
|
+
when :delete
|
1063
|
+
module_eval "before_destroy '#{reflection.class_name}.delete(#{reflection.name}.id) unless #{reflection.name}.nil?'"
|
1018
1064
|
when :nullify
|
1019
|
-
module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'"
|
1065
|
+
module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil) unless #{reflection.name}.nil?'"
|
1020
1066
|
when nil, false
|
1021
1067
|
# pass
|
1022
1068
|
else
|
1023
|
-
raise ArgumentError, "The :dependent option expects either :destroy or :nullify."
|
1069
|
+
raise ArgumentError, "The :dependent option expects either :destroy, :delete or :nullify."
|
1024
1070
|
end
|
1025
1071
|
end
|
1026
1072
|
|
@@ -1042,6 +1088,7 @@ module ActiveRecord
|
|
1042
1088
|
:exclusively_dependent, :dependent,
|
1043
1089
|
:select, :conditions, :include, :order, :group, :limit, :offset,
|
1044
1090
|
:as, :through, :source,
|
1091
|
+
:uniq,
|
1045
1092
|
:finder_sql, :counter_sql,
|
1046
1093
|
:before_add, :after_add, :before_remove, :after_remove,
|
1047
1094
|
:extend
|
@@ -1079,7 +1126,8 @@ module ActiveRecord
|
|
1079
1126
|
options.assert_valid_keys(
|
1080
1127
|
:class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
|
1081
1128
|
:select, :conditions, :include, :order, :group, :limit, :offset,
|
1082
|
-
:
|
1129
|
+
:uniq,
|
1130
|
+
:finder_sql, :delete_sql, :insert_sql,
|
1083
1131
|
:before_add, :after_add, :before_remove, :after_remove,
|
1084
1132
|
:extend
|
1085
1133
|
)
|
@@ -1112,31 +1160,6 @@ module ActiveRecord
|
|
1112
1160
|
"#{name} Load Including Associations"
|
1113
1161
|
)
|
1114
1162
|
end
|
1115
|
-
|
1116
|
-
def construct_counter_sql_with_included_associations(options, join_dependency)
|
1117
|
-
scope = scope(:find)
|
1118
|
-
sql = "SELECT COUNT(DISTINCT #{table_name}.#{primary_key})"
|
1119
|
-
|
1120
|
-
# A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
|
1121
|
-
if !Base.connection.supports_count_distinct?
|
1122
|
-
sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{table_name}.#{primary_key}"
|
1123
|
-
end
|
1124
|
-
|
1125
|
-
sql << " FROM #{table_name} "
|
1126
|
-
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
1127
|
-
|
1128
|
-
add_joins!(sql, options, scope)
|
1129
|
-
add_conditions!(sql, options[:conditions], scope)
|
1130
|
-
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
1131
|
-
|
1132
|
-
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
1133
|
-
|
1134
|
-
if !Base.connection.supports_count_distinct?
|
1135
|
-
sql << ")"
|
1136
|
-
end
|
1137
|
-
|
1138
|
-
return sanitize_sql(sql)
|
1139
|
-
end
|
1140
1163
|
|
1141
1164
|
def construct_finder_sql_with_included_associations(options, join_dependency)
|
1142
1165
|
scope = scope(:find)
|
@@ -1145,11 +1168,13 @@ module ActiveRecord
|
|
1145
1168
|
|
1146
1169
|
add_joins!(sql, options, scope)
|
1147
1170
|
add_conditions!(sql, options[:conditions], scope)
|
1148
|
-
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]
|
1171
|
+
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
1149
1172
|
|
1150
|
-
sql << "
|
1173
|
+
sql << "GROUP BY #{options[:group]} " if options[:group]
|
1151
1174
|
|
1175
|
+
add_order!(sql, options[:order], scope)
|
1152
1176
|
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
1177
|
+
add_lock!(sql, options, scope)
|
1153
1178
|
|
1154
1179
|
return sanitize_sql(sql)
|
1155
1180
|
end
|
@@ -1168,26 +1193,35 @@ module ActiveRecord
|
|
1168
1193
|
"#{name} Load IDs For Limited Eager Loading"
|
1169
1194
|
).collect { |row| connection.quote(row[primary_key]) }.join(", ")
|
1170
1195
|
end
|
1171
|
-
|
1196
|
+
|
1172
1197
|
def construct_finder_sql_for_association_limiting(options, join_dependency)
|
1173
|
-
scope
|
1198
|
+
scope = scope(:find)
|
1199
|
+
is_distinct = include_eager_conditions?(options) || include_eager_order?(options)
|
1174
1200
|
sql = "SELECT "
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1201
|
+
if is_distinct
|
1202
|
+
sql << connection.distinct("#{table_name}.#{primary_key}", options[:order])
|
1203
|
+
else
|
1204
|
+
sql << primary_key
|
1205
|
+
end
|
1178
1206
|
sql << " FROM #{table_name} "
|
1179
|
-
|
1180
|
-
if
|
1181
|
-
sql << join_dependency.join_associations.collect
|
1207
|
+
|
1208
|
+
if is_distinct
|
1209
|
+
sql << join_dependency.join_associations.collect(&:association_join).join
|
1182
1210
|
add_joins!(sql, options, scope)
|
1183
1211
|
end
|
1184
|
-
|
1212
|
+
|
1185
1213
|
add_conditions!(sql, options[:conditions], scope)
|
1186
|
-
|
1214
|
+
if options[:order]
|
1215
|
+
if is_distinct
|
1216
|
+
connection.add_order_by_for_association_limiting!(sql, options)
|
1217
|
+
else
|
1218
|
+
sql << "ORDER BY #{options[:order]}"
|
1219
|
+
end
|
1220
|
+
end
|
1187
1221
|
add_limit!(sql, options, scope)
|
1188
1222
|
return sanitize_sql(sql)
|
1189
1223
|
end
|
1190
|
-
|
1224
|
+
|
1191
1225
|
# Checks if the conditions reference a table other than the current model table
|
1192
1226
|
def include_eager_conditions?(options)
|
1193
1227
|
# look in both sets of conditions
|
@@ -1199,7 +1233,7 @@ module ActiveRecord
|
|
1199
1233
|
end
|
1200
1234
|
end
|
1201
1235
|
return false unless conditions.any?
|
1202
|
-
conditions.join(' ').scan(/(
|
1236
|
+
conditions.join(' ').scan(/([\.\w]+)\.\w+/).flatten.any? do |condition_table_name|
|
1203
1237
|
condition_table_name != table_name
|
1204
1238
|
end
|
1205
1239
|
end
|
@@ -1208,7 +1242,7 @@ module ActiveRecord
|
|
1208
1242
|
def include_eager_order?(options)
|
1209
1243
|
order = options[:order]
|
1210
1244
|
return false unless order
|
1211
|
-
order.scan(/(
|
1245
|
+
order.scan(/([\.\w]+)\.\w+/).flatten.any? do |order_table_name|
|
1212
1246
|
order_table_name != table_name
|
1213
1247
|
end
|
1214
1248
|
end
|
@@ -1225,7 +1259,7 @@ module ActiveRecord
|
|
1225
1259
|
def add_association_callbacks(association_name, options)
|
1226
1260
|
callbacks = %w(before_add after_add before_remove after_remove)
|
1227
1261
|
callbacks.each do |callback_name|
|
1228
|
-
full_callback_name = "#{callback_name
|
1262
|
+
full_callback_name = "#{callback_name}_for_#{association_name}"
|
1229
1263
|
defined_callbacks = options[callback_name.to_sym]
|
1230
1264
|
if options.has_key?(callback_name.to_sym)
|
1231
1265
|
class_inheritable_reader full_callback_name.to_sym
|
@@ -1248,7 +1282,7 @@ module ActiveRecord
|
|
1248
1282
|
extension_module_name.constantize
|
1249
1283
|
end
|
1250
1284
|
|
1251
|
-
class JoinDependency
|
1285
|
+
class JoinDependency # :nodoc:
|
1252
1286
|
attr_reader :joins, :reflections, :table_aliases
|
1253
1287
|
|
1254
1288
|
def initialize(base, associations, joins)
|
@@ -1334,11 +1368,15 @@ module ActiveRecord
|
|
1334
1368
|
when :has_many, :has_and_belongs_to_many
|
1335
1369
|
collection = record.send(join.reflection.name)
|
1336
1370
|
collection.loaded
|
1337
|
-
|
1371
|
+
|
1338
1372
|
return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
|
1339
1373
|
association = join.instantiate(row)
|
1340
1374
|
collection.target.push(association) unless collection.target.include?(association)
|
1341
|
-
when :has_one
|
1375
|
+
when :has_one
|
1376
|
+
return if record.id.to_s != join.parent.record_id(row).to_s
|
1377
|
+
association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
|
1378
|
+
record.send("set_#{join.reflection.name}_target", association)
|
1379
|
+
when :belongs_to
|
1342
1380
|
return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
|
1343
1381
|
association = join.instantiate(row)
|
1344
1382
|
record.send("set_#{join.reflection.name}_target", association)
|
@@ -1348,7 +1386,7 @@ module ActiveRecord
|
|
1348
1386
|
return association
|
1349
1387
|
end
|
1350
1388
|
|
1351
|
-
class JoinBase
|
1389
|
+
class JoinBase # :nodoc:
|
1352
1390
|
attr_reader :active_record, :table_joins
|
1353
1391
|
delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record
|
1354
1392
|
|
@@ -1393,7 +1431,7 @@ module ActiveRecord
|
|
1393
1431
|
end
|
1394
1432
|
end
|
1395
1433
|
|
1396
|
-
class JoinAssociation < JoinBase
|
1434
|
+
class JoinAssociation < JoinBase # :nodoc:
|
1397
1435
|
attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
|
1398
1436
|
delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
|
1399
1437
|
|
@@ -1407,7 +1445,7 @@ module ActiveRecord
|
|
1407
1445
|
@parent = parent
|
1408
1446
|
@reflection = reflection
|
1409
1447
|
@aliased_prefix = "t#{ join_dependency.joins.size }"
|
1410
|
-
@aliased_table_name = table_name # start with the table name
|
1448
|
+
@aliased_table_name = table_name #.tr('.', '_') # start with the table name, sub out any .'s
|
1411
1449
|
@parent_table_name = parent.active_record.table_name
|
1412
1450
|
|
1413
1451
|
if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{aliased_table_name.downcase}\son}
|
@@ -1418,18 +1456,22 @@ module ActiveRecord
|
|
1418
1456
|
# if the table name has been used, then use an alias
|
1419
1457
|
@aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}"
|
1420
1458
|
table_index = join_dependency.table_aliases[aliased_table_name]
|
1459
|
+
join_dependency.table_aliases[aliased_table_name] += 1
|
1421
1460
|
@aliased_table_name = @aliased_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
|
1461
|
+
else
|
1462
|
+
join_dependency.table_aliases[aliased_table_name] += 1
|
1422
1463
|
end
|
1423
|
-
join_dependency.table_aliases[aliased_table_name] += 1
|
1424
1464
|
|
1425
1465
|
if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through])
|
1426
1466
|
@aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name
|
1427
1467
|
unless join_dependency.table_aliases[aliased_join_table_name].zero?
|
1428
1468
|
@aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join"
|
1429
1469
|
table_index = join_dependency.table_aliases[aliased_join_table_name]
|
1470
|
+
join_dependency.table_aliases[aliased_join_table_name] += 1
|
1430
1471
|
@aliased_join_table_name = @aliased_join_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
|
1472
|
+
else
|
1473
|
+
join_dependency.table_aliases[aliased_join_table_name] += 1
|
1431
1474
|
end
|
1432
|
-
join_dependency.table_aliases[aliased_join_table_name] += 1
|
1433
1475
|
end
|
1434
1476
|
end
|
1435
1477
|
|
@@ -1440,7 +1482,7 @@ module ActiveRecord
|
|
1440
1482
|
table_alias_for(options[:join_table], aliased_join_table_name),
|
1441
1483
|
aliased_join_table_name,
|
1442
1484
|
options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
|
1443
|
-
|
1485
|
+
parent.aliased_table_name, reflection.active_record.primary_key] +
|
1444
1486
|
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
|
1445
1487
|
table_name_and_alias, aliased_table_name, klass.primary_key,
|
1446
1488
|
aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key
|
@@ -1457,7 +1499,7 @@ module ActiveRecord
|
|
1457
1499
|
table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
|
1458
1500
|
aliased_join_table_name, polymorphic_foreign_key,
|
1459
1501
|
parent.aliased_table_name, parent.primary_key,
|
1460
|
-
aliased_join_table_name, polymorphic_foreign_type, klass.
|
1502
|
+
aliased_join_table_name, polymorphic_foreign_type, klass.quote_value(parent.active_record.base_class.name)] +
|
1461
1503
|
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [table_name_and_alias,
|
1462
1504
|
aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.classify.foreign_key
|
1463
1505
|
]
|
@@ -1472,23 +1514,28 @@ module ActiveRecord
|
|
1472
1514
|
aliased_table_name, "#{source_reflection.options[:as]}_id",
|
1473
1515
|
aliased_join_table_name, options[:foreign_key] || primary_key,
|
1474
1516
|
aliased_table_name, "#{source_reflection.options[:as]}_type",
|
1475
|
-
klass.
|
1517
|
+
klass.quote_value(source_reflection.active_record.base_class.name)
|
1476
1518
|
]
|
1477
1519
|
else
|
1478
1520
|
case source_reflection.macro
|
1479
1521
|
when :belongs_to
|
1480
1522
|
first_key = primary_key
|
1481
|
-
second_key = options[:foreign_key] || klass.to_s.classify.foreign_key
|
1523
|
+
second_key = source_reflection.options[:foreign_key] || klass.to_s.classify.foreign_key
|
1524
|
+
extra = nil
|
1482
1525
|
when :has_many
|
1483
|
-
first_key = through_reflection.klass.to_s.classify.foreign_key
|
1526
|
+
first_key = through_reflection.klass.base_class.to_s.classify.foreign_key
|
1484
1527
|
second_key = options[:foreign_key] || primary_key
|
1528
|
+
extra = through_reflection.klass.descends_from_active_record? ? nil :
|
1529
|
+
" AND %s.%s = %s" % [
|
1530
|
+
aliased_join_table_name,
|
1531
|
+
reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
|
1532
|
+
through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
|
1485
1533
|
end
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
|
1534
|
+
" LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) " % [
|
1535
|
+
table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
|
1536
|
+
aliased_join_table_name, through_reflection.primary_key_name,
|
1537
|
+
parent.aliased_table_name, parent.primary_key, extra] +
|
1538
|
+
" LEFT OUTER JOIN %s ON (%s.%s = %s.%s) " % [
|
1492
1539
|
table_name_and_alias,
|
1493
1540
|
aliased_table_name, first_key,
|
1494
1541
|
aliased_join_table_name, second_key
|
@@ -1502,7 +1549,7 @@ module ActiveRecord
|
|
1502
1549
|
aliased_table_name, "#{reflection.options[:as]}_id",
|
1503
1550
|
parent.aliased_table_name, parent.primary_key,
|
1504
1551
|
aliased_table_name, "#{reflection.options[:as]}_type",
|
1505
|
-
klass.
|
1552
|
+
klass.quote_value(parent.active_record.base_class.name)
|
1506
1553
|
]
|
1507
1554
|
when reflection.macro == :has_one && reflection.options[:as]
|
1508
1555
|
" LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
|
@@ -1510,7 +1557,7 @@ module ActiveRecord
|
|
1510
1557
|
aliased_table_name, "#{reflection.options[:as]}_id",
|
1511
1558
|
parent.aliased_table_name, parent.primary_key,
|
1512
1559
|
aliased_table_name, "#{reflection.options[:as]}_type",
|
1513
|
-
klass.
|
1560
|
+
klass.quote_value(reflection.active_record.base_class.name)
|
1514
1561
|
]
|
1515
1562
|
else
|
1516
1563
|
foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
|
@@ -1530,9 +1577,13 @@ module ActiveRecord
|
|
1530
1577
|
end || ''
|
1531
1578
|
join << %(AND %s.%s = %s ) % [
|
1532
1579
|
aliased_table_name,
|
1533
|
-
reflection.active_record.connection.quote_column_name(
|
1534
|
-
klass.
|
1535
|
-
|
1580
|
+
reflection.active_record.connection.quote_column_name(klass.inheritance_column),
|
1581
|
+
klass.quote_value(klass.name.demodulize)] unless klass.descends_from_active_record?
|
1582
|
+
|
1583
|
+
[through_reflection, reflection].each do |ref|
|
1584
|
+
join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions]
|
1585
|
+
end
|
1586
|
+
|
1536
1587
|
join
|
1537
1588
|
end
|
1538
1589
|
|
@@ -1550,8 +1601,8 @@ module ActiveRecord
|
|
1550
1601
|
end
|
1551
1602
|
|
1552
1603
|
def interpolate_sql(sql)
|
1553
|
-
instance_eval("%@#{sql.gsub('@', '\@')}@")
|
1554
|
-
end
|
1604
|
+
instance_eval("%@#{sql.gsub('@', '\@')}@")
|
1605
|
+
end
|
1555
1606
|
end
|
1556
1607
|
end
|
1557
1608
|
end
|