Instantly share code, notes, and snippets.
Last active
December 20, 2015 18:39
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save tlux/6177072 to your computer and use it in GitHub Desktop.
Breadcrumbs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class ApplicationController < ActionController::Base | |
| include BreadcrumbedController | |
| before_action :set_controller_context | |
| private | |
| def set_controller_context | |
| RequestStore.store[:controller_context] = self | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Breadcrumb | |
| attr_reader :breadcrumbs, :caption, :destination, :path, :url | |
| private :breadcrumbs | |
| def initialize(breadcrumbs, caption, destination) | |
| raise ArgumentError, 'no breadcrumb collection specified' if breadcrumbs.nil? | |
| raise ArgumentError, 'caption is blank' if caption.blank? | |
| raise ArgumentError, 'no destination specified' if destination.nil? | |
| @breadcrumbs = breadcrumbs | |
| @caption = caption.to_s | |
| @destination = destination | |
| if @destination.is_a? String | |
| @path = @url = @destination | |
| else | |
| @path = breadcrumbs.controller_context.polymorphic_path(destination) | |
| @url = breadcrumbs.controller_context.polymorphic_url(destination) | |
| end | |
| end | |
| def index | |
| breadcrumbs.index(self) | |
| end | |
| def first? | |
| index.zero? | |
| end | |
| def last? | |
| index == breadcrumbs.length - 1 | |
| end | |
| def inspect | |
| "#<#{self.class.name} caption: #{caption.inspect}, destination: #{destination.inspect}>" | |
| end | |
| def to_s | |
| caption | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class BreadcrumbCollection < Array | |
| attr_reader :controller_context | |
| def initialize(controller_context) | |
| super() | |
| @controller_context = controller_context | |
| end | |
| def append(caption, destination, options = {}) | |
| add_with_method(:push, caption, destination, options) | |
| end | |
| alias_method :add, :append | |
| def inspect | |
| substr = " " + map { |item| "\"#{item.caption}\"=>\"#{item.path}\"" }.join(', ') | |
| "#<#{self.class.name}#{substr}>" | |
| end | |
| def prepend(caption, destination, options = {}) | |
| add_with_method(:unshift, caption, destination, options) | |
| end | |
| private | |
| def add_with_method(method, caption, destination, options) | |
| options.assert_valid_keys(:only, :except) | |
| breadcrumb_addable = options[:only] && controller_context.action_name.in?(Array(options[:only]).map(&:to_s)) | |
| breadcrumb_addable ||= options[:except] && !controller_context.action_name.in?(Array(options[:except]).map(&:to_s)) | |
| breadcrumb_addable ||= options[:only].nil? && options[:except].nil? | |
| send(method, Breadcrumb.new(self, caption, destination)) if breadcrumb_addable | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module BreadcrumbedController | |
| extend ActiveSupport::Concern | |
| RECORD_CAPTION_METHODS = %i(name title to_s).freeze | |
| included do | |
| helper_method :breadcrumbs | |
| end | |
| module ClassMethods | |
| protected | |
| def breadcrumb(caption, destination, options = {}) | |
| before_action(options) do |controller| | |
| controller.breadcrumbs.append(caption, destination) | |
| end | |
| end | |
| def breadcrumbs(options = {}, &block) | |
| raise ArgumentError, 'no block given' unless block_given? | |
| before_action(options) do |controller| | |
| controller.instance_exec(breadcrumbs, controller, &block) | |
| end | |
| end | |
| # TODO Refactor!!! | |
| def breadcrumbs_for_resource(*args) | |
| options = args.extract_options! | |
| route_key = options[:route] | |
| record_caption_methods = Array(options.fetch(:record_caption) { RECORD_CAPTION_METHODS }) | |
| breadcrumbs(options.slice(:only, :except, :if, :unless)) do |b, controller| | |
| parent_resource = args.first.present? && try_call(options[:parent]) != false | |
| controller_namespace = options.fetch(:namespace) do | |
| controller.class.name.deconstantize.split('::').map(&:underscore) | |
| end | |
| controller_namespace = try_call(controller_namespace) | |
| if controller_namespace == false | |
| controller_namespace = [] | |
| else | |
| controller_namespace = Array(controller_namespace).flatten.map(&:to_sym) | |
| end | |
| # Determine collection and element names | |
| if options[:class_name] | |
| element_name = args.first | |
| resource_class = options[:class_name].constantize | |
| else | |
| resource_class = case args.first | |
| when NilClass then controller.controller_name.classify.constantize | |
| when Class then args.first | |
| when Symbol, String then args.first.to_s.classify.constantize | |
| else raise ArgumentError, 'resource element and collection name could not be determined by reflection' | |
| end | |
| end | |
| element_name ||= resource_class.model_name.element.to_sym | |
| collection_name ||= resource_class.model_name.collection.to_sym | |
| @parent_breadcrumb_records ||= [] | |
| @breadcrumb_routes ||= {} | |
| if parent_resource | |
| if record = load_record(element_name, options) | |
| if !singular_string?(args.first.to_s) and try_call(options[:include_collection]) != false | |
| b.append(resource_class.model_name.human(count: 2), controller_namespace + [collection_name]) | |
| end | |
| record_caption = load_caption(record) | |
| if try_call(options[:include_element]) != false | |
| b.append(record_caption, controller_namespace + @parent_breadcrumb_records + [record]) | |
| end | |
| @parent_breadcrumb_records << record | |
| @breadcrumb_routes[record] = route_key.to_s.try(:singularize).presence || record.class.model_name.singular | |
| end | |
| else | |
| # Resource is primary resource in controller | |
| if try_call(options[:include_collection]) != false | |
| b.append(resource_class.model_name.human(count: 2), controller_namespace + @parent_breadcrumb_records + [collection_name]) | |
| end | |
| if controller.action_name.in? %w(show edit update) | |
| if record = load_record(element_name, options) | |
| @breadcrumb_routes[record] = route_key.to_s.try(:singularize).presence || record.class.model_name.singular | |
| record_caption = load_caption(record) | |
| if try_call(options[:include_element]) != false | |
| if route_key | |
| #raise @parent_breadcrumb_records.inspect | |
| route = send("#{controller_namespace.join('_')}_#{@breadcrumb_routes.values.join('_')}_path", *@parent_breadcrumb_records, record) | |
| else | |
| route = controller_namespace + @parent_breadcrumb_records + [record] | |
| end | |
| b.append(record_caption, route) | |
| end | |
| end | |
| end | |
| if controller.action_name.in? %w(new create) | |
| if route_key | |
| route = send("new_#{controller_namespace.join('_')}_#{@breadcrumb_routes.values.join('_')}_path", *@parent_breadcrumb_records) | |
| else | |
| route = [:new] + controller_namespace + @parent_breadcrumb_records + [element_name] | |
| end | |
| b.append(I18n.translate('breadcrumbs.new', model: resource_class.model_name.human), route) | |
| end | |
| if controller.action_name.in? %w(edit update) | |
| # [:edit, controller_namespace, @parent_breadcrumb_records, record].compact.flatten(1) | |
| if route_key | |
| route = send("edit_#{controller_namespace.join('_')}_#{@breadcrumb_routes.values.join('_')}_path", *@parent_breadcrumb_records, record) | |
| else | |
| route = [:edit] + controller_namespace + @parent_breadcrumb_records + [record] | |
| end | |
| b.append(I18n.translate('breadcrumbs.edit', model: resource_class.model_name.human), route) | |
| end | |
| end | |
| end | |
| end | |
| end | |
| protected | |
| def breadcrumbs | |
| @breadcrumbs ||= BreadcrumbCollection.new(self) | |
| end | |
| private | |
| def load_caption(record) | |
| RECORD_CAPTION_METHODS.collect { |method| record.try(method) }.reject(&:blank?).compact.first | |
| end | |
| def load_record(element_name, options) | |
| record = instance_variable_get("@#{element_name}") | |
| raise "@#{element_name} has not been set in #{self.class.name.deconstantize.underscore}/#{controller_name}##{action_name}" if options[:allow_nil] != true and record.nil? | |
| if options[:class_name] | |
| record.becomes(options[:class_name].to_s.constantize) | |
| else | |
| record | |
| end | |
| end | |
| def singular_string?(str) | |
| str.pluralize != str and str.singularize == str | |
| end | |
| def try_call(proc_or_symbol_or_object, *args) | |
| return nil if proc_or_symbol_or_object.nil? | |
| if proc_or_symbol_or_object.is_a? Symbol | |
| method(proc_or_symbol_or_object).call | |
| elsif proc_or_symbol_or_object.respond_to? :call | |
| self.instance_exec(*args, &proc_or_symbol_or_object) | |
| else | |
| proc_or_symbol_or_object | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment