Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

178 lines (151 sloc) 6.307 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
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
def relation #:nodoc:
@relation ||= begin
result = Relation.new(self, arel_table)
# CPK
class << result
include CompositePrimaryKeys::ActiveRecord::FinderMethods::InstanceMethods
include CompositePrimaryKeys::ActiveRecord::Relation::InstanceMethods
end
result
end
finder_needs_type_condition? ? @relation.where(type_condition) : @relation
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 id_before_type_cast
self.class.primary_keys.map do |key|
self.send("#{key.to_s}_before_type_cast")
end
end
def quoted_id #:nodoc:
[self.class.primary_keys, ids].
transpose.
map {|attr_name,id| quote_value(id, column_for_attribute(attr_name))}
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
def ==(comparison_object)
ids.is_a?(Array) ? super(comparison_object) && ids.all? {|id| id.present?} : super(comparison_object)
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
end
end
end
Jump to Line
Something went wrong with that request. Please try again.