How to access a Rails controller view context from outside of a controller?
Asked Answered
V

5

11

I am working on cleaning up some code that relies on some custom controller helper methods, by creating a "plain old Ruby" presenter object. In my controller, I am able to pass the view context to the class:

def show
  # old code: view_context.bad_helper_method
  @foobar = FoobarPresenter.new(Foobar.find(params[:id]), view_context)
end

class FoobarPresenter
  def initialize(model, view_context)
    @model = model
    @view_context = view_context
  end

  def something
    @view_context.bad_helper_method
  end
end

However, I'm not sure what to pass in my test. I would rather pull the helper/view_context dynamically so that I don't have to pass it in.

How can I access the view/controller helper context outside of the controller?

This is a Rails 3.2 project.

Vaccinate answered 9/1, 2015 at 22:56 Comment(0)
R
6

How about testing the expectations?

  1. Test for controller (note that subject is the instance of the controller, assuming we're testing using rspec-rails):

    view_context     = double("View context")
    foobar_presenter = double("FoobarPresenter")
    
    allow(subject).to receive(:view_context).and_return(view_context)
    allow(FoobarPresenter).to receive(:new).with(1, view_context).and_return(foobar_presenter)
    
    get :show, id: 1
    
    expect(assigns(:foobar)).to eql(foobar_presenter)
    
  2. Test for presenter:

    view_context = double('View context', bad_helper_method: 'some_expected_result')
    presenter    = FoobarPresenter.new(double('Model'), view_context)
    
    expect(presenter.something).to eql('some_expected_result')
    
Rowlandson answered 10/1, 2015 at 0:29 Comment(1)
I think this is probably the best approach. Thanks!Vaccinate
T
14

Simpler than you think! (I lost almost an hour until I found a way)

You can instantiate an ActionView

_view_context = ActionView::Base.new

and use it in your test

FoobarPresenter.new(Foobar.new, _view_context)
Tody answered 1/3, 2017 at 11:54 Comment(3)
Thanks! I was trying to instantiate one in a rails console, and this did it for me.Godliman
In modern rails (eg 6.1), ActionView::Base.new says wrong number of arguments (given 0, expected 3)Dvina
In Rails 7, you can use the controller to create one i.e: _view_context = InvoicesController.new.view_context.Prowler
R
6

How about testing the expectations?

  1. Test for controller (note that subject is the instance of the controller, assuming we're testing using rspec-rails):

    view_context     = double("View context")
    foobar_presenter = double("FoobarPresenter")
    
    allow(subject).to receive(:view_context).and_return(view_context)
    allow(FoobarPresenter).to receive(:new).with(1, view_context).and_return(foobar_presenter)
    
    get :show, id: 1
    
    expect(assigns(:foobar)).to eql(foobar_presenter)
    
  2. Test for presenter:

    view_context = double('View context', bad_helper_method: 'some_expected_result')
    presenter    = FoobarPresenter.new(double('Model'), view_context)
    
    expect(presenter.something).to eql('some_expected_result')
    
Rowlandson answered 10/1, 2015 at 0:29 Comment(1)
I think this is probably the best approach. Thanks!Vaccinate
B
1

I unfortunately don't have a perfect answer for you. However, I've dug through the Draper Decorator library, and they have solved this problem.

In particular, they have a HelperProxy class and a ViewContext class that seem to automatically infer the context that you want.

https://github.com/drapergem/draper

They also have some specs around both of these classes, which I'm sure you could borrow from in setting up your own specs.

Basseterre answered 10/1, 2015 at 0:16 Comment(1)
That's a good idea. I'll dig through the source and see if I can find something.Vaccinate
G
0

Based on this answer the following (a bit modified version) worked for me:

  1. Add the following to rails_helper.rb
config.include ActionView::TestCase::Behavior, file_path: %r{spec/presenters}

(assuming your presenter classes are located in the /presenters folder)

  1. After this you can access the view context in your specs via the view method:
FoobarPresenter.new(Foobar.new, view)
Gamy answered 26/8, 2021 at 1:17 Comment(0)
D
0

Unfortunately, it's only available from within the controllers, however, it's fairly easy to create it.

class ViewContextDelegator
  RouteHelpers = Module.new { delegate_missing_to 'Rails.application.routes.url_helpers' }
  HELPERS = ApplicationController.helpers.extend(RouteHelpers)
  delegate_missing_to(:HELPERS)
end

Then use it in any file as follows:

ViewContextDelegator.new.time_ago_in_words(Time.now)
# "less than a minute"
ViewContextDelegator.new.root_path
# "/"

or in a spec:

subject { described_class.new(arg1, view_context) }
let(:view_context) { ViewContextDelegator.new }

Alternatively, you can just define a test helper if you only need it for testing purposes.

def view_context
  @view_context ||= ApplicationController
                    .helpers
                    .extend(Module.new { delegate_missing_to 'Rails.application.routes.url_helpers' })
end
Dunkin answered 13/6, 2022 at 8:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.