module MassiveRecord module ORM module Relations # # Parent class for all proxies sitting between records. # It's responsibility is to transparently load and forward # method calls to it's proxy_target. Iy may also do some small maintenance # work like setting foreign key in proxy_owner object etc. That kind of # functionality is likely to be implemented in one of it's more # specific sub class proxies. # class Proxy instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a|to_s|extend|equal\?)$|^__|^respond_to|^should|^instance_variable_/ } attr_reader :proxy_target attr_accessor :proxy_owner, :metadata delegate :class_name, :proxy_target_class, :represents_a_collection?, :to => :metadata def initialize(options = {}) options.to_options! self.metadata = options[:metadata] self.proxy_owner = options[:proxy_owner] self.proxy_target = options[:proxy_target] reset if proxy_target.nil? end # # The proxy_target of a relation is the record # the proxy_owner references. For instance, # # class Person # references_one :car # end # # The proxy_owner is a record of class person, the proxy_target will be the car. # def proxy_target=(proxy_target) @proxy_target = proxy_target loaded! unless @proxy_target.nil? end # # Returns the proxy_target. Loads it, if it's not there. # Returns nil if for some reason proxy_target could not be found. # def load_proxy_target(options = {}) self.proxy_target = find_proxy_target_or_find_with_proc(options) if find_proxy_target? proxy_target rescue RecordNotFound reset end def reload reset load_proxy_target end def reset(force = true) @loaded = @proxy_target = nil end def replace(proxy_target) if proxy_target.nil? reset(true) else raise_if_type_mismatch(proxy_target) self.proxy_target = proxy_target end end def inspect load_proxy_target.inspect end # # If the proxy is loaded it has a proxy_target # def loaded? !!@loaded end def loaded! @loaded = true end def respond_to?(*args) super || (load_proxy_target && proxy_target.respond_to?(*args)) end def method_missing(method, *args, &block) return proxy_target.send(method, *args, &block) if load_proxy_target && proxy_target.respond_to?(method) super rescue NoMethodError => e raise e, e.message.sub(/ for #<.*$/, " via proxy for #{proxy_target}") end def blank? load_proxy_target.blank? end def is_a?(type) load_proxy_target.is_a?(type) end # Strange.. Without Rails, to_param goes through method_missing, # With Rails it seems like the proxy answered to to_param, which # kinda was not what I wanted. def to_param # :nodoc: proxy_target.try :to_param end protected def find_proxy_target_or_find_with_proc(options = {}) find_with_proc? ? find_proxy_target_with_proc(options) : find_proxy_target(options) end # # "Abstract" method used to find proxy_target for the proxy. # Implement in subclasses. It is not called when the meta # data contains a find_with proc; in that case find_proxy_target_with_proc # is used instead # def find_proxy_target(options = {}) end # # Gives sub classes a place to hook into when we are # gonna find proxy_target(s) by the proc. For instance, the # references_many proxy ensures that the result of proc # is put inside of an array. # def find_proxy_target_with_proc(options = {}, &block) metadata.find_with.call(proxy_owner, options, &block) end # # When are we supposed to find a proxy_target? Find a proxy_target is done # through load_proxy_target. # def find_proxy_target? !loaded? && can_find_proxy_target? end # # Override this to controll when a proxy_target may be found. # def can_find_proxy_target? find_with_proc? end def update_foreign_key_fields_in_proxy_owner? !proxy_owner.destroyed? end # # Are we supposed to find proxy_target with a proc? # def find_with_proc? !metadata.find_with.nil? && metadata.find_with.respond_to?(:call) end def raise_if_type_mismatch(record) unless record.is_a? proxy_target_class message = "#{class_name}(##{proxy_target_class.object_id}) expected, got #{record.class}(##{record.class.object_id})" raise RelationTypeMismatch.new(message) end end end end end end