app/helpers/trestle/url_helper.rb in trestle-0.10.0 vs app/helpers/trestle/url_helper.rb in trestle-0.10.1

- old
+ new

@@ -1,82 +1,152 @@ module Trestle module UrlHelper MODAL_ACTIONS = [:new, :show, :edit] - def admin_link_to(content, instance_or_url=nil, options={}, &block) + # Generates a link to an admin, optionally for a specific instance on a resourceful admin. + # + # It has a few additional conveniences over using the standard `link_to` helper: + # + # 1) It can automatically infer the admin from the given instance. + # 2) It will automatically add data-controller="modal-trigger" when linking to a form + # action that is set to show as a modal. + # 3) It sets data-turbo-frame appropriately for modal and non-modal contexts to ensure + # the admin can correctly detect modal requests. + # + # content - HTML or text content to use as the link content + # (will be ignored if a block is provided) + # instance - Optional model instance, or explicit String path + # admin - Optional admin instance to link to. Will be inferred from instance if provided, + # otherwise falling back to the current admin if available + # action - Optional admin action to link to. Will default to :show if instance is provided, + # otherwise the admin's root action (usually :index) will be used + # method - Optional request method (e.g. :delete), that will be set as `data-turbo-method` + # params - Hash of URL parameters to pass to `instance_path` or `path` admin methods (default: {}) + # options - Hash of options to forward to the `link_to` helper (default: {}) + # block - Optional block to capture to use as the link content + # + # Examples + # + # <%= admin_link_to article.name, article %> + # + # <%= admin_link_to admin: :dashboard, action: :index do %> + # <%= icon "fas fa-gauge" %> Dashboard + # <% end %> + # + # Returns a HTML-safe String. + # Raises ActionController::UrlGenerationError if the admin cannot be automatically inferred. + def admin_link_to(content=nil, instance=nil, admin: nil, action: nil, method: nil, params: {}, **options, &block) # Block given - ignore content parameter and capture content from block if block_given? - instance_or_url, options = content, instance_or_url || {} - content = capture(&block) + instance, content = content, capture(&block) end - if instance_or_url.is_a?(String) - # Treat string URL as regular link - link_to(content, instance_or_url, options) - else - # Normalize options if instance is not provided - if instance_or_url.is_a?(Hash) - instance, options = nil, instance_or_url - else - instance = instance_or_url - end + # Treat string URL as regular link + if instance.is_a?(String) + return link_to(content, instance, options) + end - # Determine admin - if options.key?(:admin) - admin = Trestle.lookup(options.delete(:admin)) - elsif instance - admin = admin_for(instance) - end + # Determine target admin + target = lookup_admin_from_options( + instance: instance, + admin: admin, + fallback: self&.admin, + raise: true + ) - admin ||= self.admin if respond_to?(:admin) + # Set default action depending on instance or not + action ||= (instance ? :show : target.root_action) - if admin - # Ensure admin has controller context - admin = admin.new(self) if admin.is_a?(Class) + path = admin_url_for(instance, admin: target, action: action, **params) - # Generate path - action = options.delete(:action) || :show - params = options.delete(:params) || {} + # Determine link data options + options[:data] ||= {} - if admin.respond_to?(:instance_path) && instance - path = admin.instance_path(instance, params.reverse_merge(action: action)) - else - params[:id] ||= admin.to_param(instance) if instance - path = admin.path(action, params) - end + if MODAL_ACTIONS.include?(action) && target&.form&.modal? + options[:data][:controller] ||= "modal-trigger" + else + options[:data][:turbo_frame] ||= (modal_request? ? "modal" : "_top") + end - # Determine link data options - options[:data] ||= {} + options[:data][:turbo_method] ||= method if method - if MODAL_ACTIONS.include?(action) && admin.respond_to?(:form) && admin.form.modal? - options[:data][:controller] ||= "modal-trigger" - else - options[:data][:turbo_frame] ||= (modal_request? ? "modal" : "_top") - end - - link_to(content, path, options) - else - raise ActionController::UrlGenerationError, "An admin could not be inferred. Please specify an admin using the :admin option." - end - end + link_to(content, path, options) end - def admin_url_for(instance, options={}) - admin = Trestle.lookup(options.delete(:admin)) if options.key?(:admin) - admin ||= admin_for(instance) - return unless admin + # Returns the admin path for a given instance. + # + # An admin can either be explicitly specified (as a symbol or admin class), + # or it can be automatically inferred based the instance type using `admin_for`. + # + # instance - The model instance to generate a path for + # admin - Optional admin (symbol or admin class) + # action - Optional admin action to generate the URL for. Will default to :show if + # instance is provided, otherwise the admin's root action (usually :index) + # will be used + # raise - Whether to raise a ActionController::UrlGenerationError if the admin + # cannot be determined, either from the admin parameter or automatically + # params - Hash of URL parameters to pass to `instance_path` or `path` admin methods + # + # Examples + # + # <%= admin_url_for(article, action: :edit) %> + # <%= admin_url_for(article, admin: :special_articles) %> + # + # Returns a String, or nil if the admin cannot be automatically inferred. + def admin_url_for(instance=nil, admin: nil, action: nil, raise: false, **params) + target = lookup_admin_from_options( + instance: instance, + admin: admin, + fallback: self&.admin, + raise: raise + ) + return unless target - # Ensure admin has controller context - admin = admin.new(self) if admin.is_a?(Class) + # Set default action depending on instance or not + action ||= (instance ? :show : target.root_action) - if admin.respond_to?(:instance_path) - admin.instance_path(instance, options) + if instance + if target.respond_to?(:instance_path) + target.instance_path(instance, action: action, **params) + else + target.path(action, params.merge(id: target.to_param(instance))) + end else - admin.path(options[:action] || :show, id: admin.to_param(instance)) + target.path(action, params) end end + # Looks up the registered Trestle admin for a given model instance. + # + # The lookup is performed on the global `Trestle::Registry` instance, + # which tracks admin resource models unless the resource was created + # with `register_model: false`. + # + # instance - The model instance to look up in the registry + # + # Returns a Trestle::Admin subclass or nil if no matching admin found. def admin_for(instance) Trestle.lookup_model(instance.class) + end + + private + def lookup_admin_from_options(instance: nil, admin: nil, fallback: nil, raise: true) + if admin + result = Trestle.lookup(admin) + elsif instance + result = Trestle.lookup_model(instance.class) || fallback + else + result = fallback + end + + if result && result.is_a?(Class) + # Instantiate admin with current context + result.new(self) + elsif result + result + elsif raise + raise ActionController::UrlGenerationError, + "An admin could not be inferred. Please specify an admin using the :admin option." + end end end end