mwlang/flowmor_router

View on GitHub
lib/flowmor_router/router_classes.rb

Summary

Maintainability
A
3 hrs
Test Coverage
module FlowmorRouter
  #
  # class Post < ActiveRecord::Base
  #   acts_as_routable
  # end
  # 
  # route_prefix = ''
  # actor   = posts
  # route_name   = posts_title_me_silly
  # route_path   = posts_title_me_silly_path
  # route_url    = posts_title_me_silly_url
  # @post.path   = /posts/title-me-silly
  # @post.url    = http://example.com/posts/title-me-silly
  #
  #
  # class Post < ActiveRecord::Base
  #   acts_as_routable :ramblings
  # end
  # 
  # route_prefix = ''
  # actor   = ramblings
  # route_name   = ramblings_title_me_silly
  # route_path   = ramblings_title_me_silly_path
  # route_url    = ramblings_title_me_silly_url
  # @post.path   = /ramblings/title-me-silly
  # @post.url    = http://example.com/ramblings/title-me-silly
  #
  # 
  # class Post < ActiveRecord::Base
  #   acts_as_routable :ramblings, prefix: :posts
  # end
  # 
  # route_prefix = posts
  # actor   = ramblings
  # route_name   = posts_ramblings_title_me_silly
  # route_path   = posts_ramblings_title_me_silly_path
  # route_url    = posts_ramblings_title_me_silly_url
  # @post.path   = /posts/ramblings/title-me-silly
  # @post.url    = http://example.com/posts/ramblings/title-me-silly
  # 
  # 
  # class Post < ActiveRecord::Base
  #   acts_as_routable :ramblings, prefix: [:blog, :posts]
  # end
  # 
  # route_prefix = blog_posts
  # actor   = ramblings
  # route_name   = blog_posts_ramblings_title_me_silly
  # route_path   = blog_posts_ramblings_title_me_silly_path
  # route_url    = blog_posts_ramblings_title_me_silly_url
  # @post.path   = /blog/posts/ramblings/title-me-silly
  # @post.url    = http://example.com/blog/posts/ramblings/title-me-silly
  # 
  #
  # class Post < ActiveRecord::Base
  #   belongs_to :category
  #   acts_as_routable :ramblings, prefix: -> { category.name }
  # end
  # 
  # route_prefix = blog_posts
  # actor   = ramblings
  # route_name   = silly_category_ramblings_title_me_silly
  # route_path   = silly_category_ramblings_title_me_silly_path
  # route_url    = silly_category_ramblings_title_me_silly_url
  # @post.path   = /silly-category/posts/ramblings/title-me-silly
  # @post.url    = http://example.com/silly-category/posts/ramblings/title-me-silly
  # 
  #
  # class Post < ActiveRecord::Base
  #   acts_as_routable
  #   acts_as_routable :archive, prefix: [:posts]
  # end
  # 
  # route_prefix = ''
  # actor   = posts
  # route_name   = posts_title_me_silly
  # route_path   = posts_title_me_silly_path
  # route_url    = posts_title_me_silly_url
  # @post.path   = /posts/title-me-silly
  # @post.url    = http://example.com/posts/title-me-silly
  # 
  # AND
  #
  # route_prefix             = posts
  # actor                    = archive
  # route_name               = posts_archive_title_me_silly
  # route_path               = posts_archive_title_me_silly_path
  # route_url                = posts_archive_title_me_silly_url
  # @post.posts_archive_path = /posts/archive/title-me-silly
  # @post.posts_archive_url  = http://example.com/posts/archive/title-me-silly
  # 
  class RouterClasses
    
    @@router_classes = []
    
    def self.register actor, model, options
      router_class = RouterClasses.new(actor, model, options)
      @@router_classes << router_class
      return router_class
    end
    
    def self.unregister model
      @@router_classes.select{ |s| s.model == model }.each do |item|
        @@router_classes.delete(item)
      end
    end
    
    def self.router_classes
      @@router_classes
    end
    
    def self.each &block
      @@router_classes.each{ |rc| yield rc }
    end
    
    attr_reader :actor, :model
    attr_reader :prefix, :suffix, :controller_action, :no_conflict
    attr_reader :name_field_attribute, :title_field_attribute
    attr_reader :custom_route, :custom_name, :delimiter

    def initialize actor, model, options
      @actor = actor.to_s
      @model = model
      
      @name_field_attribute = :name
      @title_field_attribute = :title

      previous = @@router_classes.detect{|rc| rc.model == @model}
      raise DuplicateRouterActors.new("duplicate actors registered!") if previous.try(:route_route) == actor
      @no_conflict = !!previous
      
      @controller_action = options[:controller_action] || "#{actor}#show" 
      @name_field_attribute = options[:name_field] || :name
      @title_field_attribute = options[:title_field] || :title
      @custom_route = options[:route]
      @custom_name = options[:name]
      @prefix = options[:prefix] if options[:prefix]
      @suffix = options[:suffix] if options[:suffix]
      @delimiter = options[:delimiter] || "-"
    end

    def default_url_options
      { :host => Thread.current[:host], :port => Thread.current[:port] }
    end

    def no_conflict_model_name
      return unless no_conflict
      @model.name.underscore.downcase.split("/").last.pluralize
    end
    
    def route_base_name
      @route_base_name ||= [no_conflict_model_name, actor].compact.join("_")
    end
    
    def route_base_path
      @route_base_path ||= "/" + actor
    end
    
    def scope_name
      "flowmor_#{route_base_name}_routable"
    end
    
    def routable
      @model.send scope_name
    end
    
    def to_param value
      return unless value
      value.downcase.gsub(/[^\w\s\d\_\-]/,'').gsub(/\s\s+/,' ').gsub(/[^\w\d]/, delimiter)
    end

    def named_instance method = "router_class"
      "flowmor_#{route_base_name}_#{method}"
    end
    
    def name_field record
      record.send(name_field_attribute)
    end
    
    def title_field record
      record.send(title_field_attribute)
    end

    def name record
      name = record.send(custom_name) if custom_name
      name ||= name_field(record) || to_param(title_field(record))
      raise UnroutableRecord if name.to_s.strip.blank?
      return name
    end
    
    def route_suffix record
      return if @suffix.nil?
      Array(@suffix.is_a?(Proc) ? record.send(suffix.call) : @suffix)
    end
    
    def route_name_suffix record
      return if @suffix.nil?
      route_suffix(record).join("_") + "_"
    end
    
    def route_path_suffix record
      return if @suffix.nil?
      "/" + route_suffix(record).join("/")
    end
    
    def route_prefix record
      return if @prefix.nil?
      Array(@prefix.is_a?(Proc) ? record.send(prefix.call) : @prefix)
    end
    
    def route_name_prefix record
      return if @prefix.nil?
      route_prefix(record).join("_") + "_"
    end
    
    def route_path_prefix record
      return if @prefix.nil?
      "/" + route_prefix(record).join("/")
    end
    
    def verb_prefix verb
      return "#{verb}_" if verb
      "#{controller_action.keys.first}_" if controller_action.is_a? Hash
    end
    
    def route_name record, verb=nil
      "#{verb_prefix(verb)}#{route_name_prefix(record)}#{route_base_name}_#{route_name_suffix(record)}#{name(record)}".underscore.parameterize("_")
    end

    def route_path record
      if custom_route
        record.send(custom_route)
      else
        "#{route_path_prefix(record)}#{route_base_path}#{route_path_suffix(record)}/#{name(record)}"
      end
    end

    def route_name_method_name
      no_conflict ? "#{route_base_name}_route_name" : "route_name"
    end
    
    def url_method_name
      no_conflict ? "#{actor}_url" : "url"
    end
    
    def path_method_name
      no_conflict ? "#{actor}_path" : "path"
    end
    
    def path record
      begin
        Rails.application.routes.url_helpers.send("#{route_name(record)}_path")
      rescue NoMethodError
        raise FlowmorRouter::UnroutedRecord.new("[#{route_name(record)}] #{record.inspect} was not routed.")
      end
    end
    
    def url record
      begin
        Rails.application.routes.url_helpers.send("#{route_name(record)}_url", default_url_options)
      rescue NoMethodError
        raise FlowmorRouter::UnroutedRecord.new("[#{route_name(record)}] #{record.inspect} was not routed.")
      end
    end
  end
end