module Stringex module ActsAsUrl module Adapter class Base attr_accessor :base_url, :callback_options, :configuration, :instance, :klass, :settings def initialize(configuration) ensure_loadable self.configuration = configuration self.settings = configuration.settings end def create_callbacks!(klass) self.klass = klass self.callback_options = {} create_method_to_callback create_callback end def ensure_unique_url!(instance) @url_owners = nil self.instance = instance handle_url! handle_blacklisted_url! handle_duplicate_url! unless settings.allow_duplicates end def initialize_urls!(klass) self.klass = klass klass_previous_instances do |instance| ensure_unique_url_for! instance end end def url_attribute(instance) # Retrieve from database record if there are errors on attribute_to_urlify if !is_new?(instance) && is_present?(instance.errors[settings.attribute_to_urlify]) self.instance = instance read_attribute instance_from_db, settings.url_attribute else read_attribute instance, settings.url_attribute end end def self.ensure_loadable raise "The #{self} adapter cannot be loaded" unless loadable? Stringex::ActsAsUrl::Adapter.add_loaded_adapter self end def self.loadable? orm_class rescue NameError false end private def add_new_record_url_owner_conditions return if is_new?(instance) @url_owner_conditions.first << " and #{primary_key} != ?" @url_owner_conditions << instance.id end def add_scoped_url_owner_conditions [settings.scope_for_url].flatten.compact.each do |scope| @url_owner_conditions.first << " and #{scope} = ?" @url_owner_conditions << instance.send(scope) end end def create_callback klass.send klass_callback_method, :ensure_unique_url, callback_options end def klass_callback_method settings.sync_url ? klass_sync_url_callback_method : klass_non_sync_url_callback_method end def klass_sync_url_callback_method configuration.settings.callback_method end def klass_non_sync_url_callback_method case configuration.settings.callback_method when :before_save :before_create else # :before_validation callback_options[:on] = :create configuration.settings.callback_method end end def create_method_to_callback klass.class_eval <<-"END" def #{settings.url_attribute} acts_as_url_configuration.adapter.url_attribute self end END end def duplicate_for_base_url(n) "#{base_url}#{settings.duplicate_count_separator}#{n}" end def ensure_loadable self.class.ensure_loadable end # NOTE: The instance here is not the cached instance but a block variable # passed from klass_previous_instances, just to be clear def ensure_unique_url_for!(instance) instance.send :ensure_unique_url instance.save end def get_base_url_owner_conditions @url_owner_conditions = ["#{settings.url_attribute} LIKE ?", base_url + '%'] end def handle_duplicate_url! return if url_owners.none?{|owner| url_attribute_for(owner) == base_url} n = 1 while url_owners.any?{|owner| url_attribute_for(owner) == duplicate_for_base_url(n)} n = n.succ end write_url_attribute duplicate_for_base_url(n) end def handle_url! self.base_url = instance.send(settings.url_attribute) modify_base_url if is_blank?(base_url) || !settings.only_when_blank write_url_attribute base_url end def handle_blacklisted_url! return unless settings.blacklist.to_set.include?(base_url) self.base_url = settings.blacklist_policy.call(instance, base_url) write_url_attribute base_url end def instance_from_db instance.class.find(instance.id) end def is_blank?(object) object.blank? end def is_new?(object) object.new_record? end def is_present?(object) object.present? end def loadable? self.class.loadable? end def modify_base_url root = instance.send(settings.attribute_to_urlify).to_s self.base_url = root.to_url(configuration.string_extensions_settings) end def orm_class self.class.orm_class end def primary_key instance.class.primary_key end def read_attribute(instance, attribute) instance.read_attribute attribute end def url_attribute_for(object) object.send settings.url_attribute end def url_owner_conditions get_base_url_owner_conditions add_new_record_url_owner_conditions add_scoped_url_owner_conditions @url_owner_conditions end def url_owners @url_owners ||= url_owners_class.unscoped.where(url_owner_conditions).to_a end def url_owners_class return instance.class unless settings.enforce_uniqueness_on_sti_base_class klass = instance.class while klass.superclass < orm_class klass = klass.superclass end klass end def write_attribute(instance, attribute, value) instance.send :write_attribute, attribute, value end def write_url_attribute(value) write_attribute instance, settings.url_attribute, value end end end end end