How to test Rails :helper_methods that live in application.rb? - ruby-on-rails

Here's my situation - I have a helper named LayoutHelper that I use to help me build my layout, and I'm trying to test the following method.
It has a method in LayoutHelper that looks similar to this:
def show_login_form?
return false if #hide_login_form || logged_in?
return true
end
I also have the following in my application.rb:
helper_method :logged_in?
def logged_in?
return #logged_in_user.present?
end
And finally, my test file looks like this:
require File.dirname(__FILE__) + '/../../test_helper'
class LayoutHelperTest < ActionView::TestCase
def test_hide_login_form
assert(show_login_form?)
hide_login_form
assert(!show_login_form?)
end
end
My problem now is that when I run this test, I get this:
NoMethodError: undefined method `logged_in?' for #<LayoutHelperTest:0xb7a7aca8>
app/helpers/layout_helper.rb:17:in `show_login_form?'
layout_helper_test.rb:12:in `test_hide_login_form'
I'm wondering if I'm doing this the correct Rails way, and if so, what I need to change in order to get this test to work.

I have to say, this stumped me at first - but I finally figured out what wasn't quite right.
Helpers are html generators, and should be built in a bubble - not knowing what's going on in sessions, controllers, etc. Your show_login_form? method isn't generating code, it's working with controller methods. It should be in application_controller.rb itself, and tested in controller tests.
Once you move it here, you should have no problem testing. Oh, and this might be a shorter, sweeter way to write the method itself:
def show_login_form?
!(#hide_login_form || logged_in?)
end
I hope this helps. It certainly got me to research helper tests, which I've neglected until now.

Related

Rspec Controller testing in Rails a controller that inherits from AbstractController::Base

I am writing controller tests for an application that I did not build, so it's definitely been a learning process. This is my first time encountering a controller that inherits directly from AbstractController::Base. It does not behave, obviously, the same as other controllers.
Its format is roughly:
class SchwadGenericController < AbstractController::Base
def schwad_method var_one, var_two = nil, var_three = nil
if var_two.blank?
var_one.generic_method
end
render template: "schwad_templates/generic_template", layout: false
end
end
I tried normal testing, this is where I am currently at to get ANYTHING to happen.
require 'rails_helper'
describe SchwadGenericController do
# before(:each) do
# SchwadGenericController.skip_authorize_resource
# end
# login_user
let!(:variable){ create(:my_factory_variable) }
describe 'controller methods' do
it 'should hit this method' do
binding.pry
SchwadGenericController.schwad_method(variable)
# expect(response).to_render template: "schwad_templates/generic_template"
end
end
end
And here is roughly where my failures are landing.
Failures:
1) SchwadGenericController controller methods should hit this method
Failure/Error: Unable to find matching line from backtrace
NoMethodError:
undefined method `request=' for # <SchwadGenericController:0x007f8022db0a20>
I read up on abstract controllers and their role in rails here: https://www.mobomo.com/2012/06/and-you-thought-render-farms-were-just-for-pixar/
I read up on the docs here: http://api.rubyonrails.org/classes/AbstractController/Base.html
I would really appreciate another set of eyes on this and guidance as to how you guys have tested controllers and their methods, with controllers that are inheriting from AbstractController::Base.... What am I missing?
-Schwad
After some testing, I don't think this is possible. Controller specs are just wrappers for Rails functional tests which test classes inheriting from ActionController::Base. For controller tests to even run, the controller must support the request and response objects, which is not the case of AbstractController::Base (these are defined in ActionController::Base). That is why you get the particular error when you run the test. For the same reason, you will not be able to use the controller spec helpers (expects) such as to_render because, again, they are defined only for controller specs and your controller class is not a "controller" in the "controller specs" sense.
The only option you seem to have for testing is to test the controller just as any other plain ruby class. You'd need to move your test out of the spec/controllers directory to some other, e.g. spec/abstract_controllers and then you'd have to give up all controller spec helpers and test just calling the instance methods, e.g.:
describe 'controller methods' do
it 'should hit this method' do
c = SchwadGenericController.new
expect(c).to receive(:render).with(template: "schwad_templates/generic_template", layout: false)
c.schwad_method(variable)
end
end
Extending directly from AbstractController::Base seems the likely source of the error to me. Unless you're doing something very nonconventional there should be no reason to do this.
Are you sure you don't intend to inherit from ActionController::Base? There's a whole bunch of modules in ActionController required for rendering which is probably explains the error on a missing method in your tests.
If switching to ActionController::Base doesn't work. Try running app.get "/path/to/action" from the rails console. Do you get the same error?

