Skip to content

Commit

Permalink
Added that has_and_belongs_to_many associations with additional attri…
Browse files Browse the repository at this point in the history
…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
dhh committed Jan 24, 2005
1 parent 1d61845 commit b29c01e
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 18 deletions.
2 changes: 2 additions & 0 deletions activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*SVN* *SVN*


* 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]

* 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] * 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]


* Added mass-assignment protection for the inheritance column -- regardless of a custom column is used or not * Added mass-assignment protection for the inheritance column -- regardless of a custom column is used or not
Expand Down
Expand Up @@ -81,8 +81,8 @@ def find(*args)


def push_with_attributes(record, join_attributes = {}) def push_with_attributes(record, join_attributes = {})
raise_on_type_mismatch(record) raise_on_type_mismatch(record)
insert_record_with_join_attributes(record, join_attributes) join_attributes.each { |key, value| record[key.to_s] = value }
join_attributes.each { |key, value| record.send(:write_attribute, key, value) } insert_record(record) unless @owner.new_record?
@target << record @target << record
self self
end end
Expand All @@ -105,22 +105,33 @@ def count_records


def insert_record(record) def insert_record(record)
return false unless record.save return false unless record.save

if @options[:insert_sql] if @options[:insert_sql]
@owner.connection.execute(interpolate_sql(@options[:insert_sql], record)) @owner.connection.execute(interpolate_sql(@options[:insert_sql], record))
else else
sql = "INSERT INTO #{@join_table} (#{@association_class_primary_key_name}, #{@association_foreign_key}) " + columns = @owner.connection.columns(@join_table, "#{@join_table} Columns")
"VALUES (#{@owner.quoted_id},#{record.quoted_id})"
attributes = columns.inject({}) do |attributes, column|
case column.name
when @association_class_primary_key_name
attributes[column.name] = @owner.quoted_id
when @association_foreign_key
attributes[column.name] = record.quoted_id
else
value = record[column.name]
attributes[column.name] = value unless value.nil?
end
attributes
end

sql =
"INSERT INTO #{@join_table} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
"VALUES (#{attributes.values.collect { |value| @owner.send(:quote, value) }.join(', ')})"

@owner.connection.execute(sql) @owner.connection.execute(sql)
end end
true
end return true

def insert_record_with_join_attributes(record, join_attributes)
attributes = { @association_class_primary_key_name => @owner.id, @association_foreign_key => record.id }.update(join_attributes)
sql =
"INSERT INTO #{@join_table} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
"VALUES (#{attributes.values.collect { |value| @owner.send(:quote, value) }.join(', ')})"
@owner.connection.execute(sql)
end end


def delete_records(records) def delete_records(records)
Expand Down
12 changes: 7 additions & 5 deletions activerecord/lib/active_record/base.rb
Expand Up @@ -1115,11 +1115,13 @@ def attributes_protected_by_default
# an SQL statement. # an SQL statement.
def attributes_with_quotes(include_primary_key = true) def attributes_with_quotes(include_primary_key = true)
columns_hash = self.class.columns_hash columns_hash = self.class.columns_hash

attrs_quoted = @attributes.inject({}) do |attrs_quoted, pair| attrs_quoted = @attributes.inject({}) do |attrs_quoted, pair|
attrs_quoted[pair.first] = quote(pair.last, columns_hash[pair.first]) unless !include_primary_key && pair.first == self.class.primary_key attrs_quoted[pair.first] = quote(pair.last, columns_hash[pair.first]) unless !include_primary_key && pair.first == self.class.primary_key
attrs_quoted attrs_quoted
end end
attrs_quoted.delete_if { | key, value | !self.class.columns_hash.keys.include?(key) }
attrs_quoted.delete_if { |key, value| !self.class.columns_hash.keys.include?(key) }
end end


# Quote strings appropriately for SQL statements. # Quote strings appropriately for SQL statements.
Expand Down Expand Up @@ -1178,7 +1180,7 @@ def extract_callstack_for_multiparameter_attributes(pairs)


unless value.empty? unless value.empty?
attributes[attribute_name] << attributes[attribute_name] <<
[find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value)] [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
end end
end end


Expand All @@ -1203,10 +1205,10 @@ def quoted_column_names(attributes = attributes_with_quotes)
end end


def quote_columns(column_quoter, hash) def quote_columns(column_quoter, hash)
hash.inject({}) {|list, pair| hash.inject({}) do |list, pair|
list[column_quoter.quote_column_name(pair.first)] = pair.last list[column_quoter.quote_column_name(pair.first)] = pair.last
list list
} end
end end


def quoted_comma_pair_list(column_quoter, hash) def quoted_comma_pair_list(column_quoter, hash)
Expand All @@ -1231,4 +1233,4 @@ def has_yaml_encoding_header?(string)
string[0..3] == "--- " string[0..3] == "--- "
end end
end end
end end
21 changes: 21 additions & 0 deletions activerecord/test/associations_test.rb
Expand Up @@ -724,6 +724,27 @@ def test_habtm_adding_before_save
assert_equal 2, aridridel.projects(true).size assert_equal 2, aridridel.projects(true).size
end end


def test_habtm_adding_before_save_with_join_attributes
no_of_devels = Developer.count
no_of_projects = Project.count
now = Date.today
ken = Developer.new("name" => "Ken")
ken.projects.push_with_attributes( Project.find(1), :joined_on => now )
p = Project.new("name" => "Foomatic")
ken.projects.push_with_attributes( p, :joined_on => now )
assert ken.new_record?
assert p.new_record?
assert ken.save
assert !ken.new_record?
assert_equal no_of_devels+1, Developer.count
assert_equal no_of_projects+1, Project.count
assert_equal 2, ken.projects.size
assert_equal 2, ken.projects(true).size

kenReloaded = Developer.find_by_name 'Ken'
kenReloaded.projects.each { |prj| assert_equal(now.to_s, prj.joined_on.to_s) }
end

def test_build def test_build
devel = Developer.find(1) devel = Developer.find(1)
proj = devel.projects.build("name" => "Projekt") proj = devel.projects.build("name" => "Projekt")
Expand Down

0 comments on commit b29c01e

Please sign in to comment.