OSDN Git Service

83a4a8b9e2785fca53d6a658be28f9187666bc6d
[redminele/redminele.git] / ruby / lib / ruby / gems / 1.8 / gems / activerecord-2.3.5 / lib / active_record / associations / has_and_belongs_to_many_association.rb
1 module ActiveRecord
2   module Associations
3     class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
4       def initialize(owner, reflection)
5         super
6         @primary_key_list = {}
7       end
8
9       def create(attributes = {})
10         create_record(attributes) { |record| insert_record(record) }
11       end
12
13       def create!(attributes = {})
14         create_record(attributes) { |record| insert_record(record, true) }
15       end
16
17       def columns
18         @reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
19       end
20
21       def reset_column_information
22         @reflection.reset_column_information
23       end
24
25       def has_primary_key?
26         return @has_primary_key unless @has_primary_key.nil?
27         @has_primary_key = (@owner.connection.supports_primary_key? &&
28           @owner.connection.primary_key(@reflection.options[:join_table]))
29       end
30
31       protected
32         def construct_find_options!(options)
33           options[:joins]      = @join_sql
34           options[:readonly]   = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
35           options[:select]   ||= (@reflection.options[:select] || '*')
36         end
37         
38         def count_records
39           load_target.size
40         end
41
42         def insert_record(record, force = true, validate = true)
43           if has_primary_key?
44             raise ActiveRecord::ConfigurationError,
45               "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
46           end
47
48           if record.new_record?
49             if force
50               record.save!
51             else
52               return false unless record.save(validate)
53             end
54           end
55
56           if @reflection.options[:insert_sql]
57             @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
58           else
59             attributes = columns.inject({}) do |attrs, column|
60               case column.name.to_s
61                 when @reflection.primary_key_name.to_s
62                   attrs[column.name] = owner_quoted_id
63                 when @reflection.association_foreign_key.to_s
64                   attrs[column.name] = record.quoted_id
65                 else
66                   if record.has_attribute?(column.name)
67                     value = @owner.send(:quote_value, record[column.name], column)
68                     attrs[column.name] = value unless value.nil?
69                   end
70               end
71               attrs
72             end
73
74             sql =
75               "INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
76               "VALUES (#{attributes.values.join(', ')})"
77
78             @owner.connection.insert(sql)
79           end
80
81           return true
82         end
83
84         def delete_records(records)
85           if sql = @reflection.options[:delete_sql]
86             records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
87           else
88             ids = quoted_record_ids(records)
89             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})"
90             @owner.connection.delete(sql)
91           end
92         end
93
94         def construct_sql
95           if @reflection.options[:finder_sql]
96             @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
97           else
98             @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
99             @finder_sql << " AND (#{conditions})" if conditions
100           end
101
102           @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}"
103
104           if @reflection.options[:counter_sql]
105             @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
106           elsif @reflection.options[:finder_sql]
107             # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
108             @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
109             @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
110           else
111             @counter_sql = @finder_sql
112           end
113         end
114
115         def construct_scope
116           { :find => {  :conditions => @finder_sql,
117                         :joins => @join_sql,
118                         :readonly => false,
119                         :order => @reflection.options[:order],
120                         :include => @reflection.options[:include],
121                         :limit => @reflection.options[:limit] } }
122         end
123
124         # Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
125         # clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
126         # an id column. This will then overwrite the id column of the records coming back.
127         def finding_with_ambiguous_select?(select_clause)
128           !select_clause && columns.size != 2
129         end
130
131       private
132         def create_record(attributes, &block)
133           # Can't use Base.create because the foreign key may be a protected attribute.
134           ensure_owner_is_not_new
135           if attributes.is_a?(Array)
136             attributes.collect { |attr| create(attr) }
137           else
138             build_record(attributes, &block)
139           end
140         end
141     end
142   end
143 end