Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

186 lines (158 sloc) 6.888 kb
module ActiveRecord
class CompositeKeyError < StandardError #:nodoc:
end
class Base
INVALID_FOR_COMPOSITE_KEYS = 'Not appropriate for composite primary keys'
NOT_IMPLEMENTED_YET = 'Not implemented for composite primary keys yet'
class << self
def set_primary_keys(*keys)
keys = keys.first if keys.first.is_a?(Array)
if keys.length == 1
set_primary_key(keys.first)
return
end
cattr_accessor :primary_keys
self.primary_keys = keys.map { |k| k.to_sym }
class_eval <<-EOV
extend CompositeClassMethods
include CompositeInstanceMethods
include CompositePrimaryKeys::ActiveRecord::AssociationPreload
EOV
class << unscoped
include CompositePrimaryKeys::ActiveRecord::FinderMethods::InstanceMethods
include CompositePrimaryKeys::ActiveRecord::Relation::InstanceMethods
end
end
def composite?
false
end
end
def composite?
self.class.composite?
end
def [](attr_name)
# CPK
if attr_name.is_a?(String) and attr_name != attr_name.split(CompositePrimaryKeys::ID_SEP).first
attr_name = attr_name.split(CompositePrimaryKeys::ID_SEP)
end
# CPK
if attr_name.is_a?(Array)
values = attr_name.map {|name| read_attribute(name)}
CompositePrimaryKeys::CompositeKeys.new(values)
else
read_attribute(attr_name)
end
end
def []=(attr_name, value)
# CPK
if attr_name.is_a?(String) and attr_name != attr_name.split(CompositePrimaryKeys::ID_SEP).first
attr_name = attr_name.split(CompositePrimaryKeys::ID_SEP)
end
if attr_name.is_a? Array
unless value.length == attr_name.length
raise "Number of attr_names and values do not match"
end
[attr_name, value].transpose.map {|name,val| write_attribute(name, val)}
value
else
write_attribute(attr_name, value)
end
end
module CompositeClassMethods
def primary_key
primary_keys
end
def primary_key=(keys)
primary_keys = keys
end
def composite?
true
end
#ids_to_s([[1,2],[7,3]]) -> "(1,2),(7,3)"
#ids_to_s([[1,2],[7,3]], ',', ';') -> "1,2;7,3"
def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')')
many_ids.map {|ids| "#{left_bracket}#{CompositePrimaryKeys::CompositeKeys.new(ids)}#{right_bracket}"}.join(list_sep)
end
end
module CompositeInstanceMethods
# A model instance's primary keys is always available as model.ids
# whether you name it the default 'id' or set it to something else.
def id
attr_names = self.class.primary_keys
::CompositePrimaryKeys::CompositeKeys.new(attr_names.map { |attr_name| read_attribute(attr_name) })
end
alias_method :ids, :id
def ids_hash
self.class.primary_key.zip(ids).inject(Hash.new) do |hash, (key, value)|
hash[key] = value
hash
end
end
def to_param
id.join(CompositePrimaryKeys::ID_SEP)
end
def id_before_type_cast #:nodoc:
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::NOT_IMPLEMENTED_YET
end
def quoted_id #:nodoc:
[self.class.primary_keys, ids].
transpose.
map {|attr_name,id| quote_value(id, column_for_attribute(attr_name))}.
to_composite_ids
end
# Sets the primary ID.
def id=(ids)
ids = ids.split(CompositePrimaryKeys::ID_SEP) if ids.is_a?(String)
ids.flatten!
unless ids.is_a?(Array) and ids.length == self.class.primary_keys.length
raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"
end
[primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}
id
end
# Cloned objects have no id assigned and are treated as new records. Note that this is a "shallow" clone
# as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
# application specific and is therefore left to the application to implement according to its need.
def initialize_copy(other)
# Think the assertion which fails if the after_initialize callback goes at the end of the method is wrong. The
# deleted clone method called new which therefore called the after_initialize callback. It then went on to copy
# over the attributes. But if it's copying the attributes afterwards then it hasn't finished initializing right?
# For example in the test suite the topic model's after_initialize method sets the author_email_address to
# test@test.com. I would have thought this would mean that all cloned models would have an author email address
# of test@test.com. However the test_clone test method seems to test that this is not the case. As a result the
# after_initialize callback has to be run *before* the copying of the atrributes rather than afterwards in order
# for all tests to pass. This makes no sense to me.
callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
# CPK
#cloned_attributes.delete(self.class.primary_key)
self.class.primary_key.each {|key| cloned_attributes.delete(key.to_s)}
@attributes = cloned_attributes
clear_aggregation_cache
@attributes_cache = {}
@new_record = true
ensure_proper_type
if scope = self.class.send(:current_scoped_methods)
create_with = scope.scope_for_create
create_with.each { |att,value| self.send("#{att}=", value) } if create_with
end
end
def destroy
if persisted?
# CPK
# self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
self.class.unscoped.where(ids_hash).delete_all
end
@destroyed = true
freeze
end
def update(attribute_names = @attributes.keys)
attributes_with_values = arel_attributes_values(false, false, attribute_names)
return 0 if attributes_with_values.empty?
# CPK
# self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
self.class.unscoped.where(ids_hash).arel.update(attributes_with_values)
end
end
end
end
Jump to Line
Something went wrong with that request. Please try again.