Ruby ApplicationController function call in ActionMailer

I'm sending out emails to users and I need to call a function I have in my ApplicationController. Seems simple enough, but I cannot for the life of me find any documentation on how to to this. I can access ApplicationHelper functions just fine, but that's not doing me much good for what I need it to do... Anyone got any light they can shed on the subject?
So not sure if this will help anybody out since this was a highly project specific case, but here's what I did to work through this:
Move functions required for the mailer to the ApplicationHelper.
Add include ApplicationHelper to the Mailer class and my ApplicationController.
For the logic where I needed if !provider_signed_in? I updated to if defined?(provider_signed_in?) && !provider_signed_in? so that it wouldn't return an undefined method error... The logic that I needed if the provider was signed in was irrelevant for the mailer (since a provider would never get this email), so it didn't matter there even though it does matter everywhere else I'm using it on the site.
Thanks Rahul for helping me think through this.
there is a method called helper which you can use in your mailer to inlude your helper in your mailer..
# mailer_helper.rb
module MailerHelper
def your_method
# do whatever you want to do here.
end
end
# in your mailer file
class WelcomeMailer < ActionMailer::Base
helper MailerHelper
.....
end
and that't it, now you can use the methods in the views also.

Test controller method that uses request-url with Rspec

I wrote a gem for Rails that extends ApplicationController with a certain method. This method parses the current URL and uses the result to do a lookup. It looks something like this (simplified):
#current_account = Account.where(subdomain => request.subdomains.first).first
I want to include a test in the gem that asserts that the subdomain is looked up correctly based on a given URL.
I am running into two problems trying to write the test:
1) Since i'm testing within a gem, there is no controller (or Rails app for that matter) so I don't actually know where to start (Unit test, Controller test?)
2) I have searched everywhere, but I cannot find a way to setup the request hash in Rspec for testing. I would expect I would be able to do something like request.url = 'account1.example.com'
Any help on how to setup a proper test for this situation on Rspec is highly appreciated
If you are doing a controller test, then the URLs are usually specified on the configuration, like:
config.action_controller.default_url_options = { host: 'www.test.host' }
that is, if you're testing this as a rails application.
To test that a method in an abstract class works, your best option is to create a Test Subclass, and test using that. Something like
class TestController < ApplicationController; end
and then do your specs around this controller, which should behave exactly as an ApplicationController
EDIT
This would be an example of what I am proposing:
class TestController < ApplicationController
def index
render text: 'fake page' #This is so the action does not fail
end
end
describe TestController do
it 'searches for the current account in the right subdomain' do
Account.should_receive(:where).with({subdomain: 'www'})
get :index
end
end

Rails - URL helpers not working in mailers

