adamantium 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.rvmrc +1 -0
- data/.travis.yml +86 -0
- data/Gemfile +54 -0
- data/Guardfile +18 -0
- data/LICENSE +21 -0
- data/README.md +90 -0
- data/Rakefile +9 -0
- data/TODO +1 -0
- data/adamantium.gemspec +25 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/roodi.yml +18 -0
- data/config/site.reek +91 -0
- data/config/yardstick.yml +2 -0
- data/lib/adamantium.rb +249 -0
- data/lib/adamantium/version.rb +3 -0
- data/spec/rcov.opts +7 -0
- data/spec/shared/command_method_behavior.rb +7 -0
- data/spec/shared/each_method_behaviour.rb +15 -0
- data/spec/shared/hash_method_behavior.rb +17 -0
- data/spec/shared/idempotent_method_behavior.rb +7 -0
- data/spec/shared/invertible_method_behaviour.rb +9 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/unit/adamantium/class_methods/freeze_object_spec.rb +57 -0
- data/spec/unit/adamantium/class_methods/new_spec.rb +14 -0
- data/spec/unit/adamantium/dup_spec.rb +13 -0
- data/spec/unit/adamantium/fixtures/classes.rb +28 -0
- data/spec/unit/adamantium/freeze_spec.rb +51 -0
- data/spec/unit/adamantium/memoize_spec.rb +57 -0
- data/spec/unit/adamantium/memoized_spec.rb +29 -0
- data/spec/unit/adamantium/module_methods/included_spec.rb +16 -0
- data/spec/unit/adamantium/module_methods/memoize_spec.rb +88 -0
- data/tasks/metrics/ci.rake +7 -0
- data/tasks/metrics/flay.rake +47 -0
- data/tasks/metrics/flog.rake +43 -0
- data/tasks/metrics/heckle.rake +208 -0
- data/tasks/metrics/metric_fu.rake +29 -0
- data/tasks/metrics/reek.rake +15 -0
- data/tasks/metrics/roodi.rake +15 -0
- data/tasks/metrics/yardstick.rake +23 -0
- data/tasks/spec.rake +45 -0
- data/tasks/yard.rake +9 -0
- metadata +174 -0
data/lib/adamantium.rb
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'ice_nine'
|
2
|
+
|
3
|
+
# Allows objects to be made immutable
|
4
|
+
module Adamantium
|
5
|
+
|
6
|
+
# Storage for memoized methods
|
7
|
+
Memory = Class.new(::Hash)
|
8
|
+
|
9
|
+
# Hook called when module is included
|
10
|
+
#
|
11
|
+
# @param [Module] descendant
|
12
|
+
# the module or class including Adamantium
|
13
|
+
#
|
14
|
+
# @return [self]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def self.included(descendant)
|
18
|
+
super
|
19
|
+
descendant.extend ModuleMethods if descendant.kind_of?(Module)
|
20
|
+
descendant.extend ClassMethods if descendant.kind_of?(Class)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# Attempt to freeze an object
|
25
|
+
#
|
26
|
+
# @example using a value object
|
27
|
+
# Adamantium.freeze_object(12345) # => noop
|
28
|
+
#
|
29
|
+
# @example using a normal object
|
30
|
+
# Adamantium.freeze_object({}) # => duplicate & freeze object
|
31
|
+
#
|
32
|
+
# @param [Object] object
|
33
|
+
# the object to freeze
|
34
|
+
#
|
35
|
+
# @return [Object]
|
36
|
+
# if supported, the frozen object, otherwise the object directly
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
def self.freeze_object(object)
|
40
|
+
case object
|
41
|
+
when Numeric, TrueClass, FalseClass, NilClass, Symbol
|
42
|
+
object
|
43
|
+
else
|
44
|
+
freeze_value(object)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns a frozen value
|
49
|
+
#
|
50
|
+
# @param [Object] value
|
51
|
+
# a value to freeze
|
52
|
+
#
|
53
|
+
# @return [Object]
|
54
|
+
# if frozen, the value directly, otherwise a frozen copy of the value
|
55
|
+
#
|
56
|
+
# @api private
|
57
|
+
def self.freeze_value(value)
|
58
|
+
value.frozen? ? value : IceNine.deep_freeze(value.dup)
|
59
|
+
end
|
60
|
+
|
61
|
+
private_class_method :freeze_value
|
62
|
+
|
63
|
+
# Freeze the object
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# object.freeze # object is now frozen
|
67
|
+
#
|
68
|
+
# @return [Object]
|
69
|
+
#
|
70
|
+
# @api public
|
71
|
+
def freeze
|
72
|
+
memory # initialize memory
|
73
|
+
super
|
74
|
+
end
|
75
|
+
|
76
|
+
# Get the memoized value for a method
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# hash = object.memoized(:hash)
|
80
|
+
#
|
81
|
+
# @param [Symbol] name
|
82
|
+
# the method name
|
83
|
+
#
|
84
|
+
# @return [Object]
|
85
|
+
#
|
86
|
+
# @api public
|
87
|
+
def memoized(name)
|
88
|
+
memory[name]
|
89
|
+
end
|
90
|
+
|
91
|
+
# Sets a memoized value for a method
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# object.memoize(:hash, 12345)
|
95
|
+
#
|
96
|
+
# @param [Symbol] name
|
97
|
+
# the method name
|
98
|
+
# @param [Object] value
|
99
|
+
# the value to memoize
|
100
|
+
#
|
101
|
+
# @return [self]
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def memoize(name, value)
|
105
|
+
store_memory(name, value) unless memory.key?(name)
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
# A noop #dup for immutable objects
|
110
|
+
#
|
111
|
+
# @example
|
112
|
+
# object.dup # => self
|
113
|
+
#
|
114
|
+
# @return [self]
|
115
|
+
#
|
116
|
+
# @api public
|
117
|
+
def dup
|
118
|
+
self
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
# The memoized method results
|
124
|
+
#
|
125
|
+
# @return [Hash]
|
126
|
+
#
|
127
|
+
# @api private
|
128
|
+
def memory
|
129
|
+
@__memory ||= Memory.new
|
130
|
+
end
|
131
|
+
|
132
|
+
# Store the value in memory
|
133
|
+
#
|
134
|
+
# @param [Symbol] name
|
135
|
+
# the method name
|
136
|
+
# @param [Object] value
|
137
|
+
# the value to memoize
|
138
|
+
#
|
139
|
+
# @return [self]
|
140
|
+
#
|
141
|
+
# @return [value]
|
142
|
+
#
|
143
|
+
# @api private
|
144
|
+
def store_memory(name, value)
|
145
|
+
memory[name] = Adamantium.freeze_object(value)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Methods mixed in to adamantium modules
|
149
|
+
module ModuleMethods
|
150
|
+
|
151
|
+
# Hook called when module is included
|
152
|
+
#
|
153
|
+
# @param [Module] mod
|
154
|
+
# the module including ModuleMethods
|
155
|
+
#
|
156
|
+
# @return [self]
|
157
|
+
#
|
158
|
+
# @api private
|
159
|
+
def included(mod)
|
160
|
+
Adamantium.included(mod)
|
161
|
+
self
|
162
|
+
end
|
163
|
+
|
164
|
+
# Memoize a list of methods
|
165
|
+
#
|
166
|
+
# @example
|
167
|
+
# memoize :hash
|
168
|
+
#
|
169
|
+
# @param [Array<#to_s>] *methods
|
170
|
+
# a list of methods to memoize
|
171
|
+
#
|
172
|
+
# @return [self]
|
173
|
+
#
|
174
|
+
# @api public
|
175
|
+
def memoize(*methods)
|
176
|
+
methods.each { |method| memoize_method(method) }
|
177
|
+
self
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
# Memoize the named method
|
183
|
+
#
|
184
|
+
# @param [#to_s] method
|
185
|
+
# a method to memoize
|
186
|
+
#
|
187
|
+
# @return [undefined]
|
188
|
+
#
|
189
|
+
# @api private
|
190
|
+
def memoize_method(method)
|
191
|
+
visibility = method_visibility(method)
|
192
|
+
define_memoize_method(method)
|
193
|
+
send(visibility, method)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Define a memoized method that delegates to the original method
|
197
|
+
#
|
198
|
+
# @param [Symbol] method
|
199
|
+
# the name of the method
|
200
|
+
#
|
201
|
+
# @return [undefined]
|
202
|
+
#
|
203
|
+
# @api private
|
204
|
+
def define_memoize_method(method)
|
205
|
+
original = instance_method(method)
|
206
|
+
undef_method(method)
|
207
|
+
define_method(method) do |*args|
|
208
|
+
if memory.key?(method)
|
209
|
+
memoized(method)
|
210
|
+
else
|
211
|
+
store_memory(method, original.bind(self).call(*args))
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Return the method visibility of a method
|
217
|
+
#
|
218
|
+
# @param [String, Symbol] method
|
219
|
+
# the name of the method
|
220
|
+
#
|
221
|
+
# @return [String]
|
222
|
+
#
|
223
|
+
# @api private
|
224
|
+
def method_visibility(method)
|
225
|
+
if private_method_defined?(method) then 'private'
|
226
|
+
elsif protected_method_defined?(method) then 'protected'
|
227
|
+
else 'public'
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
end # module ModuleMethods
|
232
|
+
|
233
|
+
# Methods mixed in to adamantium classes
|
234
|
+
module ClassMethods
|
235
|
+
|
236
|
+
# Instantiate a new frozen object
|
237
|
+
#
|
238
|
+
# @example
|
239
|
+
# object = AdamantiumClass.new # object is frozen
|
240
|
+
#
|
241
|
+
# @return [Object]
|
242
|
+
#
|
243
|
+
# @api public
|
244
|
+
def new(*)
|
245
|
+
IceNine.deep_freeze(super)
|
246
|
+
end
|
247
|
+
|
248
|
+
end # module ClassMethods
|
249
|
+
end # module Adamantium
|
data/spec/rcov.opts
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
shared_examples_for 'an #each method' do
|
4
|
+
it_should_behave_like 'a command method'
|
5
|
+
|
6
|
+
context 'with no block' do
|
7
|
+
subject { object.each }
|
8
|
+
|
9
|
+
it { should be_instance_of(to_enum.class) }
|
10
|
+
|
11
|
+
it 'yields the expected values' do
|
12
|
+
subject.to_a.should eql(object.to_a)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
shared_examples_for 'a hash method' do
|
4
|
+
it_should_behave_like 'an idempotent method'
|
5
|
+
|
6
|
+
specification = proc do
|
7
|
+
should be_instance_of(Fixnum)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'is a fixnum' do
|
11
|
+
instance_eval(&specification)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'memoizes the hash code' do
|
15
|
+
subject.should eql(object.memoized(:hash))
|
16
|
+
end
|
17
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'adamantium'
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/autorun'
|
6
|
+
|
7
|
+
# require spec support files and shared behavior
|
8
|
+
Dir[File.expand_path('../{support,shared}/**/*.rb', __FILE__)].each { |f| require f }
|
9
|
+
|
10
|
+
Spec::Runner.configure do |config|
|
11
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Adamantium, '.freeze_object' do
|
6
|
+
subject { object.freeze_object(value) }
|
7
|
+
|
8
|
+
let(:object) { self.class.described_type }
|
9
|
+
|
10
|
+
context 'with a numeric value' do
|
11
|
+
let(:value) { 1 }
|
12
|
+
|
13
|
+
it { should equal(value) }
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'with a true value' do
|
17
|
+
let(:value) { true }
|
18
|
+
|
19
|
+
it { should equal(value) }
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'with a false value' do
|
23
|
+
let(:value) { false }
|
24
|
+
|
25
|
+
it { should equal(value) }
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with a nil value' do
|
29
|
+
let(:value) { nil }
|
30
|
+
|
31
|
+
it { should equal(value) }
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with a symbol value' do
|
35
|
+
let(:value) { :symbol }
|
36
|
+
|
37
|
+
it { should equal(value) }
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'with a frozen value' do
|
41
|
+
let(:value) { String.new.freeze }
|
42
|
+
|
43
|
+
it { should equal(value) }
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with an unfrozen value' do
|
47
|
+
let(:value) { String.new }
|
48
|
+
|
49
|
+
it { should_not equal(value) }
|
50
|
+
|
51
|
+
it { should be_instance_of(String) }
|
52
|
+
|
53
|
+
it { should == value }
|
54
|
+
|
55
|
+
it { should be_frozen }
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require File.expand_path('../../fixtures/classes', __FILE__)
|
5
|
+
|
6
|
+
describe Adamantium::ClassMethods, '#new' do
|
7
|
+
subject { object.new }
|
8
|
+
|
9
|
+
let(:object) { AdamantiumSpecs::Object }
|
10
|
+
|
11
|
+
it { should be_instance_of(object) }
|
12
|
+
|
13
|
+
it { should be_frozen }
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require File.expand_path('../fixtures/classes', __FILE__)
|
5
|
+
|
6
|
+
describe Adamantium, '#dup' do
|
7
|
+
subject { object.dup }
|
8
|
+
|
9
|
+
let(:described_class) { AdamantiumSpecs::Object }
|
10
|
+
let(:object) { described_class.new }
|
11
|
+
|
12
|
+
it { should equal(object) }
|
13
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module AdamantiumSpecs
|
4
|
+
class Object
|
5
|
+
include Adamantium
|
6
|
+
|
7
|
+
def test
|
8
|
+
'test'
|
9
|
+
end
|
10
|
+
|
11
|
+
def public_method
|
12
|
+
caller
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def protected_method
|
18
|
+
caller
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def private_method
|
24
|
+
caller
|
25
|
+
end
|
26
|
+
|
27
|
+
end # class Object
|
28
|
+
end # module AdamantiumSpecs
|