Call yield from another do ...end block - ruby

I try to define own 'context' method in Rspec.
Have next:
module MiscSpecHelper
def its_ok
context "if everything is OK" do
yield
end
end
end
in spec file:
describe "GET index" do
its_ok do
it "has a 200 status code" do
get :index
expect(response.status).to eq(200)
end
end
end
I got:
GET index
has a 200 status code
I expect:
GET index
if everything is OK
has a 200 status code
Why does it ignore my 'context' description?

module MiscSpecHelper
def its_ok(&block)
context "if everything is OK", &block
end
end

Related

Best practice of error handling on controller and interactor

# users_show_controller.rb
class Controllers::Users::Show
include Hanami::Action
params do
required(:id).filled(:str?)
end
def call(params)
result = users_show_interactor(id: params[:id])
halt 404 if result.failure?
#user = result.user
end
end
# users_show_interactor.rb
class Users::Show::Interactor
include Hanami::Interactor
expose :user
def call(:id)
#user = UserRepository.find_by(:id)
end
end
I have a controller and a interactor like above.
And I'm considering the better way to distinguish ClientError from ServerError, on the controller.
I think It is nice if I could handle an error like below.
handle_exeption StandardError => :some_handler
But, hanami-interactor wraps errors raised inside themselves and so, controller receive errors through result object from interactor.
I don't think that re-raising an error on the controller is good way.
result = some_interactor.call(params)
raise result.error if result.failure
How about implementing the error handler like this?
I know the if statement will increase easily and so this way is not smart.
def call(params)
result = some_interactor.call(params)
handle_error(result.error) if result.faulure?
end
private
def handle_error(error)
return handle_client_error(error) if error.is_a?(ClientError)
return server_error(error) if error.is_a?(ServerError)
end
Not actually hanami-oriented way, but please have a look at dry-monads with do notation. The basic idea is that you can write the interactor-like processing code in the following way
def some_action
value_1 = yield step_1
value_2 = yield step_2(value_1)
return yield(step_3(value_2))
end
def step_1
if condition
Success(some_value)
else
Failure(:some_error_code)
end
end
def step_2
if condition
Success(some_value)
else
Failure(:some_error_code_2)
end
end
Then in the controller you can match the failures using dry-matcher:
matcher.(result) do |m|
m.success do |v|
# ok
end
m.failure :some_error_code do |v|
halt 400
end
m.failure :some_error_2 do |v|
halt 422
end
end
The matcher may be defined in the prepend code for all controllers, so it's easy to remove the code duplication.
Hanami way is validating input parameters before each request handler. So, ClientError must be identified always before actions logic.
halt 400 unless params.valid? #halt ClientError
#your code
result = users_show_interactor(id: params[:id])
halt 422 if result.failure? #ServerError
halt 404 unless result.user
#user = result.user
I normally go about by raising scoped errors in the interactor, then the controller only has to rescue the errors raised by the interactor and return the appropriate status response.
Interactor:
module Users
class Delete
include Tnt::Interactor
class UserNotFoundError < ApplicationError; end
def call(report_id)
deleted = UserRepository.new.delete(report_id)
fail_with!(UserNotFoundError) unless deleted
end
end
end
Controller:
module Api::Controllers::Users
class Destroy
include Api::Action
include Api::Halt
params do
required(:id).filled(:str?, :uuid?)
end
def call(params)
halt 422 unless params.valid?
Users::Delete.new.call(params[:id])
rescue Users::Delete::UserNotFoundError => e
halt_with_status_and_error(404, e)
end
end
end
fail_with! and halt_with_status_and_error are helper methods common to my interactors and controllers, respectively.
# module Api::Halt
def halt_with_status_and_error(status, error = ApplicationError)
halt status, JSON.generate(
errors: [{ key: error.key, message: error.message }],
)
end
# module Tnt::Interactor
def fail_with!(exception)
#__result.fail!
raise exception
end

How should I return Sinatra HTTP errors from inside a class where HALT is not available?

