davideghz davideghz - 4 months ago 19
Ruby Question

Rails content_tag method

I was browsing the source code of this gem and I found a use of content_tag that confuses me a lot.

def render(view)
view.content_tag(name, attributes[:content], attributes.except(:content))
end


I do not understand why content_tag is called on view. I generally use it as helper to generate HTML tags, but I've never called it as a method.

Answer

Most commonly, content_tag is called in the context of a view - so, you don't need to call view.content_tag because the view knows how to respond to content_tag (and simply calling content_tag is the same as calling self.content_tag).

The render method that you're showing exists within the class MetaTag which inherits from Tag. Tag is a plain old Ruby object (PORO), so it doesn't know how to respond to content_tag.

But, as you can see, the render method takes a view as an argument. And, naturally, the view object knows how to respond to content_tag. So, calling view.content_tag is the way that MetaTag is able to render the content tag.

This is pretty much an instance of the Presenter Pattern (different people use different terms). Ryan Bates has a good RailsCast on this here.

To your question in the comments, Rails doesn't "know" that view is an instance of ActionView::Base. You have the responsiblity of passing in an actual view instance. I tend to pass in the controller so that I have access to the view and params. Maybe something like this:

class FooController < ApplicationController

  def foo_action
    FooPresenter.present(self)
  end

end

and...

class FooPresenter
  class << self

    def present(controller)
      new(controller).present
    end

  end # class methods

  #===================================================================
  # instance methods
  #===================================================================

    def initialize(controller)
      @controller = controller
    end

    def present
      content_tag :div, data: {foo: params[:foo]}, class: 'bar'
    end

  private

    def controller()  @controller                end
    def view()        controller.view_context    end
    def params()      controller.params          end

    def method_missing(*args, &block)
      view.send(*args, &block)
    end

end

By including the method_missing method, I no longer have to call view.content_tag. I can just call content_tag. FooPresenter won't find the method, so will send the call onto the view where the method will be found and executed.

Again, Ryan does a great job explaining all of this.