Recently, I took on the task of refactoring some repetitive code that occurred several times throughout our codebase into a neat method. In other words, I made the code a bit more DRY (“Don’t Repeat Yourself” - a well-known convention in writing good code).
Essentially, the method (feature_enabled?), checks to see if a new feature has been enabled within the Orbit app for a user or their workspace.
In this case, we used a gem called Flipper to help us turn on feature flags for certain users before implementing big feature changes to all of our users at once.
Here’s how the code looked before the refactored method:
{% c-block language="ruby" %}
if Flipper[:my_feature].enabled?(current_user) ||
Flipper[:my_feature].enabled?(@workspace)
# do something cool
end
{% c-block-end %}
And here’s how the same code would look with the feature_enabled? method:
{% c-block language="ruby" %}
if feature_enabled?(:my_feature, current_user, @workspace)
# do something cool
end
{% c-block-end %}
When I first defined feature_enabled?, I did so in the application_helperfile. Consequently, the corresponding unit tests were written in application_helper_spec.
This method, however, needs to be used in both the helpers and the controllers. As I was working my way through this refactor, I started to run into an issue where the helper method wasn’t being recognised anywhere it was called in any controller files.
As I tried to work through it, I had a couple of different ideas as to what was going on:
Spoiler alert! It ended up being the second option. So, I ended up transferring some code into different files by moving:
Here’s what the function looks like in application_controller:
{% c-block language="ruby" %}
# frozen_string_literal: true
class ApplicationController < ActionController::Base
helper_method :feature_enabled?
protected
def feature_enabled?(feature, user, workspace)
Flipper[feature].enabled?(user) || Flipper[feature].enabled?(workspace)
end
end
{% c-block-end %}
And here’s the corresponding application_controller_spec:
{% c-block language="ruby" %}
RSpec.describe ApplicationController, type: :controller do
let(:controller) { described_class.new }
describe '#feature_enabled?' do
let(:user) { create(:user) }
let(:workspace) { create(:workspace) }
let(:another_user) { create(:user) }
subject { controller.send(:feature_enabled?, :my_feature, user, workspace) }
it 'returns true if feature is enabled for everybody' do
Flipper.enable(:my_feature)
expect(subject).to be true
end
describe do
context 'when feature is not enabled' do
it { expect(subject).to be false }
context 'when feature is enabled for this user' do
before { Flipper.enable_actor(:my_feature, user) }
it { expect(subject).to be true }
end
end
end
it 'returns true if feature is enabled for everybody' do
Flipper.enable(:my_feature)
expect(subject).to be true
end
end
{% c-block-end %}
I think it was here that things were starting to come together for me, but it didn’t seem that the tests were accurately testing our feature_enabled? method.
By this point, we’d written the specs that proved feature_enabled? did what it is supposed to. However, we still needed to prove that the method would be available in the helpers, and therefore the views.
(That peace of mind is important, as someone could easily remove the helper_method :feature_enabled? call in application_controller by mistake and consequently break any usage of it in the helpers and views.)
To that extent, we needed to write a test that proved this method would indeed be available in the helper and view files.
My first approach was to try and write a test in application_helper_spec to verify the presence of the method, but it turns out that the Application Controller helper_methods are only added to the helpers during a real request/response cycle.
The application_helper_spec doesn’t simulate that - instead, it just calls a plain old object, meaning that feature_enabled? wasn’t defined when I tried to call it there.
I also realized it is the controller's (not the helper's) job to make sure the method exists in the helpers. After all, the helper_method call is located in the controller.
So once I realized that, it hit me that the application_helper was the wrong place for this spec. No wonder it was so difficult to test there!
From there, I went looking for a way to test our feature_enabled? method in the controller. This was the StackOverflow solution:
You can call any helper methods from a controller using the view_context, e.g. View_context.my_helper_method
With this next step in the right direction, I added a test that would specifically test whether the view_context of the controller had the helper method defined. I used the respond_to RSpec matcher for this, which calls respond_to? on the object to see if the method is defined:
{% c-block language="ruby" %}
describe '#view_context' do
subject { controller.view_context }
it { expect(subject).to respond_to(:feature_enabled?) }
end
{% c-block-end %}
Because of how view_context works in Rails, we can be sure that any methods it defines will be available in our helpers and views.
In conclusion, that’s how I figured out how to test a helper method from the Application Controller in Rails.
I removed the method from the Application Helper and transferred it to the Application Controller, and was then able to successfully test the behavior of the method in the Application Controller spec.
I also added a test to prove the feature_enabled? method would be available to helpers and views.
Thanks so much for reading! Feel free to stay in touch with us on Twitter (@OrbitModel).