Ramon Tayag
Ramon Tayag

Reputation: 16084

Ruby/Rails: How to temporarily define what scope to call methods

I have a module:

module LiquidFilters
  include ActionView::Helpers::AssetTagHelper

  def stylesheet_tag(stylesheet_file_path)
    stylesheet_link_tag stylesheet_file_path
  end
end

And my spec:

describe LiquidFilters do
  describe "#stylesheet_tag" do
    it "should return the css file in an html (media=screen) tag" do
      helper.stylesheet_tag("/path/css").should match(/<link href="\/path\/css\.css" media="screen" rel="stylesheet" type="text\/css"\s*\/>/)
    end
  end
end

But I get a wrong number of arguments (2 for 1) error:

Failures:
  1) LiquidFilters#stylesheet_tag should return the css file in an html (media=screen) tag
     Failure/Error: helper.stylesheet_tag("/path/css").should match(/<link href="\/path\/css\.css" media="screen" rel="stylesheet" type="text\/css"\s*\/>/)
     wrong number of arguments (2 for 1)
     # ./app/filters/liquid_filters.rb:16:in `stylesheet_tag'
     # ./spec/helpers/liquid_filters_spec.rb:13

I can see the problem is at stylesheet_link_tag stylesheet_file_path in the LiquidFilters module -- the stylesheet_link_tag is supposed to call a AssetTagHelper private method "stylesheet_tag", but it ends up calling LiquidFilter's stylesheet_tag method.

Question

How do I force stylesheet_link_tag to call AssetTagHelper's methods, and not LiquidFilter's?

Note: I'd like to keep the method name stylesheet_tag.

Upvotes: 1

Views: 642

Answers (2)

Laas
Laas

Reputation: 6068

Rails's alias_method_chain could help:

module LiquidFilters
  include ActionView::Helpers::AssetTagHelper

  def stylesheet_tag_with_liquid(stylesheet_file_path, options = nil)
    # if this got called with two arguments, proxy them to original
    if options
      stylesheet_tag_without_liquid(stylesheet_file_path, options)
    else
      stylesheet_link_tag stylesheet_file_path
    end
  end

  def self.included(receiver)
    alias_method_chain :stylesheet_tag, :liquid
  end
end

While you still mask the original method, you can now proxy to it.

EDIT: moved alias_method_chain to self.included callback, so it won't be called before the module is actually included somewhere.

Upvotes: 0

gnab
gnab

Reputation: 9561

You could include the AssetTagHelper module in a Helper class to avoid the method name override, like this:

require 'singleton'

module LiquidFilters
  class Helper
    include Singleton  
    include ActionView::Helpers::AssetTagHelper
  end

  def stylesheet_tag(stylesheet_file_path)
    Helper.instance.stylesheet_link_tag stylesheet_file_path
  end
end

That way, your stylesheet_tag method won't get mixed up with the AssetTagHelper's private method.

Edit

Based on your feedback, I guess this is the closest you get then:

module LiquidFilters
  include ActionView::Helpers::AssetTagHelper

  alias :old_stylesheet_tag :stylesheet_tag

  def stylesheet_tag(stylesheet_file_path, options = nil)
    if options
      old_stylesheet_tag(stylesheet_file_path, options)
    else
      stylesheet_link_tag stylesheet_file_path
    end 
  end 
end

You basically override the private method, so I don't think there is any other alternative than proxying calls.

Upvotes: 1

Related Questions