I have a large backend API for my native app that's built in Sinatra, that also serves some admin web pages. I'm trying to dry up the codebase and refactor code into classes inside the lib directory.
My API clients expect a status and a message, such as 200 OK, or 404 Profile Not Found. I'd usually do this with something like halt 404, 'Profile Not Found'.
What's the easiest way of halting with an HTTP status code and a message from inside a class?
Old Wet Code
post '/api/process_something'
halt 403, 'missing profile_id' unless params[:profile_id].present?
halt 404, 'offer not found' unless params[:offer_id].present?
do_some_processing
200
end
New Dry Code
post '/api/process_something'
offer_manager = OfferManager.new
offer_manager.process_offer(params: params)
end
offer_manager.rb
class OfferManager
def process_offer(params:)
# halt 403, 'missing profile_id' unless params[:profile_id].present?
# halt 404, 'offer not found' unless params[:offer_id].present?
# halt doesn't work from in here
do_some_processing
200
end
end
This question is probably better for CodeReview but one approach you can see in an OO design here is a 'halt' path and a 'happy' path. Your class just needs to implement a few methods to help this be consistent across all your sinatra routes and methods.
Here's one approach, and it would be easy to adopt this kind of interface across other classes using inheritance.
post '/api/process_something' do
offer_manager = OfferManager.new(params)
# error guard clause
halt offer_manager.status, offer_manager.halt_message if offer_manager.halt?
# validations met, continue to process
offer_manager.process_offer
# return back 200
offer_manager.status
end
class OfferManager
attr_reader :status, :params, :halt_message
def initialize(params)
#params = params
validate_params
end
def process_offer
do_some_processing
end
def halt?
# right now we just know missing params is one error to halt on but this is where
# you could implement more business logic if need be
missing_params?
end
private
def validate_params
if missing_params?
#status = 404
#halt_message = "missing #{missing_keys.join(", ")} key(s)"
else
#status = 200
end
end
def do_some_processing
# go do other processing
end
def missing_params?
missing_keys.size > 0
end
def missing_keys
expected_keys = [:profile_id, :offer_id]
params.select { |k, _| !expected_keys.has_key?(k) }
end
end

How do I write functional test for this

I am pretty new to rspec. How do I write functional test for following piece of code.
class FooController < ApplicationController
 def new
#title = "Log in to Mint"
#msg = session[:msg]
session[:msg] = nil
end
end
 
How about something like this:
describe FooController do
describe "GET new" do
it "assigns 'Log in to Mint' to #title" do
get :new
assigns(:title).should == "Log in to Mint"
end
it "assigns message session to #msg" do
session[:msg] = "a message"
get :new
assigns(:msg).should == "a message"
end
it "sets message session to nil" do
get :new
session[:msg].should be_nil
end
end
end
See also: Rspec: testing assignment of instance variable

How can I clear class variables between rspec tests in ruby

I have the following class:
I want to ensure the class url is only set once for all instances.
class DataFactory
##url = nil
def initialize()
begin
if ##url.nil?
Rails.logger.debug "Setting url"
##url = MY_CONFIG["my value"]
end
rescue Exception
raise DataFactoryError, "Error!"
end
end
end
I have two tests:
it "should log a message" do
APP_CONFIG = {"my value" => "test"}
Rails.stub(:logger).and_return(logger_mock)
logger_mock.should_receive(:debug).with "Setting url"
t = DataFactory.new
t = nil
end
it "should throw an exception" do
APP_CONFIG = nil
expect {
DataFactory.new
}.to raise_error(DataFactoryError, /Error!/)
end
The problem is the second test never throws an exception as the ##url class variable is still set from the first test when the second test runs.
Even though I have se the instance to nil at the end of the first test garbage collection has not cleared the memory before the second test runs:
Any ideas would be great!
I did hear you could possibly use Class.new but I am not sure how to go about this.
describe DataFactory
before(:each) { DataFactory.class_variable_set :##url, nil }
...
end
Here is an alternative to the accepted answer, which while wouldn't solve your particular example, I'm hoping it might help a few people with a question in the same vein. If the class in question doesn't specify a default value, and remains undefined until set, this seems to work:
describe DataFactory
before(:each) do
DataFactory.remove_class_variable :##url if DataFactory.class_variable_defined? :##url
end
...
end
Works for me with a class with something more like:
def initialize
##url ||= MY_CONFIG["my value"]
...
end

Simple Rspec test fails - for what reason?

Coding one of my first rspec tests. headers == nil prints true, but the next test line headers should be_nil fails. Why?
require 'net/http'
$url_arr = []
$url_arr << ...
$url_arr << ...
$url_arr << ...
module NetHelpers
def get_headers(uri)
Net::HTTP.get_response(URI.parse(uri)).get_fields('Set-Cookie')
end
end
describe "new script" do
include NetHelpers
$url_arr.each do |uri|
it "should not return cookies" do
headers = get_headers(uri)
p "==========> #{headers == nil}"
headers should be_nil
end
end
end
Also, the output is
got: "new script" (using ==)
Why "new script" is printed, while headers really contains nil?
Try
headers.should be_nil
instead.

Resources