require 'active_support/core_ext/array/extract_options' module MassiveRecord module ORM module Relations # Raised when an invalid start option is given to a find_in_batches class InvalidStartsWithOption < MassiveRecordError end # # The master of metadata related to a relation. For instance; # references_one :employee, :foreign_key => "person_id", :class_name => "Person" # class Metadata attr_writer :foreign_key, :store_in, :class_name, :name, :relation_type, :polymorphic, :inverse_of attr_accessor :find_with, :owner_class attr_reader :records_starts_from def initialize(name, options = {}) options.to_options! self.name = name self.foreign_key = options[:foreign_key] if options.has_key? :store_in self.store_in = options[:store_in] elsif options.has_key? :store_foreign_key_in self.store_foreign_key_in = options[:store_foreign_key_in] end self.class_name = options[:class_name] self.find_with = options[:find_with] self.records_starts_from = options[:records_starts_from] if options[:records_starts_from] self.polymorphic = options[:polymorphic] self.inverse_of = options[:inverse_of] end def name @name.to_s if @name end def relation_type if @relation_type relation_type = @relation_type.to_s relation_type += "_polymorphic" if polymorphic? relation_type end end def foreign_key (@foreign_key || calculate_foreign_key).to_s end def foreign_key_setter foreign_key+'=' end def polymorphic_type_column type_column = foreign_key.gsub(/_id$/, '') type_column + "_type" end def polymorphic_type_column_setter polymorphic_type_column+'=' end def class_name (@class_name || calculate_class_name).to_s end def inverse_of (@inverse_of || calculate_inverse_of).to_s end def proxy_target_class class_name.constantize end def store_in return @store_in.to_s if @store_in @store_in = name if embedded? end def store_foreign_key_in ActiveSupport::Deprecation.warn("store_foreign_key_in is deprecated. Use store_in instead!") store_in end def store_foreign_key_in=(column_family) ActiveSupport::Deprecation.warn("store_foreign_key_in is deprecated. Use store_in instead!") self.store_in = column_family end def persisting_foreign_key? !embedded? && !!store_in && !records_starts_from end def polymorphic !!@polymorphic end alias polymorphic? polymorphic def embedded relation_type == "embeds_many" end alias embedded? embedded def embedded_in relation_type == "embedded_in" || relation_type == "embedded_in_polymorphic" end alias embedded_in? embedded_in def new_relation_proxy(proxy_owner) proxy_class_name.constantize.new(:proxy_owner => proxy_owner, :metadata => self) end def ==(other) other.instance_of?(self.class) && other.hash == hash end alias_method :eql?, :== def hash name.hash end def represents_a_collection? %w(references_many embeds_many).include? relation_type end # # Sets a method which we should ask for how to find where # related records starts with. Method injects a find_with # Proc which finds are made through. # # That proc takes different options as it sends on to the # receiving finder method on target class. It also takes a # block which is sent on to the finder method. # def records_starts_from=(method) @records_starts_from = method if @records_starts_from self.find_with = Proc.new do |proxy_owner, options = {}, &block| options = MassiveRecord::Adapters::Thrift::Table.warn_and_change_deprecated_finder_options(options) finder_method = options.delete(:finder_method) || :all if ids_starts_with = proxy_owner.send(records_starts_from) if options[:starts_with] if options[:starts_with].starts_with?(ids_starts_with) ids_starts_with = options[:starts_with] else raise InvalidStartsWithOption.new("The starts with option: #{options[:starts_with]} must begin with: #{ids_starts_with}.") end end proxy_target_class.send(finder_method, options.merge({:starts_with => ids_starts_with}), &block) end end else self.find_with = nil end end private def calculate_class_name name.to_s.classify end def calculate_inverse_of raise "Can't return inverse of without it being explicitly set or without an owner_class" unless owner_class if represents_a_collection? owner_class.to_s.demodulize.underscore.singularize else owner_class.to_s.demodulize.underscore.pluralize end end def calculate_foreign_key if represents_a_collection? name.downcase.singularize + "_ids" else name.downcase + "_id" end end def proxy_class_name "MassiveRecord::ORM::Relations::Proxy::"+relation_type.classify end end end end end