aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/associations.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/associations.rb')
-rwxr-xr-xactiverecord/lib/active_record/associations.rb66
1 files changed, 49 insertions, 17 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index aeca74ef4a..baad8fc5fd 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -42,11 +42,12 @@ module ActiveRecord
end
end
- class HasManyThroughCantAssociateThroughHasManyReflection < ActiveRecordError #:nodoc:
+ class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
end
end
+
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
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.")
@@ -416,6 +417,32 @@ module ActiveRecord
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
# @firm.invoices # selects all invoices by going through the Client join model.
#
+ # Similarly you can go through a +has_one+ association on the join model:
+ #
+ # class Group < ActiveRecord::Base
+ # has_many :users
+ # has_many :avatars, :through => :users
+ # end
+ #
+ # class User < ActiveRecord::Base
+ # belongs_to :group
+ # has_one :avatar
+ # end
+ #
+ # class Avatar < ActiveRecord::Base
+ # belongs_to :user
+ # end
+ #
+ # @group = Group.first
+ # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
+ # @group.avatars # selects all avatars by going through the User join model.
+ #
+ # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are
+ # *read-only*. For example, the following would not work following the previous example:
+ #
+ # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around.
+ # @group.avatars.delete(@group.avatars.last) # so would this
+ #
# === Polymorphic Associations
#
# Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
@@ -819,7 +846,7 @@ module ActiveRecord
# [:through]
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
- # or <tt>has_many</tt> association on the join model.
+ # <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
# [:source]
# Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
@@ -1367,7 +1394,7 @@ module ActiveRecord
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
ids = (new_value || []).reject { |nid| nid.blank? }
- send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
+ send("#{reflection.name}=", reflection.klass.find(ids))
end
end
end
@@ -1656,7 +1683,7 @@ module ActiveRecord
relation.join(joins)
relation.where(construct_conditions(options[:conditions], scope))
- relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
+ relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
relation.project(column_aliases(join_dependency))
relation.group(construct_group(options[:group], options[:having], scope))
@@ -1685,18 +1712,23 @@ module ActiveRecord
end
def construct_finder_sql_for_association_limiting(options, join_dependency)
- # Only join tables referenced in order or conditions since this is particularly slow on the pre-query.
- tables_from_conditions = conditions_tables(options)
- tables_from_order = order_tables(options)
- all_tables = tables_from_conditions + tables_from_order
- options[:joins] = all_tables.uniq.map {|table|
- join_dependency.joins_for_table_name(table)
- }.flatten.compact.uniq.collect { |assoc| assoc.association_join }.join
-
- construct_finder_sql(options.merge(
- :select => connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(","))
- )
- )
+ scope = scope(:find)
+
+ relation = arel_table(options[:from])
+
+ joins = join_dependency.join_associations.collect{|join| join.association_join }.join
+ joins << construct_join(options[:joins], scope)
+ relation.join(joins)
+
+ relation.where(construct_conditions(options[:conditions], scope))
+ relation.project(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(",")))
+
+ relation.group(construct_group(options[:group], options[:having], scope))
+ relation.order(construct_order(options[:order], scope))
+ relation.take(construct_limit(options[:limit], scope))
+ relation.skip(construct_limit(options[:offset], scope))
+
+ sanitize_sql(relation.to_sql)
end
def tables_in_string(string)
@@ -1870,7 +1902,7 @@ module ActiveRecord
descendant
end.flatten.compact
- remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty?
+ remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
end
end
end