I tried:
class MyMailer
def routes
Rails.application.routes.url_helpers
end
def my_mail
#my_route = routes.my_helper
... code omitted
end
Also inside mailer:
include Rails.application.routes.url_helpers
def my_mail
#my_route = my_helper
Also, the simple way, in mailer template:
= link_to 'asd', my_helper
But then when I try to start console I get:
undefined method `my_helper' for #<Module:0x007f9252e39b80> (NoMethodError)
Update
I am using the _url form of the helper, i.e. my_helper_url
For Rails 5, I included the url helpers into the my mailer file:
# mailers/invoice_mailer.rb
include Rails.application.routes.url_helpers
class InvoiceMailer < ApplicationMailer
def send_mail(invoice)
#invoice = invoice
mail(to: #invoice.client.email, subject: "Invoice Test")
end
end
Doing this broke my other routes.
# mailers/invoice_mailer.rb
include Rails.application.routes.url_helpers
Doing this is not the right way, this will break application as routes are reloading and routes will not be available is those are reloading
module SimpleBackend
extend ActiveSupport::Concern
Rails.application.reload_routes!
Right answer is to use *_url and not *_path methods in email templates as explained below in Rails docs.
https://guides.rubyonrails.org/action_mailer_basics.html#generating-urls-in-action-mailer-views
I ran into the same issue but with a Concern, i was unable to use any of the url helpers in my code even using directly Rails.application.routes.url_helpers.administrator_beverages_url i was getting this error:
undefined method `administrator_beverages_url' for #<Module:0x00000002167158> (NoMethodError)
even unable to use the rails console because of this error the only way i found to solve this was to use this code in my Concern
module SimpleBackend
extend ActiveSupport::Concern
Rails.application.reload_routes! #this did the trick
.....
The problem was Rails.application.routes.url_helpers was empty at the moment of the initialization. I don't know the performance implication of using this but for my case this is a small application and i can take the bullet.
In the mailer add
helper :my
or the helper you need
and it will load app/helpers/my_helper.rb & includes MyHelper
Enjoy
The rails route helpers are in Rails.application.routes.url_helpers. You should just be able to put
include Rails.application.routes.url_helpers at the top of your class, though I haven't tested this
I came across this issue while working with a newly added route that seemed to be unavailable in my Mailer. My problem was I needed to restart all the workers, so they would pick up the newly added route. Just leaving this footnote in here in case someone runs into the same issue, it can be tricky to solve if you don't know this is happening.
Please take a look at this blog which explains how you can make use of Rails.application.routes.url_helpers in the right manner.
http://hawkins.io/2012/03/generating_urls_whenever_and_wherever_you_want/
You need to use the my_helper_url

Calling Super from an Aliased Method

I am trying to DRY up my code a bit so I am writing a method to defer or delegate certain methods to a different object, but only if it exists. Here is the basic idea: I have Shipment < AbstractShipment which could have a Reroute < AbstractShipment. Either a Shipment or it's Reroute can have a Delivery (or deliveries), but not both.
When I call shipment.deliveries, I want it to check to see if it has a reroute first. If not, then simply call AbstractShipment's deliveries method; if so, delegate the method to the reroute.
I tried this with the simple code below:
module Kernel
private
def this_method
caller[0] =~ /`([^']*)'/ and $1
end
end
class Shipment < AbstractShipment
...
def deferToReroute
if self.reroute.present?
self.reroute.send(this_method)
else
super
end
end
alias_method :isComplete?, :deferToReroute
alias_method :quantityReceived, :deferToReroute
alias_method :receiptDate, :deferToReroute
end
The Kernel.this_method is just a convenience to find out which method was called. However, calling super throws
super: no superclass method `deferToReroute'
I searched a bit and found this link which discusses that this is a bug in Ruby 1.8 but is fixed in 1.9. Unfortunately, I can't upgrade this code to 1.9 yet, so does anyone have any suggestions for workarounds?
Thanks :-)
Edit: After a bit of looking at my code, I realized that I don't actually need to alias all of the methods that I did, I actually only needed to overwrite the deliveries method since the other three actually call it for their calculations. However, I would still love to know y'all's thoughts since I have run into this before.
Rather than using alias_method here, you might be better served by hard-overriding these methods, like so:
class Shipment < AbstractShipment
def isComplete?
return super unless reroute
reroute.isComplete?
end
end
if you find you are doing this 5-10 times per class, you can make it nicer like so:
class Shipment < AbstractShipment
def self.deferred_to_reroute(*method_names)
method_names.each do |method_name|
eval "def #{method_name}; return super unless reroute; reroute.#{method_name}; end"
end
end
deferred_to_reroute :isComplete?, :quantityReceived, :receiptDate
end
Using a straight eval offers good performance characteristics and allows you to have a simple, declarative syntax for what you are doing within your class definition.

Resources