I have created the following class
class Contact
def initialize(id, name, phone)
#id = id
#name = name
#phone = phone
end
def to_json(*a)
{
json_class: self.class.name,
data: { id: #id, name: #name, phone: #phone }
}.to_json(*a)
end
def self.json_create(o)
new( o[:data][:id], o[:data][:name], o[:data][:phone] )
end
end
I can now convert it to json using this
Contact.new(1,'nik',10).to_json
=> "{\"json_class\":\"Contact\",\"data\":{\"id\":1,\"name\":\"nik\",\"phone\":10}}"
But it explodes with an error when I call JSON.parse on the it.
JSON.parse(Contact.new(1,'nik',10).to_json)
NoMethodError: undefined method `[]' for nil:NilClass
from (irb):44:in `json_create'
I picked up the syntax from this tutorial.
Get rid of the symbols in your json_create method.
def self.json_create(o)
new( o['data']['id'], o['data']['name'], o['data']['phone'] )
end
Use as_json instead of to_json.
Related
I've got pure Ruby class which does the parsing of the JSON file to the expected hash (located in src/parsers/incoming_events/create_quiz.rb). I want to test this class with Minitest like below:
# test/src/parsers/incoming_events/create_quiz_test.rb
require 'minitest/autorun'
require_relative '../../../../src/parsers/incoming_events/create_quiz'
module Parsers
module IncomingEvents
class CreateQuiz < ActiveSupport::TestCase
test 'parse JSON to expected format' do
assert_equal expected_hash, service.call
end
private
def service
#service ||= ::Parsers::IncomingEvents::CreateQuiz.new(payload: payload)
end
def payload
{
'quiz' =>
{
'first_name' => 'john',
'last_name' => 'doe',
'ssn' => '1234',
}
}.to_json
end
def expected_hash
{ 'name': 'john doe' }
end
end
end
end
When I'm trying to run above code via ruby test/src/parsers/incoming_events/create_quiz_test.rb I'm getting below error:
test/src/parsers/incoming_events/create_quiz_test.rb:11:in `<module:IncomingEvents>': uninitialized constant Parsers::IncomingEvents::ActiveSupport (NameError)
from test/src/parsers/incoming_events/create_quiz_test.rb:10:in `<module:Parsers>'
from test/src/parsers/incoming_events/create_quiz_test.rb:9:in `<main>'
I've got a class where in initializer I need to call instance variable from parsed params:
class PrintResults
include SortResults
attr_accessor :views_hash
def initialize(parser)
#parser = parser
#views_hash = parser.page_views
end
I want to test attributes accessors, I tried something below:
RSpec.describe PrintResults do
subject { described_class.new(views_hash) }
describe 'attributes accessors' do
let(:accessors) { double(page_views: { '/that_70s_show' => ['111.111.111.111'] }) }
it 'should have views hash' do
subject.views_hash = accessors
expect(subject.views_hash).to eq(['111.111.111.111'])
end
end
but I'm getting an error:
1) PrintResults attributes accessors should have views hash
Failure/Error: expect(subject.views_hash).to eq(['111.111.111.111'])
expected: ["111.111.111.111"]
got: #<Double (anonymous)>
(compared using ==)
Diff:
## -1 +1 ##
-["111.111.111.111"]
+#<Double (anonymous)>
You assign your test double directly to the attribute that is returned instead of using the initialize method.
Instead of
subject { described_class.new(views_hash) }
describe 'attributes accessors' do
let(:accessors) { double(page_views: { '/that_70s_show' => ['111.111.111.111'] }) }
it 'should have views hash' do
subject.views_hash = accessors
expect(subject.views_hash).to eq(['111.111.111.111'])
end
end
use
subject { described_class.new(parser) }
describe 'attributes accessors' do
let(:parser) { double(page_views: { '/that_70s_show' => ['111.111.111.111'] }) }
it 'should have views hash' do
expect(subject.views_hash).to eq('/that_70s_show' => ['111.111.111.111'])
end
end
I've used this series as a starting point for a Rails backend for a work portfolio website. Adapting it has been mostly straightforward, and it's doing what I want it to. The one big problem is that the 'index' and 'show' (read actions) should be available without authentication, while 'create', 'update', and 'delete' (write actions) should require a valid JWT.
Following the approach used to exclude the signup and login routes from authentication, I've tried
skip_before_action :authorize_request, only: [:index, :show]
in the appropriate controller. This will however crash the application, with
NoMethodError (undefined method `works' for nil:NilClass):
app/controllers/works_controller.rb:10:in `index'
While the problem seems apparent - if skipping the authentication action the class doesn't get instantiated - the fix isn't, to me at least. Could anyone please help?
The code for the project is here.
Application controller
class ApplicationController < ActionController::API
include Response
include ExceptionHandler
# called before every action on controllers
before_action :authorize_request
attr_reader :current_user
private
# Check for valid request token and return user
def authorize_request
#current_user = (AuthorizeApiRequest.new(request.headers).call)[:user]
end
end
'Works' controller
class WorksController < ApplicationController
#skip_before_action :authorize_request, only: [:index, :show]
before_action :set_work, only: [:show, :update, :destroy]
# GET /works
def index
#works = current_user.works
json_response(#works)
end
# POST /works
def create
#work = current_user.works.create!(work_params)
json_response(#work, :created)
end
# GET /works/:id
def show
json_response(#work)
end
# PUT /works/:id
def update
#work.update(work_params)
head :no_content
end
# DELETE /works/:id
def destroy
#work.destroy
head :no_content
end
private
def work_params
# whitelist params
params.permit(:title, :nature, :role, :client, :timeframe, :description, :images, :url, :blog_post)
end
def set_work
#work = Work.find(params[:id])
end
end
'Users' controller
class UsersController < ApplicationController
skip_before_action :authorize_request, only: :create
def create
user = User.create!(user_params)
auth_token = AuthenticateUser.new(user.username, user.password).call
response = { message: Message.account_created, access_token: auth_token }
json_response(response, :created)
end
def show
json_response(username: current_user.username)
end
private
def user_params
params.permit(
:username,
:password,
:password_confirmation
)
end
end
'Authentication' controller
class AuthenticationController < ApplicationController
skip_before_action :authorize_request, only: :authenticate
# return auth token once user is authenticated
def authenticate
auth_token =
AuthenticateUser.new(auth_params[:username], auth_params[:password]).call
json_response(access_token: auth_token)
end
private
def auth_params
params.permit(:username, :password)
end
end
'AuthenticateUser' helper
class AuthenticateUser
def initialize(username, password)
#username = username
#password = password
end
# Service entry point
def call
JsonWebToken.encode(user_id: user.id) if user
end
private
attr_reader :username, :password
# verify user credentials
def user
user = User.find_by(username: username)
return user if user && user.authenticate(password)
# raise Authentication error if credentials are invalid
raise(ExceptionHandler::AuthenticationError, Message.invalid_credentials)
end
end
'AuthorizeApiRequest' helper
class AuthorizeApiRequest
def initialize(headers = {})
#headers = headers
end
# Service entry point - return valid user object
def call
{
user: user
}
end
private
attr_reader :headers
def user
# check if user is in the database
# memoize user object
#user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
# handle user not found
rescue ActiveRecord::RecordNotFound => e
# raise custom error
raise(
ExceptionHandler::InvalidToken,
("#{Message.invalid_token} #{e.message}")
)
end
# decode authentication token
def decoded_auth_token
#decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
end
# check for token in `Authorization` header
def http_auth_header
if headers['Authorization'].present?
return headers['Authorization'].split(' ').last
end
raise(ExceptionHandler::MissingToken, Message.missing_token)
end
end
'ExceptionHandler' helper
module ExceptionHandler
extend ActiveSupport::Concern
# Define custom error subclasses - rescue catches `StandardErrors`
class AuthenticationError < StandardError; end
class MissingToken < StandardError; end
class InvalidToken < StandardError; end
included do
# Define custom handlers
rescue_from ActiveRecord::RecordInvalid, with: :four_twenty_two
rescue_from ExceptionHandler::AuthenticationError, with: :unauthorized_request
rescue_from ExceptionHandler::MissingToken, with: :four_twenty_two
rescue_from ExceptionHandler::InvalidToken, with: :four_twenty_two
rescue_from ActiveRecord::RecordNotFound do |e|
json_response({ message: e.message }, :not_found)
end
end
private
# JSON response with message; Status code 422 - unprocessable entity
def four_twenty_two(e)
json_response({ message: e.message }, :unprocessable_entity)
end
# JSON response with message; Status code 401 - Unauthorized
def unauthorized_request(e)
json_response({ message: e.message }, :unauthorized)
end
end
The error message states:
NoMethodError (undefined method `works' for nil:NilClass):
app/controllers/works_controller.rb:10:in `index'
Or to translate that, on line 10 of the works_controller.rb file, we're calling a method called works on nil, which is throwing an error.
Assuming line 10 of the works_controller is
#works = current_user.works
Then the error message is telling us that we're calling works on nil, i.e. we have no current_user.
Either where you assign this code is not working properly, or you're accessing this part of the code without signing in and haven't coded around that. Either way, the current_user variable is returning nil and shouldn't be.
I am testing a class's initialization block as below
class A
attr_accessor :client
def initialize(options, configuration)
self.client = B.new(options)
config = C.new(
url: configuration[:url],
headers: configuration[:headers],
username: configuration[:username],
password: configuration[:password]
)
client.configure(config)
end
end
class C
def initialize(options)
# does something with options hash
end
end
class B
def initialize(options)
# does something with options hash
end
def configure(config)
# some configuration with config object
end
end
My test case is as follows:
let(:options) {
{
force_basic_auth: true
}
}
let(:configuration) {
{
url: 'https://localhost:3000',
headers: { awesome: true },
username: 'test',
password: 'pass'
}
}
let(:api_config) {
C.new(configuration)
}
it 'configures object with passed params' do
expect_any_instance_of(B).to receive(:configure)
.with(api_config)
A.new(
options,
configuration
)
end
This fails my test case because the object that is created in the initialization block has a different object_id than the object_id of api_config which I am using in the expectations.
-[#<C:0x00000002b51128 #url="https://localhost:3000", #headers={:awesome=>true}, #username="test", #password="pass">]
+[#<C:0x00000002a1b628 #url="https://localhost:3000", #headers={:awesome=>true}, #username="test", #password="pass">]
Seeing that failure I was thinking whether it's a best practice to pass such objects directly in the initialization block. I mean I can fix it by directly passing the object in the initialization block.
There are many functions which are initializing the A class with a hash option being passed because of which I am doing it in the current way.
Is there a way to expect the contents of the object passed in rspec instead of verifying the objects are same ? Is passing the object directly in the initialization a more better approach ?
You can define arbitrary expectation handling to check the value of the parameter checked (see here):
it 'configures object with passed params' do
expect_any_instance_of(B).to receive(:configure) do |config|
expect(config).to be_a(C)
expect(config.url).to eq(configuration[:url])
expect(config.headers).to eq(configuration[:headers])
# ...
end
A.new(
options,
configuration
)
end
You want the configuration hash (rather than the object) under B.configure(config), so your class has to change slightly to accommodate.
Class file
class A
attr_accessor :client
def initialize(options, configuration)
self.client = B.new(options)
config = C.new(
url: configuration[:url],
headers: configuration[:headers],
username: configuration[:username],
password: configuration[:password]
)
client.configure(config.options)
end
end
class C
attr_reader :options
def initialize(options)
#options = options
end
end
class B
def initialize(options)
# does something with options hash
end
def configure(config)
# some configuration with config object
end
end
Here's what your RSpec code would look like.
describe do
let(:options) do
{
force_basic_auth: true
}
end
let(:configuration) do
{
url: 'https://localhost:3000',
headers: { awesome: true },
username: 'test',
password: 'pass'
}
end
let(:my_a_object) { A.new(options, configuration) }
let(:my_b_object) { B.new(options) }
it 'configures object with passed params' do
allow(B).to receive(:new).with(options).and_return(my_b_object)
expect(my_b_object).to receive(:configure).with(configuration)
my_a_object
end
end
How could I optimize (refactor) this non activerecord based model in rails3 that i have created.?
application.rb contains this:
CS = CloudServers::Connection.new(:username => '<hidden>', :api_key => '<hidden>')
cloudserver.rb (model) contains this:
class Cloudserver
# extend ActiveModel::Naming
attr_reader :id
attr_reader :name
attr_reader :image_id
attr_reader :flavor_id
attr_reader :status
attr_reader :progress
attr_reader :host_id
def initialize(id,name,image_id,flavor_id,status,progress,host_id)
#id = id
#name = name
#image_id = image_id
#flavor_id = flavor_id
#status = status
#progres = progress
#host_id = host_id
end
def self.all
server = CS.servers.map { |i|
new(i[:id],i[:name],i[:imageId],i[:flavorId],i[:status],i[:progress],i[:hostId])
# new(i)
}
end
def self.find(param)
all.detect { |l| l.id == param.to_i } || raise(ActiveRecord::RecordNotFound)
end
# def self.new
# server = CS.create_server(:name => "BOOYA", :imageId => 49, :flavorId => 2, :metadata => {'Luke' => 'Awesome'})
# end
end
FYI i am trying to build rails models for this api:
https://github.com/rackspace/ruby-cloudservers
should I bother or just have the controllers access the CS Object directly
Just for the sake of refactoring, here's a shorter way of writing the same class
class Cloudserver < Struct.new(:id, :name, :image_id, :flavor_id, :status, :progress, :host_id)
class << self
def all
server = CS.servers.map { |i|
new(i[:id],i[:name],i[:imageId],i[:flavorId],i[:status],i[:progress],i[:hostId])
}
end
def find
all.detect { |l| l.id == param.to_i } || raise(ActiveRecord::RecordNotFound)
end
end
end