adamantium 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.gitignore +4 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +86 -0
  4. data/Gemfile +54 -0
  5. data/Guardfile +18 -0
  6. data/LICENSE +21 -0
  7. data/README.md +90 -0
  8. data/Rakefile +9 -0
  9. data/TODO +1 -0
  10. data/adamantium.gemspec +25 -0
  11. data/config/flay.yml +3 -0
  12. data/config/flog.yml +2 -0
  13. data/config/roodi.yml +18 -0
  14. data/config/site.reek +91 -0
  15. data/config/yardstick.yml +2 -0
  16. data/lib/adamantium.rb +249 -0
  17. data/lib/adamantium/version.rb +3 -0
  18. data/spec/rcov.opts +7 -0
  19. data/spec/shared/command_method_behavior.rb +7 -0
  20. data/spec/shared/each_method_behaviour.rb +15 -0
  21. data/spec/shared/hash_method_behavior.rb +17 -0
  22. data/spec/shared/idempotent_method_behavior.rb +7 -0
  23. data/spec/shared/invertible_method_behaviour.rb +9 -0
  24. data/spec/spec_helper.rb +11 -0
  25. data/spec/unit/adamantium/class_methods/freeze_object_spec.rb +57 -0
  26. data/spec/unit/adamantium/class_methods/new_spec.rb +14 -0
  27. data/spec/unit/adamantium/dup_spec.rb +13 -0
  28. data/spec/unit/adamantium/fixtures/classes.rb +28 -0
  29. data/spec/unit/adamantium/freeze_spec.rb +51 -0
  30. data/spec/unit/adamantium/memoize_spec.rb +57 -0
  31. data/spec/unit/adamantium/memoized_spec.rb +29 -0
  32. data/spec/unit/adamantium/module_methods/included_spec.rb +16 -0
  33. data/spec/unit/adamantium/module_methods/memoize_spec.rb +88 -0
  34. data/tasks/metrics/ci.rake +7 -0
  35. data/tasks/metrics/flay.rake +47 -0
  36. data/tasks/metrics/flog.rake +43 -0
  37. data/tasks/metrics/heckle.rake +208 -0
  38. data/tasks/metrics/metric_fu.rake +29 -0
  39. data/tasks/metrics/reek.rake +15 -0
  40. data/tasks/metrics/roodi.rake +15 -0
  41. data/tasks/metrics/yardstick.rake +23 -0
  42. data/tasks/spec.rake +45 -0
  43. data/tasks/yard.rake +9 -0
  44. metadata +174 -0
@@ -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
@@ -0,0 +1,3 @@
1
+ module Adamantium
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,7 @@
1
+ --exclude-only "spec/,^/"
2
+ --sort coverage
3
+ --callsites
4
+ --xrefs
5
+ --profile
6
+ --text-summary
7
+ --failure-threshold 100
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a command method' do
4
+ it 'returns self' do
5
+ should equal(object)
6
+ end
7
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an idempotent method' do
4
+ it 'is idempotent' do
5
+ should equal(instance_eval(&self.class.subject))
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an invertible method' do
4
+ it_should_behave_like 'an idempotent method'
5
+
6
+ it 'is invertible' do
7
+ subject.inverse.should equal(object)
8
+ end
9
+ end
@@ -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