Skip to content

Commit

Permalink
Fix to raise NameError via configuration and error message check (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
tian-im committed Apr 22, 2024
1 parent 29e3387 commit 824b535
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ jobs:
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
# @see https://github.com/openstreetmap/openstreetmap-website/issues/4364
rubygems: latest

- name: Rubocop Check
if: ${{ github.event_name == 'pull_request' }}
Expand Down
5 changes: 5 additions & 0 deletions spec/dummy/app/not_autoloaded_models/broken_product.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class BrokenProduct
missing_class_method_called
end
89 changes: 89 additions & 0 deletions spec/wallaby-core/lib/wallaby/classifier_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# frozen_string_literal: true

require 'rails_helper'

describe Wallaby::Classifier do
describe '.class_name_of' do
subject(:class_name) { described_class.class_name_of(object) }

context 'when object is class' do
let(:object) { Class }

it { is_expected.to eq('Class') }

context 'when object is anonymous class' do
let(:object) { Class.new }

it { is_expected.to be_nil }
end
end

context 'when object is not a class' do
context 'when object is string' do
let(:object) { 'Class' }

it { is_expected.to eq('Class') }

context 'when object is empty string' do
let(:object) { '' }

it { is_expected.to eq('') }
end
end

context 'when object is symbol' do
let(:object) { :Class }

it { is_expected.to eq('Class') }
end

context 'when object is integer' do
let(:object) { 1 }

it { is_expected.to eq('1') }
end

context 'when object is nil' do
let(:object) { nil }

it { is_expected.to be_nil }
end
end
end

describe '.to_class' do
subject(:klass) { described_class.to_class(class_name, raising: raising) }

let(:raising) { false }

context 'when class exists' do
let(:class_name) { 'Product' }

it { is_expected.to eq(Product) }

context 'when class has missing method error' do
let(:class_name) { 'BrokenProduct' }

it { expect { subject }.to raise_error(NameError, "undefined local variable or method `missing_class_method_called' for BrokenProduct:Class") }
end
end

context 'when class not exists' do
let(:class_name) { 'UnknownProduct' }

it { is_expected.to be_nil }

context 'when raising is true' do
let(:raising) { true }

it { expect { subject }.to raise_error(NameError, /uninitialized constant/) }
end
end

context 'when class name is empty string' do
let(:class_name) { '' }

it { is_expected.to be_nil }
end
end
end
6 changes: 6 additions & 0 deletions spec/wallaby-core/lib/wallaby/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
:base_controller, ::ApplicationController, ::ActionController::Base
end

describe '#raise_on_name_error' do
it_behaves_like \
'has attribute with default value',
:raise_on_name_error, nil, true
end

describe '#security' do
it 'returns the configuration of security' do
expect(subject.security).to be_a described_class::Security
Expand Down
24 changes: 16 additions & 8 deletions wallaby-core/lib/wallaby/classifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,32 @@ module Classifier

# Convert Class to String. If not Class, unchanged.
# @param klass [Object]
# @return [String] if klass is a Class
# @return [Object] if klass is not a Class
# @return [String] if klass is not nil
# @return [nil] if klass is nil or klass is an anonymous Class
def class_name_of(klass)
klass.try(:name) || klass || nil
klass.is_a?(Class) ? klass.try(:name) : klass.try(:to_s)
end

# Convert String to Class. If not String, unchanged.
# @param name [Object]
# @return [Class] if name is a Class
# @return [Object] if name is not a String
# @return [nil] if class cannot be found
def to_class(name)
return name unless name.is_a? String
def to_class(name, raising: Wallaby.configuration.raise_on_name_error)
return name unless name.is_a?(String)
# blank string will lead to NameError `wrong constant name`
return if name.blank?

# NOTE: DO NOT try to use const_defined? and const_get EVER.
# This is Rails, use constantize
# NOTE: DO NOT try to use `const_defined?` and `const_get` EVER.
# Rails does all the class loading magics using `constantize`
name.constantize
rescue NameError
rescue NameError => e
raise if raising

uninitialized = e.message.start_with?('uninitialized constant')
raise unless uninitialized

# block to handle this missing constant, e.g. use a default class or log useful instruction
yield(name) if block_given?
end
end
Expand Down
4 changes: 4 additions & 0 deletions wallaby-core/lib/wallaby/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ module Wallaby
class Configuration
include Classifier

# @!attribute [w] raise_on_name_error
attr_accessor :raise_on_name_error

# @!attribute [w] logger
attr_writer :logger

Expand Down Expand Up @@ -101,6 +104,7 @@ def models
Deprecator.alert 'config.models.presence', from: '0.3.0', alternative: <<~INSTRUCTION
Please use controller_class.models instead.
INSTRUCTION
@models ||= Models.new
end

# To globally configure the models that Wallaby should handle.
Expand Down

0 comments on commit 824b535

Please sign in to comment.