instructure/canvas-lms · error · ArgumentError

Cannot pass url and use block

Error message

Cannot pass url and use block

What it means

CanvasCrumb's add_crumb builds a before_action that records a navigation crumb. It supports either an explicit url argument or a block that computes the url at request time, but not both, since the block would be discarded or conflict with the static url. Passing both is a programming mistake, so it raises ArgumentError at class/body evaluation time.

Solutions

  1. Remove either the url argument or the block from the add_crumb call
  2. If the URL is dynamic, drop the url argument and keep the block (possibly returning a Symbol to call on the instance)
  3. If the URL is static, drop the block

Example fix

// before
add_crumb('Courses', courses_path) { |c| c.courses_path }
// after
add_crumb('Courses', courses_path)
# or dynamic:
add_crumb('Courses') { |c| c.courses_path }
Defensive patterns

Strategy: validation

Validate before calling

raise ArgumentError, 'choose url or block, not both' if url && block_given?

Type guard

def valid_crumb_args?(name: nil, url: nil, block_given:) !block_given? || url.nil? end

Prevention

When it happens

Trigger: Calling add_crumb('Name', '/some/path') (or with a full URL) together with a block: add_crumb('Name', url) { |c| c.polymorphic_path(...) }. Both the url positional/keyword argument and block_given? must be truthy simultaneously.

Common situations: Refactoring a crumb that used a static URL into a dynamic one and forgetting to remove the url argument; copy-pasting add_crumb calls and merging the two supported styles.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/45e3eab793c76332. Report an issue: GitHub.

Appendix: source

Thrown at gems/canvas_crummy/lib/canvas_crummy/controller_methods.rb:40

    module ClassMethods
      # Add a crumb to the crumbs array.
      #
      #   add_crumb("Home", "/")
      #   add_crumb("Business") { |instance| instance.business_path }
      #
      # Works like a before_filter so +:only+ and +except+ both work.
      def add_crumb(name, *args)
        options = args.extract_options!
        url = args.first
        # I wanted this next line to look more like:
        # html_options = options.pluck { |k,v| %w{class id}.include? k.to_s  }
        # but couldn't figure out how
        html_options = (klass = options.delete(:class)) ? { class: klass } : {}
        if (id = options.delete(:id))
          html_options[:id] = id
        end
        raise ArgumentError, "Need more arguments" unless name || block_given?
        raise ArgumentError, "Cannot pass url and use block" if url && block_given?

        before_action(options) do |instance|
          url_value = url
          url_value = yield instance if block_given?
          url_value = instance.send url_value if url_value.is_a? Symbol
          name_value = name
          name_value = instance.instance_eval(&name_value) if name_value.is_a? Proc
          name_value = instance.instance_variable_get(:"@#{name_value}") if name_value.is_a? Symbol
          record = instance.instance_variable_get(:"@#{name_value}") unless url_value || block_given?
          if record
            name_value, url_value = record.to_s, instance.url_for(record)
          end

          # FIXME: url_value = instance.url_for(name_value) if name_value.respond_to?("to_param") && url_value.nil?
          # FIXME: Add ||= for the name_value, url_value above
          instance.add_crumb(name_value, url_value, html_options)
        end
      end

View on GitHub (pinned to 1c9f0bb801)