Skip to content

Instantly share code, notes, and snippets.

@dwg
Created May 12, 2009 11:17
Show Gist options
  • Select an option

  • Save dwg/110422 to your computer and use it in GitHub Desktop.

Select an option

Save dwg/110422 to your computer and use it in GitHub Desktop.
Adds :primary_key option to associations for Rails 2.1.1
module AssociationPrimaryKey
module ActiveRecord
private
def create_has_many_reflection(association_id, options, &extension)
options.assert_valid_keys(
:class_name, :table_name, :foreign_key, :primary_key,
:dependent,
:select, :conditions, :include, :order, :group, :limit, :offset,
:as, :through, :source, :source_type,
:uniq,
:finder_sql, :counter_sql,
:before_add, :after_add, :before_remove, :after_remove,
:extend, :readonly,
:validate
)
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
create_reflection(:has_many, association_id, options, self)
end
def create_has_one_reflection(association_id, options)
options.assert_valid_keys(
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate, :primary_key
)
create_reflection(:has_one, association_id, options, self)
end
def has_one(association_id, options = {})
if options[:through]
reflection = create_has_one_through_reflection(association_id, options)
association_accessor_methods(reflection, ::ActiveRecord::Associations::HasOneThroughAssociation)
else
reflection = create_has_one_reflection(association_id, options)
ivar = "@#{reflection.name}"
method_name = "has_one_after_save_for_#{reflection.name}".to_sym
define_method(method_name) do
association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
primary_key = reflection.options[:primary_key] || :id
if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != send(primary_key))
association["#{reflection.primary_key_name}"] = send(primary_key)
association.save(true)
end
end
after_save method_name
add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
association_accessor_methods(reflection, ::ActiveRecord::Associations::HasOneAssociation)
association_constructor_method(:build, reflection, ::ActiveRecord::Associations::HasOneAssociation)
association_constructor_method(:create, reflection, ::ActiveRecord::Associations::HasOneAssociation)
configure_dependency_for_has_one(reflection)
end
end
end
module HasMany
def self.included(base)
base.class_eval do
alias_method_chain :delete_records, :primary_key
alias_method_chain :construct_sql, :primary_key
end
end
def delete_records_with_primary_key(records)
case @reflection.options[:dependent]
when :destroy
records.each(&:destroy)
when :delete_all
@reflection.klass.delete(records.map(&:id))
else
ids = quoted_record_ids(records)
@reflection.klass.update_all(
"#{@reflection.primary_key_name} = NULL",
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
)
end
end
def construct_sql_with_primary_key
puts 'called construct_sql'
case
when @reflection.options[:finder_sql]
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
when @reflection.options[:as]
@finder_sql =
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
@finder_sql << " AND (#{conditions})" if conditions
else
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
@finder_sql << " AND (#{conditions})" if conditions
end
if @reflection.options[:counter_sql]
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
elsif @reflection.options[:finder_sql]
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
else
@counter_sql = @finder_sql
end
end
end
module HasOne
def self.included(base)
base.class_eval do
alias_method_chain :construct_sql, :primary_key
end
end
protected
def construct_sql_with_primary_key
case
when @reflection.options[:as]
@finder_sql =
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
else
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
end
@finder_sql << " AND (#{conditions})" if conditions
end
end
module HasAndBelongsToMany
def self.included(base)
base.class_eval do
alias_method_chain :insert_record, :primary_key
alias_method_chain :delete_records, :primary_key
alias_method_chain :construct_sql, :primary_key
end
end
protected
def insert_record_with_primary_key(record, force=true)
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
return true
end
def delete_records_with_primary_key(records)
if sql = @reflection.options[:delete_sql]
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
else
ids = quoted_record_ids(records)
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})"
@owner.connection.delete(sql)
end
end
def construct_sql_with_primary_key
if @reflection.options[:finder_sql]
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
else
@finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
@finder_sql << " AND (#{conditions})" if conditions
end
@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}"
end
end
module Proxy
def self.included(base)
base.class_eval do
alias_method_chain :set_belongs_to_association_for, :primary_key
end
end
protected
def owner_quoted_id
puts 'called owner_quoted_id'
if @reflection.options[:primary_key]
puts "primary key: #{@reflection.options[:primary_key]}"
puts "primary value: #{@owner.send(@reflection.options[:primary_key])}"
quote_value(@owner.send(@reflection.options[:primary_key]))
else
@owner.quoted_id
end
end
def set_belongs_to_association_for_with_primary_key(record)
if @reflection.options[:as]
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
else
unless @owner.new_record?
primary_key = @reflection.options[:primary_key] || :id
record[@reflection.primary_key_name] = @owner.send(primary_key)
end
end
end
end
end
ActiveRecord::Base.send :extend, AssociationPrimaryKey::ActiveRecord
ActiveRecord::Associations::AssociationProxy.send :include, AssociationPrimaryKey::Proxy
ActiveRecord::Associations::HasManyAssociation.send :include, AssociationPrimaryKey::HasMany
ActiveRecord::Associations::HasOneAssociation.send :include, AssociationPrimaryKey::HasOne
ActiveRecord::Associations::HasAndBelongsToManyAssociation.send :include, AssociationPrimaryKey::HasAndBelongsToMany
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment