Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent instantiation of abstract classes whose superclass isn't Object #10

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/reek.yml
Expand Up @@ -47,7 +47,7 @@ UncommunicativeModuleName:
NestedIterators:
ignore_iterators: []
exclude: [
'AbstractType::ClassMethods#create_abstract_singleton_method'
'AbstractType::AbstractMethodDeclarations#create_abstract_singleton_method'
]
enabled: true
max_allowed_nesting: 1
Expand Down
40 changes: 22 additions & 18 deletions lib/abstract_type.rb
Expand Up @@ -13,31 +13,35 @@ module AbstractType
# @api private
def self.included(descendant)
super
descendant.extend(ClassMethods)
create_new_method(descendant)
descendant.extend(AbstractMethodDeclarations)
end

private_class_method :included

module ClassMethods

# Instantiate a new object
#
# Ensures that the instance cannot be of the abstract type
# and must be a descendant.
#
# @example
# object = AbstractType.new
#
# @return [Object]
#
# @api public
def new(*)
if superclass.equal?(Object)
# Define the new method on the abstract type
#
# Ensures that the instance cannot be of the abstract type
# and must be a descendant.
#
# @param [Class] abstract_class
#
# @return [undefined]
#
# @api private
def self.create_new_method(abstract_class)
abstract_class.define_singleton_method(:new) do |*args, &block|
if equal?(abstract_class)
raise NotImplementedError, "#{inspect} is an abstract type"
else
super
super(*args, &block)
end
end
end

private_class_method :create_new_method

module AbstractMethodDeclarations

# Create abstract instance methods
#
Expand Down Expand Up @@ -109,5 +113,5 @@ def create_abstract_instance_method(name)
end
end

end # module ClassMethods
end # module AbstractMethodDeclarations
end # module AbstractType
20 changes: 20 additions & 0 deletions spec/shared/create_new_method_shared_spec.rb
@@ -0,0 +1,20 @@
# encoding: utf-8

shared_examples 'AbstractType.create_new_method' do
context 'called on a subclass' do
let(:object) { Class.new(abstract_type) }

it { should be_instance_of(object) }
end

context 'called on the class' do
let(:object) { abstract_type }

specify do
expect { subject }.to raise_error(
NotImplementedError,
"#{object} is an abstract type"
)
end
end
end
Expand Up @@ -2,7 +2,7 @@

require 'spec_helper'

describe AbstractType::ClassMethods, '#abstract_method' do
describe AbstractType::AbstractMethodDeclarations, '#abstract_method' do
subject { object.abstract_method(:some_method) }

let(:object) { Class.new { include AbstractType } }
Expand Down
Expand Up @@ -2,7 +2,8 @@

require 'spec_helper'

describe AbstractType::ClassMethods, '#abstract_singleton_method' do
describe AbstractType::AbstractMethodDeclarations,
'#abstract_singleton_method' do
subject { object.abstract_singleton_method(:some_method) }

let(:object) { Class.new { include AbstractType } }
Expand Down
Expand Up @@ -9,9 +9,17 @@
let(:klass) { Class.new }

it 'extends the klass' do
expect(klass.singleton_class).to_not include(described_class::ClassMethods)
expect(klass.singleton_class)
.to_not include(described_class::AbstractMethodDeclarations)
klass.send(:include, subject)
expect(klass.singleton_class).to include(described_class::ClassMethods)
expect(klass.singleton_class)
.to include(described_class::AbstractMethodDeclarations)
end

it 'overrides the new singleton method' do
expect(klass.method(:new).owner).to eq(Class)
klass.send(:include, subject)
expect(klass.method(:new).owner).to eq(klass.singleton_class)
end

it 'delegates to the ancestor' do
Expand Down
59 changes: 0 additions & 59 deletions spec/unit/abstract_type/class_methods/new_spec.rb

This file was deleted.

54 changes: 54 additions & 0 deletions spec/unit/abstract_type/module_methods/create_new_method_spec.rb
@@ -0,0 +1,54 @@
# encoding: utf-8

require 'spec_helper'

describe AbstractType, '.create_new_method' do
context 'with arguments' do
subject { object.new(:foo) }

let(:abstract_type) do
Class.new do
include AbstractType

def initialize(foo)
@foo = foo
end
end
end

it_behaves_like 'AbstractType.create_new_method'
end

context 'with a block' do
subject { object.new(:foo) { nil } }

let(:abstract_type) do
Class.new do
include AbstractType

def initialize(foo)
@foo = foo
yield
end
end
end

it_behaves_like 'AbstractType.create_new_method'
end

context 'without arguments' do
subject { object.new }

let(:abstract_type) { Class.new { include AbstractType } }

it_behaves_like 'AbstractType.create_new_method'
end

context 'on an class that doesn\'t have Object as its superclass' do
subject { object.new }

let(:abstract_type) { Class.new(RuntimeError) { include AbstractType } }

it_behaves_like 'AbstractType.create_new_method'
end
end