2007-01-22 14:43:50 +01:00
module ActiveRecord
module Associations
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
2009-09-05 09:01:46 +02:00
def initialize ( owner , reflection )
super
@primary_key_list = { }
end
2007-02-09 09:04:31 +01:00
def create ( attributes = { } )
2007-12-21 08:48:59 +01:00
create_record ( attributes ) { | record | insert_record ( record ) }
end
2008-05-18 06:22:34 +02:00
2007-12-21 08:48:59 +01:00
def create! ( attributes = { } )
create_record ( attributes ) { | record | insert_record ( record , true ) }
2007-02-09 09:04:31 +01:00
end
2009-02-04 21:26:08 +01:00
def columns
@reflection . columns ( @reflection . options [ :join_table ] , " #{ @reflection . options [ :join_table ] } Columns " )
end
def reset_column_information
@reflection . reset_column_information
end
2009-09-05 09:01:46 +02:00
def has_primary_key?
return @has_primary_key unless @has_primary_key . nil?
2009-12-01 02:38:34 +01:00
@has_primary_key = ( @owner . connection . supports_primary_key? &&
@owner . connection . primary_key ( @reflection . options [ :join_table ] ) )
2009-09-05 09:01:46 +02:00
end
2008-05-18 06:22:34 +02:00
protected
def construct_find_options! ( options )
2007-01-22 14:43:50 +01:00
options [ :joins ] = @join_sql
2007-12-21 08:48:59 +01:00
options [ :readonly ] = finding_with_ambiguous_select? ( options [ :select ] || @reflection . options [ :select ] )
2008-05-18 06:22:34 +02:00
options [ :select ] || = ( @reflection . options [ :select ] || '*' )
2007-01-22 14:43:50 +01:00
end
2008-05-18 06:22:34 +02:00
2007-01-22 14:43:50 +01:00
def count_records
load_target . size
end
2009-02-28 02:23:00 +01:00
def insert_record ( record , force = true , validate = true )
2009-09-05 09:01:46 +02:00
if has_primary_key?
raise ActiveRecord :: ConfigurationError ,
" Primary key is not allowed in a has_and_belongs_to_many join table ( #{ @reflection . options [ :join_table ] } ). "
end
2007-01-22 14:43:50 +01:00
if record . new_record?
2007-12-21 08:48:59 +01:00
if force
record . save!
else
2009-02-28 02:23:00 +01:00
return false unless record . save ( validate )
2007-12-21 08:48:59 +01:00
end
2007-01-22 14:43:50 +01:00
end
if @reflection . options [ :insert_sql ]
2008-05-18 06:22:34 +02:00
@owner . connection . insert ( interpolate_sql ( @reflection . options [ :insert_sql ] , record ) )
2007-01-22 14:43:50 +01:00
else
2008-05-18 06:22:34 +02:00
attributes = columns . inject ( { } ) do | attrs , column |
case column . name . to_s
when @reflection . primary_key_name . to_s
2008-10-27 07:47:01 +01:00
attrs [ column . name ] = owner_quoted_id
2008-05-18 06:22:34 +02:00
when @reflection . association_foreign_key . to_s
attrs [ column . name ] = record . quoted_id
2007-01-22 14:43:50 +01:00
else
2008-05-18 06:22:34 +02:00
if record . has_attribute? ( column . name )
2007-02-09 09:04:31 +01:00
value = @owner . send ( :quote_value , record [ column . name ] , column )
2008-05-18 06:22:34 +02:00
attrs [ column . name ] = value unless value . nil?
2007-01-22 14:43:50 +01:00
end
end
2008-05-18 06:22:34 +02:00
attrs
2007-01-22 14:43:50 +01:00
end
sql =
2008-05-18 06:22:34 +02:00
" INSERT INTO #{ @owner . connection . quote_table_name @reflection . options [ :join_table ] } ( #{ @owner . send ( :quoted_column_names , attributes ) . join ( ', ' ) } ) " +
2007-01-22 14:43:50 +01:00
" VALUES ( #{ attributes . values . join ( ', ' ) } ) "
2008-05-18 06:22:34 +02:00
@owner . connection . insert ( sql )
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
return true
end
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
def delete_records ( records )
if sql = @reflection . options [ :delete_sql ]
2008-05-18 06:22:34 +02:00
records . each { | record | @owner . connection . delete ( interpolate_sql ( sql , record ) ) }
2007-01-22 14:43:50 +01:00
else
ids = quoted_record_ids ( records )
2008-10-27 07:47:01 +01:00
sql = " DELETE FROM #{ @owner . connection . quote_table_name @reflection . options [ :join_table ] } WHERE #{ @reflection . primary_key_name } = #{ owner_quoted_id } AND #{ @reflection . association_foreign_key } IN ( #{ ids } ) "
2008-05-18 06:22:34 +02:00
@owner . connection . delete ( sql )
2007-01-22 14:43:50 +01:00
end
end
2007-12-21 08:48:59 +01:00
2007-01-22 14:43:50 +01:00
def construct_sql
if @reflection . options [ :finder_sql ]
2008-09-07 07:54:05 +02:00
@finder_sql = interpolate_sql ( @reflection . options [ :finder_sql ] )
2007-01-22 14:43:50 +01:00
else
2008-10-27 07:47:01 +01:00
@finder_sql = " #{ @owner . connection . quote_table_name @reflection . options [ :join_table ] } . #{ @reflection . primary_key_name } = #{ owner_quoted_id } "
2007-01-22 14:43:50 +01:00
@finder_sql << " AND ( #{ conditions } ) " if conditions
end
2008-05-18 06:22:34 +02:00
@join_sql = " INNER JOIN #{ @owner . connection . quote_table_name @reflection . options [ :join_table ] } ON #{ @reflection . quoted_table_name } . #{ @reflection . klass . primary_key } = #{ @owner . connection . quote_table_name @reflection . options [ :join_table ] } . #{ @reflection . association_foreign_key } "
2008-10-27 07:47:01 +01:00
if @reflection . options [ :counter_sql ]
@counter_sql = interpolate_sql ( @reflection . options [ :counter_sql ] )
elsif @reflection . options [ :finder_sql ]
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
@reflection . options [ :counter_sql ] = @reflection . options [ :finder_sql ] . sub ( / SELECT ( \/ \ *.*? \ * \/ )?(.*) \ bFROM \ b /im ) { " SELECT #{ $1 } COUNT(*) FROM " }
@counter_sql = interpolate_sql ( @reflection . options [ :counter_sql ] )
else
@counter_sql = @finder_sql
end
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
def construct_scope
{ :find = > { :conditions = > @finder_sql ,
:joins = > @join_sql ,
:readonly = > false ,
:order = > @reflection . options [ :order ] ,
2008-09-07 07:54:05 +02:00
:include = > @reflection . options [ :include ] ,
2007-12-21 08:48:59 +01:00
:limit = > @reflection . options [ :limit ] } }
end
# Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
# clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
# an id column. This will then overwrite the id column of the records coming back.
def finding_with_ambiguous_select? ( select_clause )
2009-02-04 21:26:08 +01:00
! select_clause && columns . size != 2
2007-01-22 14:43:50 +01:00
end
2007-12-21 08:48:59 +01:00
private
2008-05-18 06:22:34 +02:00
def create_record ( attributes , & block )
2007-12-21 08:48:59 +01:00
# Can't use Base.create because the foreign key may be a protected attribute.
ensure_owner_is_not_new
if attributes . is_a? ( Array )
attributes . collect { | attr | create ( attr ) }
else
2008-05-18 06:22:34 +02:00
build_record ( attributes , & block )
2007-12-21 08:48:59 +01:00
end
end
2007-01-22 14:43:50 +01:00
end
end
end