feature_envy 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -0
- data/lib/feature_envy/final_class.rb +24 -18
- data/lib/feature_envy/lazy_accessor.rb +236 -0
- data/lib/feature_envy/object_literal.rb +80 -0
- data/lib/feature_envy/version.rb +1 -1
- data/lib/feature_envy.rb +4 -2
- metadata +12 -16
- data/test/final_class_test.rb +0 -43
- data/test/internal_test.rb +0 -25
- data/test/support/assertions.rb +0 -11
- data/test/test_helper.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7675c689bf69e95854627b7dc5e28affa5a2fed1bcb46f8255a80217c6abdea
|
4
|
+
data.tar.gz: 17a050ab57c4af67a4c967756b4704220e8511940498cb5f66c9989b6ecb2225
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 073e92360b8fbcaa1b6ce67a68f37adfdc4282c69bc15be4cc1e4119a99671958c175597309169a9e122008371a70814dd3e0836250185bad95374a241b6dbd5
|
7
|
+
data.tar.gz: 2f3e21f91d1be99f5e00251568c87cb62131e8666af9c2a2d9685d42832d20cefb36a1f8ba87705c8b4f0a4fc3ddad10177c6caf473dcd6001156e2113f07804
|
data/README.md
CHANGED
@@ -5,6 +5,12 @@ Feature Envy enhances Ruby with features found in other programming languages.
|
|
5
5
|
**WARNING**: This gem is still in development and a stable release hasn't been
|
6
6
|
made yet. Bug reports and contributions are welcome!
|
7
7
|
|
8
|
+
Supported features:
|
9
|
+
|
10
|
+
- Final classes
|
11
|
+
- Thread-safe lazy accessors
|
12
|
+
- Object literals
|
13
|
+
|
8
14
|
## Installation
|
9
15
|
|
10
16
|
You can install the gem by running `gem install feature_envy` or adding it to
|
@@ -3,21 +3,35 @@
|
|
3
3
|
module FeatureEnvy
|
4
4
|
# Final classes.
|
5
5
|
#
|
6
|
+
# ### Definition
|
7
|
+
#
|
6
8
|
# A final class is a class that cannot be inherited from. In other words, a
|
7
|
-
# final class enforces the invariant that it has no subclasses.
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
9
|
+
# final class enforces the invariant that it has no subclasses.
|
10
|
+
#
|
11
|
+
# ### Applications
|
12
|
+
#
|
13
|
+
# Preventing subclassing of classes that weren't specifically designed for
|
14
|
+
# handling it.
|
11
15
|
#
|
12
|
-
#
|
16
|
+
# ### Usage
|
13
17
|
#
|
14
|
-
# 1.
|
18
|
+
# 1. Enable the feature in a specific class via `extend Feature::FinalClass`.
|
19
|
+
# The class has been marked final and there's nothing else to do.
|
20
|
+
# Alternatively, ...
|
21
|
+
# 2. Enable the feature in a specific **scope** using a refinement via
|
22
|
+
# `using FeatureEnvy::FinalClass` and call `final!` in all classes that
|
15
23
|
# should be marked final.
|
16
|
-
# 2. Extending the class with {FeatureEnvy::FinalClass}.
|
17
24
|
#
|
18
|
-
#
|
25
|
+
# ### Discussion
|
19
26
|
#
|
20
|
-
#
|
27
|
+
# A class in Ruby can be made final by raising an error in its `inherited`
|
28
|
+
# hook. This is what this module does. However, this is **not** enough to
|
29
|
+
# guarantee that no subclasses will be created. Due to Ruby's dynamic nature
|
30
|
+
# it'd be possible to define a class, subclass, and then reopen the class and
|
31
|
+
# mark it final. This edge **is** taken care of and would result in an
|
32
|
+
# exception.
|
33
|
+
#
|
34
|
+
# @example
|
21
35
|
# module Models
|
22
36
|
# # Use the refinement within the module, so that all classes defined
|
23
37
|
# # within support the final! method.
|
@@ -28,14 +42,6 @@ module FeatureEnvy
|
|
28
42
|
# final!
|
29
43
|
# end
|
30
44
|
# end
|
31
|
-
#
|
32
|
-
# @example Final classes without refinements
|
33
|
-
# module Models
|
34
|
-
# class User < Base
|
35
|
-
# # Mark the User class final.
|
36
|
-
# extend FeatureEnvy::FinalClass
|
37
|
-
# end
|
38
|
-
# end
|
39
45
|
module FinalClass
|
40
46
|
# An error representing a final class invariant violation.
|
41
47
|
class Error < FeatureEnvy::Error
|
@@ -72,7 +78,7 @@ module FeatureEnvy
|
|
72
78
|
subclasses = Internal.subclasses final_class
|
73
79
|
return if subclasses.empty?
|
74
80
|
|
75
|
-
raise Error.new(final_class
|
81
|
+
raise Error.new(final_class:, subclasses:)
|
76
82
|
end
|
77
83
|
|
78
84
|
# Determines whether a given class is marked final.
|
@@ -0,0 +1,236 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FeatureEnvy
|
4
|
+
# Lazy accessors.
|
5
|
+
#
|
6
|
+
# ### Definition
|
7
|
+
#
|
8
|
+
# A lazy **attribute** is an attribute whose value is determined on first
|
9
|
+
# access. The same value is used on all subsequent access without running the
|
10
|
+
# code to determine its value again.
|
11
|
+
#
|
12
|
+
# Lazy attributes are impossible in Ruby (see the discussion below), but
|
13
|
+
# lazy **accessors** are, and are provided by this module.
|
14
|
+
#
|
15
|
+
# ### Applications
|
16
|
+
#
|
17
|
+
# Deferring expensive computations until needed and ensuring they're performed
|
18
|
+
# at most once.
|
19
|
+
#
|
20
|
+
# ### Usage
|
21
|
+
#
|
22
|
+
# 1. Enable the feature in a specific class via `extend FeatureEnvy::LazyAccessor` or ...
|
23
|
+
# 2. Enable the feature in a specific **scope** (given module and all modules
|
24
|
+
# and classes contained within) using a refinement via `using FeatureEnvy::LazyAccessor`.
|
25
|
+
# 3. Define one or more lazy attributes via `lazy(:name) { definition }`.
|
26
|
+
# 4. Do **NOT** read or write to the underlying attribute, e.g. `@name`;
|
27
|
+
# always use the accessor method.
|
28
|
+
# 5. Lazy accessors are **thread-safe**: the definition block will be called
|
29
|
+
# at most once; if two threads call the accessor for the first time one
|
30
|
+
# will win the race to run the block and the other one will wait and reuse
|
31
|
+
# the value produced by the first.
|
32
|
+
# 6. It's impossible to reopen a class and add new lazy accessors after **the
|
33
|
+
# any class using lazy accessors has been instantiated**. Doing so would
|
34
|
+
# either make the code thread-unsafe or require additional thread-safety
|
35
|
+
# measures, potentially reducing performance.
|
36
|
+
#
|
37
|
+
# ### Discussion
|
38
|
+
#
|
39
|
+
# Ruby attributes start with `@` and assume the default value of `nil` if not
|
40
|
+
# assigned explicitly. Real lazy attributes are therefore impossible to
|
41
|
+
# implement in Ruby. Fortunately **accessors** (i.e. methods used to obtain
|
42
|
+
# attribute values) are conceptually close to attributes and can be made lazy.
|
43
|
+
#
|
44
|
+
# A naive approach found in many Ruby code bases looks like this:
|
45
|
+
#
|
46
|
+
# ```ruby
|
47
|
+
# def highest_score_user
|
48
|
+
# @highest_score_user ||= find_highest_score_user
|
49
|
+
# end
|
50
|
+
# ```
|
51
|
+
#
|
52
|
+
# It's simple but suffers from a serious flaw: if `nil` is assigned to the
|
53
|
+
# attribute then subsequent access will result in another attempt to determine
|
54
|
+
# the attribute's value.
|
55
|
+
#
|
56
|
+
# The proper approach is much more verbose:
|
57
|
+
#
|
58
|
+
# ```ruby
|
59
|
+
# def highest_score_user
|
60
|
+
# # If the underlying attribute is defined then return it no matter its value.
|
61
|
+
# return @highest_score_user if defined?(@highest_score_user)
|
62
|
+
#
|
63
|
+
# @highest_score_user = find_highest_score_user
|
64
|
+
# end
|
65
|
+
# ```
|
66
|
+
#
|
67
|
+
# ### Implementation Notes
|
68
|
+
#
|
69
|
+
# 1. Defining a lazy accessor defines a method with that name. The
|
70
|
+
# corresponding attribute is **not** set before the accessor is called for
|
71
|
+
# the first time.
|
72
|
+
# 2. The first time a lazy accessor is added to a class a special module
|
73
|
+
# is included into it. It provides an `initialize` method that sets
|
74
|
+
# `@lazy_attributes_mutexes` - a hash of mutexes protecting each lazy
|
75
|
+
# accessor.
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# class User
|
79
|
+
# # Enable the feature via refinements.
|
80
|
+
# using FeatureEnvy::LazyAccessor
|
81
|
+
#
|
82
|
+
# # Lazy accessors can return nil and have it cached and reused in
|
83
|
+
# # subsequent calls.
|
84
|
+
# lazy(:full_name) do
|
85
|
+
# "#{first_name} #{last_name}" if first_name && last_name
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# # Lazy accessors are regular methods, that follow a specific structure,
|
89
|
+
# # so they can call other methods, including other lazy accessors.
|
90
|
+
# lazy(:letter_ending) do
|
91
|
+
# if full_name
|
92
|
+
# "Sincerely,\n#{full_name}"
|
93
|
+
# else
|
94
|
+
# "Sincerely"
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
# end
|
98
|
+
module LazyAccessor
|
99
|
+
# A class representing an error related to lazy-accessors.
|
100
|
+
class Error < FeatureEnvy::Error; end
|
101
|
+
|
102
|
+
refine Class do
|
103
|
+
def lazy name, &definition
|
104
|
+
LazyAccessor.define self, name, &definition
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Defines a lazy accessor.
|
109
|
+
#
|
110
|
+
# The `definition` block will be called once when the accessor is used for
|
111
|
+
# the first time. Its value is returned and cached for subsequent accessor
|
112
|
+
# use.
|
113
|
+
#
|
114
|
+
# @param name [String|Symbol] accessor name.
|
115
|
+
# @return [Array<Symbol>] the array containing the accessor name as a
|
116
|
+
# symbol; this is motivated by the built-in behavior of `attr_reader` and
|
117
|
+
# other built-in accessor definition methods.
|
118
|
+
# @yieldreturn the value to store in the underlying attribute and return on
|
119
|
+
# subsequent accessor use.
|
120
|
+
def lazy name, &definition
|
121
|
+
LazyAccessor.define self, name, &definition
|
122
|
+
end
|
123
|
+
|
124
|
+
# A class for creating mutexes for classes that make use of lazy accessors.
|
125
|
+
#
|
126
|
+
# The class keeps a hash that maps modules and classes to arrays of lazy
|
127
|
+
# accessor names defined therein.
|
128
|
+
#
|
129
|
+
# @private
|
130
|
+
class MutexFactory
|
131
|
+
def initialize
|
132
|
+
@mutexes_by_class = Hash.new { |hash, klass| hash[klass] = [] }
|
133
|
+
end
|
134
|
+
|
135
|
+
# Register a new lazy attribute.
|
136
|
+
#
|
137
|
+
# @return [Symbol] The name of the mutex corresponding to the specified
|
138
|
+
# lazy accessor.
|
139
|
+
#
|
140
|
+
# @private
|
141
|
+
def register klass, lazy_accessor_name
|
142
|
+
ObjectSpace.each_object(klass) do # rubocop:disable Lint/UnreachableLoop
|
143
|
+
raise Error.new(<<~ERROR)
|
144
|
+
An instance of #{klass.name} has been already created, so it's no longer
|
145
|
+
possible to define a new lazy accessor, due to thread-safety reasons.
|
146
|
+
ERROR
|
147
|
+
end
|
148
|
+
|
149
|
+
mutex_name = :"@#{lazy_accessor_name}_mutex"
|
150
|
+
@mutexes_by_class[klass] << mutex_name
|
151
|
+
mutex_name
|
152
|
+
end
|
153
|
+
|
154
|
+
# Create mutexes for lazy accessor supported on a given instance.
|
155
|
+
#
|
156
|
+
# @private
|
157
|
+
def initialize_mutexes_for instance
|
158
|
+
current_class = instance.class
|
159
|
+
while current_class
|
160
|
+
@mutexes_by_class[current_class].each do |mutex_name|
|
161
|
+
instance.instance_variable_set mutex_name, Thread::Mutex.new
|
162
|
+
end
|
163
|
+
current_class = current_class.superclass
|
164
|
+
end
|
165
|
+
|
166
|
+
instance.class.included_modules.each do |mod|
|
167
|
+
@mutexes_by_class[mod].each do |mutex_name|
|
168
|
+
instance.instance_variable_set mutex_name, Thread::Mutex.new
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
private_constant :MutexFactory
|
174
|
+
|
175
|
+
@mutex_factory = MutexFactory.new
|
176
|
+
|
177
|
+
class << self
|
178
|
+
# A mutex factory used by the lazy accessors feature.
|
179
|
+
#
|
180
|
+
# @private
|
181
|
+
attr_reader :mutex_factory
|
182
|
+
|
183
|
+
# Defines a lazy accessor.
|
184
|
+
#
|
185
|
+
# Required to share the code between the extension and refinement.
|
186
|
+
#
|
187
|
+
# @private
|
188
|
+
def define klass, name, &definition
|
189
|
+
name = name.to_sym
|
190
|
+
variable_name = :"@#{name}"
|
191
|
+
mutex_name = LazyAccessor.mutex_factory.register klass, name
|
192
|
+
|
193
|
+
klass.class_eval do
|
194
|
+
# Include the lazy accessor initializer to ensure state related to
|
195
|
+
# lazy accessors is initialized properly. There's no need to include
|
196
|
+
# this module more than once.
|
197
|
+
#
|
198
|
+
# Question: is the inclusion check required? Brief testing indicates
|
199
|
+
# it's not.
|
200
|
+
if !include? Initialize
|
201
|
+
include Initialize
|
202
|
+
end
|
203
|
+
|
204
|
+
[
|
205
|
+
define_method(name) do
|
206
|
+
mutex = instance_variable_get(mutex_name)
|
207
|
+
if mutex # rubocop:disable Style/SafeNavigation
|
208
|
+
mutex.synchronize do
|
209
|
+
if instance_variable_defined?(mutex_name)
|
210
|
+
instance_variable_set variable_name, instance_eval(&definition)
|
211
|
+
remove_instance_variable mutex_name
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
instance_variable_get variable_name
|
217
|
+
end
|
218
|
+
]
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# A module included in all classes that use lazy accessors responsible for
|
224
|
+
# initializing a hash of mutexes that guarantee thread-safety on first call.
|
225
|
+
#
|
226
|
+
# @private
|
227
|
+
module Initialize
|
228
|
+
def initialize ...
|
229
|
+
super
|
230
|
+
|
231
|
+
LazyAccessor.mutex_factory.initialize_mutexes_for self
|
232
|
+
end
|
233
|
+
end
|
234
|
+
private_constant :Initialize
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FeatureEnvy
|
4
|
+
# Object literals.
|
5
|
+
#
|
6
|
+
# ### Definition
|
7
|
+
#
|
8
|
+
# An expression that results in creating of an object with a predefined set of
|
9
|
+
# attributes and methods, without having to define and instantiated a class.
|
10
|
+
#
|
11
|
+
# ### Applications
|
12
|
+
#
|
13
|
+
# Defining singleton objects, both state and methods, without having to define
|
14
|
+
# their class explicitly.
|
15
|
+
#
|
16
|
+
# ### Usage
|
17
|
+
#
|
18
|
+
# 1. Enable the feature in a specific class via `include FeatureEnvy::ObjectLiteral`
|
19
|
+
# or ...
|
20
|
+
# 2. Enable the feature in a specific scope via `using FeatureEnvy::ObjectLiteral`.
|
21
|
+
# 3. Create objects by calling `object { ... }`.
|
22
|
+
#
|
23
|
+
# ### Discussion
|
24
|
+
#
|
25
|
+
# Ruby does not offer literals for defining arbitrary objects. Fortunately,
|
26
|
+
# that gap is easy to fill with a helper method. The snippet below is
|
27
|
+
# literally how Feature Envy implements object literals:
|
28
|
+
#
|
29
|
+
# ```ruby
|
30
|
+
# def object &definition
|
31
|
+
# object = Object.new
|
32
|
+
# object.instance_eval &definition
|
33
|
+
# object
|
34
|
+
# end
|
35
|
+
# ```
|
36
|
+
#
|
37
|
+
# All attributes set and methods defined inside the block will be set on
|
38
|
+
# `object`.
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# # Enable the feature in the current scope.
|
42
|
+
# using FeatureEnvy::ObjectLiteral
|
43
|
+
#
|
44
|
+
# # Assuming `database` and `router` are already defined.
|
45
|
+
# app = object do
|
46
|
+
# @database = database
|
47
|
+
# @router = router
|
48
|
+
#
|
49
|
+
# def start
|
50
|
+
# @database.connect
|
51
|
+
# @router.activate
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# app.start
|
56
|
+
module ObjectLiteral
|
57
|
+
refine Kernel do
|
58
|
+
def object ...
|
59
|
+
ObjectLiteral.object(...)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Defines an object literal.
|
64
|
+
#
|
65
|
+
# @yield The block is evaluated in the context of a newly created object.
|
66
|
+
# Instance attributes and methods can be defined within the block and will
|
67
|
+
# end up being set on the resulting object.
|
68
|
+
# @return [Object] The object defined by the block passed to the call.
|
69
|
+
def object ...
|
70
|
+
ObjectLiteral.object(...)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @private
|
74
|
+
def self.object &definition
|
75
|
+
result = Object.new
|
76
|
+
result.instance_eval &definition
|
77
|
+
result
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/feature_envy/version.rb
CHANGED
data/lib/feature_envy.rb
CHANGED
@@ -11,6 +11,8 @@ module FeatureEnvy
|
|
11
11
|
# A base class for all errors raised by Feature Envy.
|
12
12
|
class Error < StandardError; end
|
13
13
|
|
14
|
-
autoload :
|
15
|
-
autoload :
|
14
|
+
autoload :FinalClass, "feature_envy/final_class"
|
15
|
+
autoload :Internal, "feature_envy/internal"
|
16
|
+
autoload :LazyAccessor, "feature_envy/lazy_accessor"
|
17
|
+
autoload :ObjectLiteral, "feature_envy/object_literal"
|
16
18
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: feature_envy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg Navis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -44,28 +44,28 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.
|
47
|
+
version: 1.49.0
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.
|
54
|
+
version: 1.49.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rubocop-minitest
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.
|
61
|
+
version: 0.29.0
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 0.
|
68
|
+
version: 0.29.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rubocop-rake
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -104,17 +104,17 @@ files:
|
|
104
104
|
- lib/feature_envy.rb
|
105
105
|
- lib/feature_envy/final_class.rb
|
106
106
|
- lib/feature_envy/internal.rb
|
107
|
+
- lib/feature_envy/lazy_accessor.rb
|
108
|
+
- lib/feature_envy/object_literal.rb
|
107
109
|
- lib/feature_envy/version.rb
|
108
|
-
- test/final_class_test.rb
|
109
|
-
- test/internal_test.rb
|
110
|
-
- test/support/assertions.rb
|
111
|
-
- test/test_helper.rb
|
112
110
|
homepage: https://github.com/gregnavis/feature_envy
|
113
111
|
licenses:
|
114
112
|
- MIT
|
115
113
|
metadata:
|
116
114
|
homepage_uri: https://github.com/gregnavis/feature_envy
|
117
115
|
source_code_uri: https://github.com/gregnavis/feature_envy
|
116
|
+
bug_tracker_uri: https://github.com/gregnavis/feature_envy/issues
|
117
|
+
documentation_uri: https://rubydoc.info/gems/feature_envy
|
118
118
|
rubygems_mfa_required: 'true'
|
119
119
|
post_install_message:
|
120
120
|
rdoc_options: []
|
@@ -124,7 +124,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
124
124
|
requirements:
|
125
125
|
- - ">="
|
126
126
|
- !ruby/object:Gem::Version
|
127
|
-
version:
|
127
|
+
version: 3.1.0
|
128
128
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
129
|
requirements:
|
130
130
|
- - ">="
|
@@ -135,8 +135,4 @@ rubygems_version: 3.4.6
|
|
135
135
|
signing_key:
|
136
136
|
specification_version: 4
|
137
137
|
summary: Feature Envy enhances Ruby with features inspired by other programming languages
|
138
|
-
test_files:
|
139
|
-
- test/final_class_test.rb
|
140
|
-
- test/internal_test.rb
|
141
|
-
- test/support/assertions.rb
|
142
|
-
- test/test_helper.rb
|
138
|
+
test_files: []
|
data/test/final_class_test.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "test_helper"
|
4
|
-
|
5
|
-
class FinalClassTest < Minitest::Test
|
6
|
-
using FeatureEnvy::FinalClass
|
7
|
-
|
8
|
-
def test_non_refinement_final_class_cannot_be_inherited
|
9
|
-
user = Class.new do
|
10
|
-
extend FeatureEnvy::FinalClass
|
11
|
-
end
|
12
|
-
|
13
|
-
assert_raises FeatureEnvy::FinalClass::Error,
|
14
|
-
"Subclassing a final class should have raised an exception" do
|
15
|
-
Class.new user
|
16
|
-
end
|
17
|
-
assert user.final?,
|
18
|
-
"A final class should have been reported as final but was not"
|
19
|
-
end
|
20
|
-
|
21
|
-
def test_refinement_final_class_cannot_be_inherited
|
22
|
-
user = Class.new do
|
23
|
-
final!
|
24
|
-
end
|
25
|
-
|
26
|
-
assert_raises FeatureEnvy::FinalClass::Error,
|
27
|
-
"Subclassing a final class should have raised an exception" do
|
28
|
-
Class.new user
|
29
|
-
end
|
30
|
-
assert user.final?,
|
31
|
-
"A final class should have been reported as final but was not"
|
32
|
-
end
|
33
|
-
|
34
|
-
def test_error_when_superclass_made_final
|
35
|
-
model = Class.new
|
36
|
-
user = Class.new model # rubocop:disable Lint/UselessAssignment
|
37
|
-
|
38
|
-
assert_raises FeatureEnvy::FinalClass::Error,
|
39
|
-
"Making a superclass final should have raised an exception" do
|
40
|
-
model.extend FeatureEnvy::FinalClass
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
data/test/internal_test.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "test_helper"
|
4
|
-
|
5
|
-
class InternalTest < Minitest::Test
|
6
|
-
class Model; end
|
7
|
-
class User < Model; end
|
8
|
-
class Project < Model; end
|
9
|
-
class AdminUser < User; end
|
10
|
-
|
11
|
-
def test_subclasses
|
12
|
-
assert_equal [User, Project].sort_by(&:name),
|
13
|
-
FeatureEnvy::Internal.subclasses(Model).sort_by(&:name),
|
14
|
-
"All subclasses and no other descendants should have been returned"
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_class_name
|
18
|
-
assert_equal "InternalTest::Model",
|
19
|
-
FeatureEnvy::Internal.class_name(Model)
|
20
|
-
assert_equal "InternalTest::User",
|
21
|
-
FeatureEnvy::Internal.class_name(User)
|
22
|
-
assert_equal "anonymous class",
|
23
|
-
FeatureEnvy::Internal.class_name(Class.new)
|
24
|
-
end
|
25
|
-
end
|
data/test/support/assertions.rb
DELETED
data/test/test_helper.rb
DELETED