Skip to content

Commit

Permalink
has_and_belongs_to_many association support
Browse files Browse the repository at this point in the history
  • Loading branch information
Luca Guidi committed Nov 12, 2008
1 parent 4369941 commit d6281f4
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 34 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
@@ -1,3 +1,5 @@
* has_and_belongs_to_many association support

* Fix memory leak issue in cached_associations

* DRYed-up belongs_to definition
Expand Down
30 changes: 28 additions & 2 deletions lib/activerecord/lib/active_record/associations.rb
Expand Up @@ -2,6 +2,7 @@
require File.dirname(__FILE__) + '/associations/association_proxy'
require File.dirname(__FILE__) + '/associations/association_collection'
require File.dirname(__FILE__) + '/associations/has_many_association'
require File.dirname(__FILE__) + '/associations/has_and_belongs_to_many_association'

module ActiveRecord
module Associations
Expand Down Expand Up @@ -119,6 +120,29 @@ def collection_accessor_methods(reflection, association_proxy_class, options, wr

valid_keys_for_has_many_association << :cached
valid_keys_for_belongs_to_association << :cached
# TODO uncomment when Rails 2.2.1 comes out
# valid_keys_for_has_and_belongs_to_many_association << :cached

# TODO remove when Rails 2.2.1 comes out
def create_has_and_belongs_to_many_reflection(association_id, options, &extension) #:nodoc:
options.assert_valid_keys(
:class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
:select, :conditions, :include, :order, :group, :limit, :offset,
:uniq,
:finder_sql, :delete_sql, :insert_sql,
:before_add, :after_add, :before_remove, :after_remove,
:extend, :readonly,
:validate, :cached
)

options[:extend] = create_extension_modules(association_id, extension, options[:extend])

reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)

reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))

reflection
end

def add_has_many_cache_callbacks
method_name = :has_many_after_save_cache_expire
Expand All @@ -128,7 +152,7 @@ def add_has_many_cache_callbacks
return unless self[:updated_at]

self.class.reflections.each do |name, reflection|
cache_delete(reflection) if reflection.options[:cached]
expire_cache_for(reflection.class_name)
end
end
after_save method_name
Expand All @@ -140,7 +164,9 @@ def add_belongs_to_cache_callbacks(reflection_name)
return if respond_to? after_save_method_name

define_method(after_save_method_name) do
send(reflection_name).expire_cache_for(self.class.name)
returning owner = send(reflection_name) do
owner.expire_cache_for(self.class.name) unless owner.blank?
end
end

alias_method after_destroy_method_name, after_save_method_name
Expand Down
Expand Up @@ -109,6 +109,19 @@ def clear
self
end

def destroy_all #:nodoc:
transaction do
each { |record| record.destroy }
end

reset_target!

# TODO it should be achievable via callbacks
if @reflection.options[:cached] && @reflection.macro == :has_and_belongs_to_many
@owner.send(:cache_write, @reflection, self)
end
end

# Returns the size of the collection by executing a SELECT COUNT(*)
# query if the collection hasn't been loaded, and calling
# <tt>collection.size</tt> if it has.
Expand Down
@@ -0,0 +1,46 @@
module ActiveRecord
module Associations
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
def insert_record(record, force=true) #:nodoc:
if record.new_record?
if force
record.save!
else
return false unless record.save
end
end

if @reflection.options[:insert_sql]
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
else
columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")

attributes = columns.inject({}) do |attrs, column|
case column.name.to_s
when @reflection.primary_key_name.to_s
attrs[column.name] = owner_quoted_id
when @reflection.association_foreign_key.to_s
attrs[column.name] = record.quoted_id
else
if record.has_attribute?(column.name)
value = @owner.send(:quote_value, record[column.name], column)
attrs[column.name] = value unless value.nil?
end
end
attrs
end

sql =
"INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
"VALUES (#{attributes.values.join(', ')})"

@owner.connection.insert(sql)
end

@owner.send(:cache_delete, @reflection) if @reflection.options[:cached]

return true
end
end
end
end
3 changes: 2 additions & 1 deletion tasks/cached_models_tasks.rake
Expand Up @@ -55,6 +55,7 @@ namespace :cached_models do
t.string :title
t.text :text
t.datetime :published_at
t.integer :rating, :default => 0

t.timestamps
end
Expand All @@ -65,7 +66,7 @@ namespace :cached_models do
t.timestamps
end

create_table :categories_posts, :force => true do |t|
create_table :categories_posts, :force => true, :id => false do |t|
t.integer :category_id
t.integer :post_id
end
Expand Down

0 comments on commit d6281f4

Please sign in to comment.