Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 159 lines (133 sloc) 5.813 kB
db045db @dhh Initial
dhh authored
1 module ActiveRecord
2 module Associations
3 class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
4 def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
5 super
68d1056 @dhh Fixed that has_and_belongs_to_many would generate bad sql when naming…
dhh authored
6
8a40c6b @dhh Fixed has_and_belongs_to_many guessing of foreign key so that keys ar…
dhh authored
7 @association_foreign_key = options[:association_foreign_key] || Inflector.underscore(Inflector.demodulize(association_class_name)) + "_id"
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
8 @association_table_name = options[:table_name] || @association_class.table_name
9 @join_table = options[:join_table]
68d1056 @dhh Fixed that has_and_belongs_to_many would generate bad sql when naming…
dhh authored
10 @order = options[:order] || "t.#{@association_class.primary_key}"
db045db @dhh Initial
dhh authored
11
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
12 construct_sql
db045db @dhh Initial
dhh authored
13 end
14
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
15 def build(attributes = {})
16 load_target
17 record = @association_class.new(attributes)
18 @target << record
19 record
20 end
21
db045db @dhh Initial
dhh authored
22 # Removes all records from this association. Returns +self+ so method calls may be chained.
23 def clear
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
24 return self if size == 0 # forces load_target if hasn't happened already
db045db @dhh Initial
dhh authored
25
26 if sql = @options[:delete_sql]
27 each { |record| @owner.connection.execute(sql) }
28 elsif @options[:conditions]
29 sql =
4940383 @dhh Fixed value quoting in all generated SQL statements, so that integers…
dhh authored
30 "DELETE FROM #{@join_table} WHERE #{@association_class_primary_key_name} = #{@owner.quoted_id} " +
db045db @dhh Initial
dhh authored
31 "AND #{@association_foreign_key} IN (#{collect { |record| record.id }.join(", ")})"
32 @owner.connection.execute(sql)
33 else
4940383 @dhh Fixed value quoting in all generated SQL statements, so that integers…
dhh authored
34 sql = "DELETE FROM #{@join_table} WHERE #{@association_class_primary_key_name} = #{@owner.quoted_id}"
db045db @dhh Initial
dhh authored
35 @owner.connection.execute(sql)
36 end
37
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
38 @target = []
db045db @dhh Initial
dhh authored
39 self
40 end
41
6bd672e @dhh Added that Base#find takes an optional options hash, including :condi…
dhh authored
42 def find_first
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
43 load_target.first
6bd672e @dhh Added that Base#find takes an optional options hash, including :condi…
dhh authored
44 end
45
46 def find(*args)
47 # Return an Array if multiple ids are given.
48 expects_array = args.first.kind_of?(Array)
49
50 ids = args.flatten.compact.uniq
51
52 # If no block is given, raise RecordNotFound.
53 if ids.empty?
54 raise RecordNotFound, "Couldn't find #{@association_class.name} without an ID#{conditions}"
55
56 # If using a custom finder_sql, scan the entire collection.
57 elsif @options[:finder_sql]
58 if ids.size == 1
59 id = ids.first
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
60 record = load_target.detect { |record| id == record.id }
6bd672e @dhh Added that Base#find takes an optional options hash, including :condi…
dhh authored
61 expects_array? ? [record] : record
62 else
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
63 load_target.select { |record| ids.include?(record.id) }
6bd672e @dhh Added that Base#find takes an optional options hash, including :condi…
dhh authored
64 end
65
66 # Otherwise, construct a query.
db045db @dhh Initial
dhh authored
67 else
6bd672e @dhh Added that Base#find takes an optional options hash, including :condi…
dhh authored
68 ids_list = ids.map { |id| @owner.send(:quote, id) }.join(',')
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
69 records = find_target(@finder_sql.sub(/ORDER BY/, "AND j.#{@association_foreign_key} IN (#{ids_list}) ORDER BY"))
6bd672e @dhh Added that Base#find takes an optional options hash, including :condi…
dhh authored
70 if records.size == ids.size
71 if ids.size == 1 and !expects_array
72 records.first
73 else
74 records
75 end
db045db @dhh Initial
dhh authored
76 else
6bd672e @dhh Added that Base#find takes an optional options hash, including :condi…
dhh authored
77 raise RecordNotFound, "Couldn't find #{@association_class.name} with ID in (#{ids_list})"
db045db @dhh Initial
dhh authored
78 end
79 end
80 end
81
82 def push_with_attributes(record, join_attributes = {})
83 raise_on_type_mismatch(record)
b29c01e @dhh Added that has_and_belongs_to_many associations with additional attri…
dhh authored
84 join_attributes.each { |key, value| record[key.to_s] = value }
85 insert_record(record) unless @owner.new_record?
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
86 @target << record
db045db @dhh Initial
dhh authored
87 self
88 end
89
90 alias :concat_with_attributes :push_with_attributes
91
92 def size
93 @options[:uniq] ? count_records : super
94 end
95
96 protected
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
97 def find_target(sql = @finder_sql)
db045db @dhh Initial
dhh authored
98 records = @association_class.find_by_sql(sql)
99 @options[:uniq] ? uniq(records) : records
100 end
6bd672e @dhh Added that Base#find takes an optional options hash, including :condi…
dhh authored
101
db045db @dhh Initial
dhh authored
102 def count_records
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
103 load_target.size
db045db @dhh Initial
dhh authored
104 end
105
106 def insert_record(record)
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
107 return false unless record.save
b29c01e @dhh Added that has_and_belongs_to_many associations with additional attri…
dhh authored
108
db045db @dhh Initial
dhh authored
109 if @options[:insert_sql]
110 @owner.connection.execute(interpolate_sql(@options[:insert_sql], record))
111 else
b29c01e @dhh Added that has_and_belongs_to_many associations with additional attri…
dhh authored
112 columns = @owner.connection.columns(@join_table, "#{@join_table} Columns")
113
114 attributes = columns.inject({}) do |attributes, column|
115 case column.name
116 when @association_class_primary_key_name
117 attributes[column.name] = @owner.quoted_id
118 when @association_foreign_key
119 attributes[column.name] = record.quoted_id
120 else
121 value = record[column.name]
122 attributes[column.name] = value unless value.nil?
123 end
124 attributes
125 end
126
127 sql =
128 "INSERT INTO #{@join_table} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
129 "VALUES (#{attributes.values.collect { |value| @owner.send(:quote, value) }.join(', ')})"
130
db045db @dhh Initial
dhh authored
131 @owner.connection.execute(sql)
132 end
b29c01e @dhh Added that has_and_belongs_to_many associations with additional attri…
dhh authored
133
134 return true
db045db @dhh Initial
dhh authored
135 end
ac3c8a5 @dhh Silence errors occurring when reloading classes
dhh authored
136
db045db @dhh Initial
dhh authored
137 def delete_records(records)
138 if sql = @options[:delete_sql]
139 records.each { |record| @owner.connection.execute(sql) }
140 else
141 ids = quoted_record_ids(records)
4940383 @dhh Fixed value quoting in all generated SQL statements, so that integers…
dhh authored
142 sql = "DELETE FROM #{@join_table} WHERE #{@association_class_primary_key_name} = #{@owner.quoted_id} AND #{@association_foreign_key} IN (#{ids})"
db045db @dhh Initial
dhh authored
143 @owner.connection.execute(sql)
144 end
145 end
823554e @dhh Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
146
147 def construct_sql
148 interpolate_sql_options!(@options, :finder_sql, :delete_sql)
149 @finder_sql = @options[:finder_sql] ||
150 "SELECT t.*, j.* FROM #{@association_table_name} t, #{@join_table} j " +
151 "WHERE t.#{@association_class.primary_key} = j.#{@association_foreign_key} AND " +
152 "j.#{@association_class_primary_key_name} = #{@owner.quoted_id} " +
153 (@options[:conditions] ? " AND " + interpolate_sql(@options[:conditions]) : "") + " " +
154 "ORDER BY #{@order}"
155 end
156 end
db045db @dhh Initial
dhh authored
157 end
68d1056 @dhh Fixed that has_and_belongs_to_many would generate bad sql when naming…
dhh authored
158 end
Something went wrong with that request. Please try again.