decors 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c33d4924184f251e5ee9c3a372251355e09efe7f
4
+ data.tar.gz: 9ddc59be81ccc266b78d36b3b9dc656b37c97bc0
5
+ SHA512:
6
+ metadata.gz: 3b965a9750314f1f8e921510c9e6275eae49ec18c3f8a2285b796700acfb813e9db6aa4f95e9c68d179d1cc2665bf8891c093a9617a94971f44cf4098f1f801e
7
+ data.tar.gz: 6f6e62a25f06fe9e2d474aa1ec45cc43c85f4e598d070e2b0da5ee9b42d854b8b0a44ab96f5e2d4b4567921dd9ac5a4ee75d8a4b766c46a0820b8b6b008132e4
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ .rspec
3
+ .rubocop.yml
4
+ *.gem
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Decors changelog
2
+
3
+ ## Version 0.1 (Released April 24, 2017)
4
+
5
+ - First implementation
data/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # Decors
2
+
3
+ #### Yet another implementation of Python Decorators/Java annotation in ruby
4
+
5
+ Here are other implementations:
6
+ - https://github.com/wycats/ruby_decorators
7
+ - https://github.com/michaelfairley/method_decorators
8
+ - https://github.com/fredwu/ruby_decorators
9
+ - https://gist.github.com/reu/2762650
10
+
11
+ ## Install
12
+
13
+ Add `gem 'decors'` to your Gemfile and then `bundle`
14
+
15
+ ## Usage
16
+
17
+ ## License
18
+
data/decors.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'decors'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "decors"
8
+ gem.version = ::Decors::VERSION
9
+ gem.authors = ['Vivien Meyet']
10
+ gem.email = ['vivien@getbannerman.com']
11
+ gem.description = "Ruby implementation of Python method decorators / Java annotations"
12
+ gem.summary = gem.description
13
+ gem.homepage = 'https://github.com/getbannerman/decors'
14
+
15
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^spec/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_development_dependency 'pry-byebug', '~> 0'
21
+ gem.add_development_dependency 'rspec', '~> 0'
22
+ end
@@ -0,0 +1,25 @@
1
+ module Decors
2
+ class DecoratorBase
3
+ attr_reader :decorated_class, :decorated_method, :decorator_args, :decorator_kwargs, :decorator_block
4
+
5
+ def initialize(decorated_class, decorated_method, *args, **kwargs, &block)
6
+ @decorated_class = decorated_class
7
+ @decorated_method = decorated_method
8
+ @decorator_args = args
9
+ @decorator_kwargs = kwargs
10
+ @decorator_block = block
11
+ end
12
+
13
+ def call(instance, *args, &block)
14
+ undecorated_call(instance, *args, &block)
15
+ end
16
+
17
+ def undecorated_call(instance, *args, &block)
18
+ decorated_method.bind(instance).call(*args, &block)
19
+ end
20
+
21
+ def decorated_method_name
22
+ decorated_method.name
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module Decors
2
+ module DecoratorDefinition
3
+ def define_mixin_decorator(decorator_name, decorator_class)
4
+ define_decorator(decorator_name, decorator_class, mixin: true)
5
+ end
6
+
7
+ def define_decorator(decorator_name, decorator_class, mixin: false)
8
+ method_definer = mixin ? :define_method : :define_singleton_method
9
+
10
+ send(method_definer, decorator_name) do |*params, &blk|
11
+ ctx = self.singleton_class? ? ObjectSpace.each_object(self).first : self
12
+ ctx.send(:extend, MethodAddedListener)
13
+ ctx.declared_decorators << [decorator_class, params, blk]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ module Decors
2
+ # Mixin that add method_added hooks
3
+ module MethodAddedListener
4
+ def method_added(meth)
5
+ super
6
+ handle_method_addition(self, meth)
7
+ end
8
+
9
+ def singleton_method_added(meth)
10
+ super
11
+ handle_method_addition(singleton_class, meth)
12
+ end
13
+
14
+ def declared_decorators
15
+ @declared_decorators ||= []
16
+ end
17
+
18
+ private
19
+
20
+ # method addition handling is the same for singleton and instance method
21
+ def handle_method_addition(clazz, method_name)
22
+ # @_ignore_additions allows to temporarily disable the hook
23
+ return if @_ignore_additions || declared_decorators.empty?
24
+ decorator_class, params, blk = declared_decorators.pop
25
+
26
+ decorated_method = clazz.instance_method(method_name)
27
+
28
+ @_ignore_additions = true
29
+ decorator = decorator_class.new(clazz, decorated_method, *params, &blk)
30
+ @_ignore_additions = false
31
+
32
+ clazz.send(:define_method, method_name) { |*args, &block| decorator.call(self, *args, &block) }
33
+ end
34
+ end
35
+ end
data/lib/decors.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'decors/decorator_base'
2
+ require 'decors/decorator_definition'
3
+ require 'decors/method_added_listener'
4
+
5
+ module Decors
6
+ VERSION = '0.1.0'
7
+ end
@@ -0,0 +1,369 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Decors do
4
+ before { stub_class(:TestClass) { extend Decors::DecoratorDefinition } }
5
+ before {
6
+ stub_class(:Spy) {
7
+ def self.passed_args(*args, **kwargs, &block)
8
+ arguments(args: args, kwargs: kwargs, evald_block: block&.call)
9
+ end
10
+
11
+ def self.arguments(*); end
12
+ }
13
+ }
14
+
15
+ context 'when simple case decorator' do
16
+ before {
17
+ stub_class(:SimpleDecorator, inherits: [::Decors::DecoratorBase]) {
18
+ def initialize(decorated_class, decorated_method, *deco_args, **deco_kwargs, &deco_block)
19
+ super
20
+ Spy.passed_args(*deco_args, **deco_kwargs, &deco_block)
21
+ end
22
+
23
+ def call(instance, *args, &block)
24
+ super
25
+ Spy.passed_args(*args, &block)
26
+ end
27
+ }
28
+
29
+ TestClass.class_eval { define_decorator :SimpleDecorator, SimpleDecorator }
30
+ }
31
+
32
+ context 'It receive all the parameters at initialization' do
33
+ it { expect(SimpleDecorator).to receive(:new).with(TestClass, anything, 1, 2, a: 3).and_call_original }
34
+ it { expect(Spy).to receive(:arguments).with(args: [1, 2], kwargs: { a: 3 }, evald_block: 'ok') }
35
+
36
+ after {
37
+ TestClass.class_eval {
38
+ SimpleDecorator(1, 2, a: 3) { 'ok' }
39
+ def test_method(*); end
40
+
41
+ def test_method_not_decorated(*); end
42
+ }
43
+ }
44
+ end
45
+
46
+ context 'It receive the instance and the arguments passed to the method when called' do
47
+ let(:instance) { TestClass.new }
48
+ before {
49
+ TestClass.class_eval {
50
+ SimpleDecorator()
51
+ def test_method(*args, &block)
52
+ Spy.passed_args(*args, &block)
53
+ end
54
+
55
+ def test_method_not_decorated; end
56
+ }
57
+ }
58
+
59
+ it { expect_any_instance_of(SimpleDecorator).to receive(:call).with(instance, 1, a: 2, b: 3, &proc { 'yes' }) }
60
+ it { expect(Spy).to receive(:arguments).with(args: [1], kwargs: { a: 2, b: 3 }, evald_block: 'yes').twice }
61
+
62
+ after {
63
+ instance.test_method(1, a: 2, b: 3) { 'yes' }
64
+ instance.test_method_not_decorated
65
+ }
66
+ end
67
+ end
68
+
69
+ context 'when decorator is defining a method during initialization' do
70
+ before {
71
+ stub_class(:StrangeDecorator, inherits: [::Decors::DecoratorBase]) {
72
+ def initialize(decorated_class, decorated_method, *deco_args, **deco_kwargs, &deco_block)
73
+ super
74
+ decorated_class.send(:define_method, :foo) { 42 }
75
+ end
76
+
77
+ def call(*)
78
+ super * 2
79
+ end
80
+ }
81
+
82
+ TestClass.class_eval { define_decorator :StrangeDecorator, StrangeDecorator }
83
+ }
84
+
85
+ before {
86
+ TestClass.class_eval {
87
+ StrangeDecorator()
88
+ StrangeDecorator()
89
+ def test_method
90
+ 5
91
+ end
92
+ }
93
+ }
94
+
95
+ it { expect(TestClass.new.test_method).to eq 5 * 2 * 2 }
96
+ it { expect(TestClass.new.foo).to eq 42 }
97
+ end
98
+
99
+ context 'when mutiple decorators' do
100
+ before {
101
+ Spy.class_eval {
102
+ @ordered_calls = []
103
+
104
+ class << self
105
+ attr_reader :ordered_calls
106
+
107
+ def calling(name)
108
+ self.ordered_calls << name
109
+ end
110
+ end
111
+ }
112
+
113
+ stub_class(:Deco1, inherits: [::Decors::DecoratorBase]) {
114
+ def call(*)
115
+ Spy.calling(:deco1_before)
116
+ super
117
+ Spy.calling(:deco1_after)
118
+ end
119
+ }
120
+
121
+ stub_class(:Deco2, inherits: [::Decors::DecoratorBase]) {
122
+ def call(*)
123
+ Spy.calling(:deco2_before)
124
+ super
125
+ Spy.calling(:deco2_after)
126
+ end
127
+ }
128
+
129
+ TestClass.class_eval {
130
+ define_decorator :Deco1, Deco1
131
+ define_decorator :Deco2, Deco2
132
+
133
+ Deco2()
134
+ Deco1()
135
+ def test_method(*)
136
+ Spy.calling(:inside)
137
+ end
138
+ }
139
+ }
140
+
141
+ before { TestClass.new.test_method }
142
+ it { expect(Spy.ordered_calls).to eq [:deco2_before, :deco1_before, :inside, :deco1_after, :deco2_after] }
143
+ end
144
+
145
+ context 'when method has return value' do
146
+ before {
147
+ stub_class(:ModifierDeco, inherits: [::Decors::DecoratorBase])
148
+
149
+ TestClass.class_eval {
150
+ define_decorator :ModifierDeco, ModifierDeco
151
+
152
+ ModifierDeco()
153
+ def test_method
154
+ :ok
155
+ end
156
+ }
157
+ }
158
+
159
+ it { expect(TestClass.new.test_method).to eq :ok }
160
+ end
161
+
162
+ context 'when method has arguments' do
163
+ before {
164
+ stub_class(:ModifierDeco, inherits: [::Decors::DecoratorBase])
165
+
166
+ TestClass.class_eval {
167
+ define_decorator :ModifierDeco, ModifierDeco
168
+
169
+ ModifierDeco()
170
+ def test_method(*args, &block)
171
+ Spy.passed_args(*args, &block)
172
+ end
173
+ }
174
+ }
175
+
176
+ it { expect(Spy).to receive(:arguments).with(args: [1, 2, 3], kwargs: { a: :a }, evald_block: 'yay') }
177
+ after { TestClass.new.test_method(1, 2, 3, a: :a) { 'yay' } }
178
+ end
179
+
180
+ context 'when changing arguments given to the method' do
181
+ before {
182
+ stub_class(:ModifierDeco, inherits: [::Decors::DecoratorBase]) {
183
+ def call(instance, *)
184
+ undecorated_call(instance, 1, 2, 3, a: :a, &proc { 'yay' })
185
+ end
186
+ }
187
+
188
+ TestClass.class_eval {
189
+ define_decorator :ModifierDeco, ModifierDeco
190
+
191
+ ModifierDeco()
192
+ def test_method(*args, &block)
193
+ Spy.passed_args(*args, &block)
194
+ end
195
+ }
196
+ }
197
+
198
+ it { expect(Spy).to receive(:arguments).with(args: [1, 2, 3], kwargs: { a: :a }, evald_block: 'yay') }
199
+ after { TestClass.new.test_method }
200
+ end
201
+
202
+ context 'when method is recursive' do
203
+ before {
204
+ stub_class(:AddOneToArg, inherits: [::Decors::DecoratorBase]) {
205
+ def call(instance, *args)
206
+ undecorated_call(instance, args.first + 1)
207
+ end
208
+ }
209
+
210
+ TestClass.class_eval {
211
+ define_decorator :AddOneToArg, AddOneToArg
212
+
213
+ AddOneToArg()
214
+ def test_method(n)
215
+ return 0 if n.zero?
216
+ n + test_method(n - 2)
217
+ end
218
+ }
219
+ }
220
+
221
+ it { expect(TestClass.new.test_method(4)).to eq 5 + 4 + 3 + 2 + 1 }
222
+ end
223
+
224
+ context 'when already has a method_added' do
225
+ before {
226
+ stub_module(:TestMixin) {
227
+ def method_added(*)
228
+ Spy.called
229
+ end
230
+ }
231
+ stub_class(:Deco, inherits: [::Decors::DecoratorBase])
232
+ }
233
+ it { expect(Spy).to receive(:called) }
234
+
235
+ after {
236
+ TestClass.class_eval {
237
+ extend TestMixin
238
+
239
+ define_decorator :Deco, Deco
240
+
241
+ def test_method; end
242
+ }
243
+ }
244
+ end
245
+
246
+ context 'when inherited' do
247
+ before {
248
+ stub_class(:Deco, inherits: [::Decors::DecoratorBase])
249
+
250
+ TestClass.class_eval {
251
+ define_decorator :Deco, Deco
252
+
253
+ Deco()
254
+ def test_method
255
+ :ok
256
+ end
257
+ }
258
+ }
259
+
260
+ it {
261
+ stub_class(:TestClass2, inherits: [TestClass])
262
+ TestClass2.class_eval {
263
+ Deco()
264
+ def test_method
265
+ :ko
266
+ end
267
+ }
268
+
269
+ expect(TestClass.new.test_method).to eq :ok
270
+ expect(TestClass2.new.test_method).to eq :ko
271
+ }
272
+
273
+ it {
274
+ stub_class(:TestClass3, inherits: [TestClass])
275
+
276
+ TestClass3.class_eval {
277
+ Deco()
278
+ def test_method
279
+ "this is #{super}"
280
+ end
281
+ }
282
+
283
+ expect(TestClass3.new.test_method).to eq 'this is ok'
284
+ }
285
+ end
286
+
287
+ context 'when decorating a class method' do
288
+ before {
289
+ stub_class(:Deco, inherits: [::Decors::DecoratorBase]) {
290
+ def call(*)
291
+ super
292
+ Spy.called
293
+ end
294
+ }
295
+ }
296
+
297
+ context 'when mixin extended on the class (singleton method in class)' do
298
+ before {
299
+ TestClass.class_eval {
300
+ define_decorator :Deco, Deco
301
+
302
+ Deco()
303
+ def self.test_method
304
+ :ok
305
+ end
306
+ }
307
+ }
308
+
309
+ it { expect(Spy).to receive(:called) }
310
+ after { TestClass.test_method }
311
+ end
312
+
313
+ context 'when mixin extended on the class (method in singleton class)' do
314
+ before {
315
+ TestClass.class_eval {
316
+ class << self
317
+ extend Decors::DecoratorDefinition
318
+
319
+ define_decorator :Deco, Deco
320
+
321
+ Deco()
322
+ def test_method
323
+ :ok
324
+ end
325
+ end
326
+ }
327
+ }
328
+
329
+ it { expect(Spy).to receive(:called) }
330
+ after { TestClass.test_method }
331
+ end
332
+
333
+ context 'when mixin extended on the class (both method in singleton class and singleton method in class)' do
334
+ before {
335
+ TestClass.class_eval {
336
+ define_decorator :Deco, Deco
337
+
338
+ Deco()
339
+ def self.test_method__in_class
340
+ :ok
341
+ end
342
+
343
+ def self.untest_method__in_class
344
+ end
345
+
346
+ class << self
347
+ extend Decors::DecoratorDefinition
348
+
349
+ define_decorator :Deco, Deco
350
+
351
+ Deco()
352
+ def test_method__in_singleton
353
+ :ok
354
+ end
355
+
356
+ def untest_method__in_singleton
357
+ end
358
+ end
359
+ }
360
+ }
361
+
362
+ it { expect(Spy).to receive(:called) and TestClass.test_method__in_class }
363
+ it { expect(Spy).to receive(:called) and TestClass.test_method__in_singleton }
364
+ it { expect(Spy).to_not receive(:called) and TestClass.untest_method__in_class }
365
+ it { expect(Spy).to_not receive(:called) and TestClass.untest_method__in_singleton }
366
+ end
367
+ end
368
+ end
369
+
@@ -0,0 +1,19 @@
1
+ require File.expand_path('../../lib/decors', __FILE__)
2
+
3
+ module SpecHelpers
4
+ def stub_class(class_name, inherits: [], &class_eval)
5
+ klass = stub_const(class_name.to_s, Class.new(*inherits))
6
+ klass.class_eval(&class_eval) if class_eval
7
+ klass
8
+ end
9
+
10
+ def stub_module(module_name, &module_eval)
11
+ mod = stub_const(module_name.to_s, Module.new)
12
+ mod.module_eval(&module_eval) if module_eval
13
+ mod
14
+ end
15
+ end
16
+
17
+ RSpec.configure do |config|
18
+ config.include SpecHelpers
19
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: decors
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vivien Meyet
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-04-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pry-byebug
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Ruby implementation of Python method decorators / Java annotations
42
+ email:
43
+ - vivien@getbannerman.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - CHANGELOG.md
50
+ - README.md
51
+ - decors.gemspec
52
+ - lib/decors.rb
53
+ - lib/decors/decorator_base.rb
54
+ - lib/decors/decorator_definition.rb
55
+ - lib/decors/method_added_listener.rb
56
+ - spec/decors_spec.rb
57
+ - spec/spec_helper.rb
58
+ homepage: https://github.com/getbannerman/decors
59
+ licenses: []
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 2.6.8
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Ruby implementation of Python method decorators / Java annotations
81
+ test_files:
82
+ - spec/decors_spec.rb
83
+ - spec/spec_helper.rb