Search code examples
ruby-on-railscachinghamldraper

Cannot cache from decorator (draper)


Caching is by far the most logic-intensive part of my view code, so I would like to do fragment caching from inside a decorator, however, I cant do it.

When i do this from my decorator:

def cached_name
  h.cache do
   "a name here"
  end
end

I get this:

You have a nil object when you didn't expect it! You might have expected an instance of Array. The error occurred while evaluating nil.length

I instantiate my decorator from inside a controller

@presenter = SomePresenter::new

I am using HAML for my views

How can I succesfully cache from inside my decorator, so my view can do stuff like this

= @decorator.cached_logic_heavy_stuff

UPDATE: I have created a git repo showing my issue: https://github.com/houen/presenter_caching

UPDATE: This maybe works - see the repo

  include Haml::Helpers
  def another_way_to_try
    self.init_haml_helpers
    buffer = haml_buffer.buffer

    h.with_output_buffer(buffer) do
      h.cache do
        h.concat "i should still not be empty"
      end
    end
  end

Solution

  • Rails' cache method tries to infer a cache key based on the view that it's being called from. Since you're not actually calling it from a view (but from inside an instance of a decorator class), I expect that it's bombing when trying to build a cache key.

    You might try passing a cache key explicitly, via h.cache "your cache key" do. With a full stack trace, you can figure out where it's throwing the exception, and then work around that, as well. Without the full stack trace, it's harder to help you, though.

    Edit: Looking at Rails' caching code, I think this might be a deeper issue; it's attempting to get the length of output_buffer, which isn't going to be available outside of your views' contexts (that is, within Draper). You might try adding:

    def output_buffer
      h.output_buffer
    end
    

    But without testing it, I'm thinking it might not work exactly as planned without some more work. This is just a rough guess - I'd be surprised if this is actually the issue, but hopefully it gets you on the right path.

    The note in the source there:

    # VIEW TODO: Make #capture usable outside of ERB
    # This dance is needed because Builder can't use capture
    

    indicates that this isn't a fully-solved problem, so you may need to do a little digging around in the Rails internals to make this one work.