lib/ripple/associations.rb in ripple-0.7.1 vs lib/ripple/associations.rb in ripple-0.8.0.beta
- old
+ new
@@ -20,13 +20,16 @@
autoload :Proxy
autoload :One
autoload :Many
autoload :Embedded
+ autoload :Linked
autoload :Instantiators
autoload :OneEmbeddedProxy
autoload :ManyEmbeddedProxy
+ autoload :OneLinkedProxy
+ autoload :ManyLinkedProxy
module ClassMethods
# @private
def inherited(subclass)
super
@@ -64,11 +67,11 @@
define_method("#{name}=") do |value|
get_proxy(association).replace(value)
value
end
- if association.one?
+ unless association.many?
define_method("#{name}?") do
get_proxy(association).present?
end
end
end
@@ -96,19 +99,21 @@
end
end
end
class Association
+ include Ripple::Translation
attr_reader :type, :name, :options
# association options :using, :class_name, :class, :extend,
# options that may be added :validate
def initialize(type, name, options={})
@type, @name, @options = type, name, options.to_options
end
+ # @return String The class name of the associated object(s)
def class_name
@class_name ||= case
when @options[:class_name]
@options[:class_name]
when @options[:class]
@@ -118,40 +123,102 @@
else
@name.to_s.camelize
end
end
+ # @return [Class] The class of the associated object(s)
def klass
@klass ||= options[:class] || class_name.constantize
end
+ # @return [true,false] Is the cardinality of the association > 1
def many?
@type == :many
end
+ # @return [true,false] Is the cardinality of the association == 1
def one?
@type == :one
end
+ # @return [true,false] Is the associated class an EmbeddedDocument
def embeddable?
klass.embeddable?
end
+ # TODO: Polymorphic not supported
+ # @return [true,false] Does the association support more than one associated class
def polymorphic?
false
end
+ # @return [true,false] Does the association use links
+ def linked?
+ using == :linked
+ end
+
+ # @return [String] the instance variable in the owner where the association will be stored
def ivar
"@_#{name}"
end
+ # @return [Class] the association proxy class
def proxy_class
@proxy_class ||= proxy_class_name.constantize
end
+ # @return [String] the class name of the association proxy
def proxy_class_name
- @using ||= options[:using] || (embeddable? ? :embedded : :link)
- klass_name = (many? ? 'Many' : 'One') + @using.to_s.camelize + ('Polymorphic' if polymorphic?).to_s + 'Proxy'
+ klass_name = (many? ? 'Many' : 'One') + using.to_s.camelize + ('Polymorphic' if polymorphic?).to_s + 'Proxy'
"Ripple::Associations::#{klass_name}"
+ end
+
+ # @return [Proc] a filter proc to be used with Enumerable#select for collecting links that belong to this association (only when #linked? is true)
+ def link_filter
+ linked? ? lambda {|link| link.tag == link_tag } : lambda {|_| false }
+ end
+
+ # @return [String,nil] when #linked? is true, the tag for outgoing links
+ def link_tag
+ linked? ? Array(link_spec).first.tag : nil
+ end
+
+ # @return [Riak::WalkSpec] when #linked? is true, a specification for which links to follow to retrieve the associated documents
+ def link_spec
+ # TODO: support transitive linked associations
+ if linked?
+ tag = name.to_s
+ bucket = polymorphic? ? '_' : klass.bucket_name
+ Riak::WalkSpec.new(:tag => tag, :bucket => bucket)
+ else
+ nil
+ end
+ end
+
+ # @return [Symbol] which method is used for representing the association - currently only supports :embedded and :linked
+ def using
+ @using ||= options[:using] || (embeddable? ? :embedded : :linked)
+ end
+
+ # @raise [ArgumentError] if the value does not match the class of the association
+ def verify_type!(value, owner)
+ unless type_matches?(value)
+ raise ArgumentError.new(t('invalid_association_value',
+ :name => name,
+ :owner => owner.inspect,
+ :klass => polymorphic? ? "<polymorphic>" : klass.name,
+ :value => value.inspect))
+ end
+ end
+
+ def type_matches?(value)
+ case
+ when polymorphic?
+ one? || Array === value
+ when many?
+ Array === value && value.all? {|d| (embeddable? && Hash === d) || klass === d }
+ when one?
+ value.nil? || (embeddable? && Hash === value) || klass === value
+ end
end
end
end