Search code examples
ruby-on-railshamlmiddleman

Define and use a function within the same HAML file


I've got a light-weight haml-based site that I'm building with Middleman (not Rails, but I'm tagging this with Rails because I think Rails people might know the answer).

Is it possible to define a function in-line in the haml file and then use it later in that same file?

(Yes, I know how to use separately-defined helper files. That's not the question.)

I have a page with a function that is uniquely specialized to that page. Defining it in a helper file is to me an unnecessary separation.

Example:

-def foo(color,text)
  -content_tag :p, style: "color: #{color}" do
    -text

%html
  %head
    %title Demo page

  %body
    %h1 demo page

    =foo("red", "this is red")
    =foo("blue", "this is blue")

Rendering the above page results in undefined method 'foo'.

Is there a way to make that work? (Did I define the function incorrectly?)


Solution

  • You have to define a singleton method:

    :ruby
      def self.foo(color, text)
        content_tag :p, style: "color: #{color}" do
          text
        end
      end
    
    %html
      %head
        %title Demo page
      %body
        %h1 demo page
    
        = foo("red", "this is red")
    

    When you define foo it is defined as an instance method of Tilt::CompiledTemplates, but the template is evaluated in the context of a new Object which doesn't have foo. This is an abridged version of what this process looks like:

    module Compiled; end
    
    # code inside the `__tilt` method is a compiled haml code:
    #   Haml::Template.compile(File.read('index.html.haml'), engine: Haml::Engine)
    source = <<~CODE
      Compiled.class_eval do
        def __tilt_4220
          _buf = ""
          def foo(text)
            text
          end
          _buf << "<h1>demo page</h1>\n"
          _buf << foo("this is red")
          _buf
        end
      end
    CODE
    
    Object.class_eval(source)
    method = Compiled.instance_method(:__tilt_4220)
    # render
    puts method.bind_call(Object.new)
    

    https://github.com/jeremyevans/tilt/blob/v2.4.0/lib/tilt/template.rb#L192