Skip to content
This repository
Browse code

Added that has_and_belongs_to_many associations with additional attri…

…butes also can be created between unsaved objects and only committed to the database when Base#save is called on the associator #524 [Eric Anderson]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@484 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit b29c01ea8914422c6f7e0bb5c65d3b8610dc54d1 1 parent 1d61845
David Heinemeier Hansson authored January 24, 2005
2  activerecord/CHANGELOG
... ...
@@ -1,5 +1,7 @@
1 1
 *SVN*
2 2
 
  3
+* Added that has_and_belongs_to_many associations with additional attributes also can be created between unsaved objects and only committed to the database when Base#save is called on the associator #524 [Eric Anderson]
  4
+
3 5
 * Fixed that records fetched with piggy-back attributes or through rich has_and_belongs_to_many associations couldn't be saved due to the extra attributes not part of the table #522 [Eric Anderson]
4 6
 
5 7
 * Added mass-assignment protection for the inheritance column -- regardless of a custom column is used or not
37  activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -81,8 +81,8 @@ def find(*args)
81 81
 
82 82
       def push_with_attributes(record, join_attributes = {})
83 83
         raise_on_type_mismatch(record)
84  
-        insert_record_with_join_attributes(record, join_attributes)
85  
-        join_attributes.each { |key, value| record.send(:write_attribute, key, value) }
  84
+        join_attributes.each { |key, value| record[key.to_s] = value }
  85
+        insert_record(record) unless @owner.new_record?
86 86
         @target << record
87 87
         self
88 88
       end
@@ -105,22 +105,33 @@ def count_records
105 105
 
106 106
         def insert_record(record)
107 107
           return false unless record.save
  108
+
108 109
           if @options[:insert_sql]
109 110
             @owner.connection.execute(interpolate_sql(@options[:insert_sql], record))
110 111
           else
111  
-            sql = "INSERT INTO #{@join_table} (#{@association_class_primary_key_name}, #{@association_foreign_key}) " + 
112  
-                  "VALUES (#{@owner.quoted_id},#{record.quoted_id})"
  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
+
113 131
             @owner.connection.execute(sql)
114 132
           end
115  
-          true
116  
-        end
117  
-        
118  
-        def insert_record_with_join_attributes(record, join_attributes)
119  
-          attributes = { @association_class_primary_key_name => @owner.id, @association_foreign_key => record.id }.update(join_attributes)
120  
-          sql = 
121  
-            "INSERT INTO #{@join_table} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
122  
-            "VALUES (#{attributes.values.collect { |value| @owner.send(:quote, value) }.join(', ')})"
123  
-          @owner.connection.execute(sql)
  133
+          
  134
+          return true
124 135
         end
125 136
         
126 137
         def delete_records(records)
12  activerecord/lib/active_record/base.rb
@@ -1115,11 +1115,13 @@ def attributes_protected_by_default
1115 1115
       # an SQL statement. 
1116 1116
       def attributes_with_quotes(include_primary_key = true)
1117 1117
         columns_hash = self.class.columns_hash
  1118
+
1118 1119
         attrs_quoted = @attributes.inject({}) do |attrs_quoted, pair| 
1119 1120
           attrs_quoted[pair.first] = quote(pair.last, columns_hash[pair.first]) unless !include_primary_key && pair.first == self.class.primary_key
1120 1121
           attrs_quoted
1121 1122
         end
1122  
-        attrs_quoted.delete_if { | key, value | !self.class.columns_hash.keys.include?(key) }
  1123
+
  1124
+        attrs_quoted.delete_if { |key, value| !self.class.columns_hash.keys.include?(key) }
1123 1125
       end
1124 1126
       
1125 1127
       # Quote strings appropriately for SQL statements.
@@ -1178,7 +1180,7 @@ def extract_callstack_for_multiparameter_attributes(pairs)
1178 1180
 
1179 1181
           unless value.empty?
1180 1182
             attributes[attribute_name] << 
1181  
-              [find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value)]
  1183
+              [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
1182 1184
           end
1183 1185
         end
1184 1186
 
@@ -1203,10 +1205,10 @@ def quoted_column_names(attributes = attributes_with_quotes)
1203 1205
       end
1204 1206
 
1205 1207
       def quote_columns(column_quoter, hash)
1206  
-        hash.inject({}) {|list, pair|
  1208
+        hash.inject({}) do |list, pair|
1207 1209
           list[column_quoter.quote_column_name(pair.first)] = pair.last
1208 1210
           list
1209  
-        }
  1211
+        end
1210 1212
       end
1211 1213
 
1212 1214
       def quoted_comma_pair_list(column_quoter, hash)
@@ -1231,4 +1233,4 @@ def has_yaml_encoding_header?(string)
1231 1233
         string[0..3] == "--- "
1232 1234
       end
1233 1235
   end
1234  
-end
  1236
+end
21  activerecord/test/associations_test.rb
@@ -724,6 +724,27 @@ def test_habtm_adding_before_save
724 724
     assert_equal 2, aridridel.projects(true).size
725 725
   end
726 726
 
  727
+  def test_habtm_adding_before_save_with_join_attributes
  728
+    no_of_devels = Developer.count
  729
+    no_of_projects = Project.count
  730
+    now = Date.today
  731
+    ken = Developer.new("name" => "Ken")
  732
+    ken.projects.push_with_attributes( Project.find(1), :joined_on => now )
  733
+    p = Project.new("name" => "Foomatic")
  734
+    ken.projects.push_with_attributes( p, :joined_on => now )
  735
+    assert ken.new_record?
  736
+    assert p.new_record?
  737
+    assert ken.save
  738
+    assert !ken.new_record?
  739
+    assert_equal no_of_devels+1, Developer.count
  740
+    assert_equal no_of_projects+1, Project.count
  741
+    assert_equal 2, ken.projects.size
  742
+    assert_equal 2, ken.projects(true).size
  743
+
  744
+    kenReloaded = Developer.find_by_name 'Ken'
  745
+    kenReloaded.projects.each { |prj| assert_equal(now.to_s, prj.joined_on.to_s) }
  746
+  end
  747
+
727 748
   def test_build
728 749
     devel = Developer.find(1)
729 750
     proj = devel.projects.build("name" => "Projekt")

0 notes on commit b29c01e

Please sign in to comment.
Something went wrong with that request. Please try again.