super_module 1.1.1 → 1.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 +34 -87
- data/VERSION +1 -1
- data/examples/reddit-readers/banister/foo.rb +1 -2
- data/lib/super_module.rb +41 -26
- data/lib/super_module/v1.rb +37 -0
- data/lib/super_module/v1/module_body_method_call_recorder.rb +42 -0
- data/lib/super_module/v1/singleton_method_definition_store.rb +106 -0
- data/spec/lib/super_module_spec.rb +159 -145
- data/spec/support/baz.rb +1 -3
- data/spec/support/v1.rb +6 -0
- data/spec/support/v1/bar.rb +51 -0
- data/spec/support/v1/baz.rb +28 -0
- data/spec/support/v1/fake_active_model.rb +16 -0
- data/spec/support/v1/foo.rb +87 -0
- data/spec/support/v2.rb +6 -0
- data/spec/support/v2/bar.rb +52 -0
- data/spec/support/v2/baz.rb +27 -0
- data/spec/support/v2/fake_active_model.rb +14 -0
- data/spec/support/v2/foo.rb +87 -0
- data/spec/support/v2_alt.rb +6 -0
- data/spec/support/v2_alt/bar.rb +52 -0
- data/spec/support/v2_alt/baz.rb +27 -0
- data/spec/support/v2_alt/fake_active_model.rb +14 -0
- data/spec/support/v2_alt/foo.rb +87 -0
- data/super_module.gemspec +21 -6
- metadata +20 -5
- data/lib/super_module/module_body_method_call_recorder.rb +0 -40
- data/lib/super_module/singleton_method_definition_store.rb +0 -102
- data/spec/support/support.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2417fe4ac87bcf7e975d971de2f86caa01f1f357
|
4
|
+
data.tar.gz: 305eda58e8250ce45e24af27325ba09f2f34c9f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4eea83f2a17b20dd8c40ab3f5e710f702c501936fea5782dbfb8295ee5c3a8a7889479d742777bbfa6151d8aa6ccfdc71ab4fdbd9431e2a13dbd4f0986f1f1a
|
7
|
+
data.tar.gz: 071b746e0e94003c5716724e9b51744986bd28c388492ed55284d9c65f8c445393d2688e2e8be0046b101bbb6bccaed75a97a472c2356c4b748012541efbad2a
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# <img src="https://raw.githubusercontent.com/AndyObtiva/super_module/master/SuperModule.jpg" alt="SuperModule" align="left" height="50" /> SuperModule
|
1
|
+
# <img src="https://raw.githubusercontent.com/AndyObtiva/super_module/master/SuperModule.jpg" alt="SuperModule" align="left" height="50" /> SuperModule 2 Beta (1.2.0)
|
2
2
|
[![Gem Version](https://badge.fury.io/rb/super_module.svg)](http://badge.fury.io/rb/super_module)
|
3
3
|
[![Build Status](https://api.travis-ci.org/AndyObtiva/super_module.svg?branch=master)](https://travis-ci.org/AndyObtiva/super_module)
|
4
4
|
[![Coverage Status](https://coveralls.io/repos/AndyObtiva/super_module/badge.svg?branch=master)](https://coveralls.io/r/AndyObtiva/super_module?branch=master)
|
@@ -8,7 +8,7 @@ Calling [Ruby](https://www.ruby-lang.org/en/)'s [`Module#include`](http://ruby-d
|
|
8
8
|
|
9
9
|
[`ActiveSupport::Concern`](http://api.rubyonrails.org/classes/ActiveSupport/Concern.html) is a popular Rails library that attempts to ease some of the boilerplate pain by offering a [DSL](http://www.infoq.com/news/2007/06/dsl-or-not) layer on top of [`Module.included(base)`](http://ruby-doc.org/core-2.2.1/Module.html#method-i-included). Unfortunately, while it improves the readability of the code needed to include class methods, it supports the same boilerplate idiom, thus feeling no more than putting a band-aid on the problem.
|
10
10
|
|
11
|
-
Fortunately, [SuperModule](https://rubygems.org/gems/super_module) comes to the rescue.
|
11
|
+
Fortunately, [SuperModule](https://rubygems.org/gems/super_module) comes to the rescue. By declaring your module as a `super_module`, it will automatically include class methods whenever it is mixed into a class or another module via [`Module#include`](http://ruby-doc.org/core-2.2.1/Module.html#method-i-include).
|
12
12
|
|
13
13
|
## Introductory Comparison
|
14
14
|
|
@@ -72,8 +72,7 @@ A step forward that addresses the boiler-plate repetitive code concern, but is o
|
|
72
72
|
#### 3) [SuperModule](https://github.com/AndyObtiva/super_module)
|
73
73
|
|
74
74
|
```ruby
|
75
|
-
|
76
|
-
include SuperModule
|
75
|
+
super_module :UserIdentifiable do
|
77
76
|
include ActiveModel::Model
|
78
77
|
|
79
78
|
belongs_to :user
|
@@ -88,13 +87,21 @@ module UserIdentifiable
|
|
88
87
|
end
|
89
88
|
end
|
90
89
|
```
|
90
|
+
Using `super_module`, developers can directly add class method invocations and definitions inside the module's body, and [`SuperModule`](https://github.com/AndyObtiva/super_module) takes care of automatically mixing them into classes that include the module.
|
91
91
|
|
92
|
-
|
93
|
-
|
94
|
-
As a result, [SuperModule](https://rubygems.org/gems/super_module) collapses the difference between extending a super class and including a super module, thus encouraging developers to write simpler code while making better Object-Oriented Design decisions.
|
92
|
+
As a result, [SuperModule](https://rubygems.org/gems/super_module) collapses the difference between extending a super class and including a super module, thus encouraging developers to write simpler code while making better Object-Oriented Design decisions.
|
95
93
|
|
96
94
|
In other words, [SuperModule](https://rubygems.org/gems/super_module) furthers Ruby's goal of making programmers happy.
|
97
95
|
|
96
|
+
By the way, SuperModule 2 Beta supports an alternate syntax as well:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
UserIdentifiable = super_module do
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
|
104
|
+
|
98
105
|
## Instructions
|
99
106
|
|
100
107
|
#### 1) Install and require gem
|
@@ -115,11 +122,10 @@ Run the following command: <pre>gem install super_module</pre>
|
|
115
122
|
|
116
123
|
Add the following at the top of your [Ruby](https://www.ruby-lang.org/en/) file: <pre>require 'super_module'</pre>
|
117
124
|
|
118
|
-
#### 2)
|
125
|
+
#### 2) Call `super_module(name)` and pass it the super module body in a block
|
119
126
|
|
120
127
|
```ruby
|
121
|
-
|
122
|
-
include SuperModule
|
128
|
+
super_module :UserIdentifiable do
|
123
129
|
include ActiveModel::Model
|
124
130
|
|
125
131
|
belongs_to :user
|
@@ -144,8 +150,7 @@ end
|
|
144
150
|
class CourseEnrollment < ActiveRecord::Base
|
145
151
|
include UserIdentifiable
|
146
152
|
end
|
147
|
-
|
148
|
-
include SuperModule
|
153
|
+
super_module :Accountable do
|
149
154
|
include UserIdentifiable
|
150
155
|
end
|
151
156
|
class Activity < ActiveRecord::Base
|
@@ -188,8 +193,7 @@ Create a ruby file called super_module_irb_example.rb with the following content
|
|
188
193
|
require 'rubygems' # to be backwards compatible with Ruby 1.8.7
|
189
194
|
require 'super_module'
|
190
195
|
|
191
|
-
|
192
|
-
include SuperModule
|
196
|
+
super_module :RequiresAttributes do
|
193
197
|
|
194
198
|
def self.requires(*attributes)
|
195
199
|
attributes.each {|attribute| required_attributes << attribute}
|
@@ -255,85 +259,20 @@ media_authorization.requirements_satisfied?
|
|
255
259
|
|
256
260
|
## How Does It Work?
|
257
261
|
|
258
|
-
|
259
|
-
|
260
|
-
```ruby
|
261
|
-
def included(base)
|
262
|
-
__define_super_module_class_methods(base)
|
263
|
-
__invoke_super_module_class_method_calls(base)
|
264
|
-
end
|
265
|
-
```
|
266
|
-
|
267
|
-
#### 1) Defines super module class methods on the including base class
|
268
|
-
|
269
|
-
For example, suppose we have a super module called Addressable:
|
270
|
-
|
271
|
-
```ruby
|
272
|
-
module Addressable
|
273
|
-
include SuperModule
|
274
|
-
|
275
|
-
include Locatable
|
276
|
-
validates :city, presence: true, length: { maximum: 255 }
|
277
|
-
validates :state, presence: true, length: { is: 2 }
|
278
|
-
|
279
|
-
def self.merge_duplicates
|
280
|
-
# 1. Look through all Addressable instances in the database
|
281
|
-
# 2. Identify duplicates
|
282
|
-
# 3. Merge duplicate addressables
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
class Contact < ActiveRecord::Base
|
287
|
-
include Addressable
|
288
|
-
# … more code follows
|
289
|
-
end
|
290
|
-
```
|
291
|
-
|
292
|
-
This step ensures that <code>merge_duplicates</code> is included in Contact as a class method, allowing the call <code>Contact.merge_duplicates</code>
|
293
|
-
|
294
|
-
It does so by recording every class method defined using the Ruby [`self.singleton_method_added(method_name)`](http://ruby-doc.org/core-2.2.1/BasicObject.html#method-i-singleton_method_added) hook, reading class method sources using the [method_source](https://rubygems.org/gems/method_source/) gem, and finally upon invocation of `self.included(base)`, `class_eval`ing the recorded class methods on the including base class (or module).
|
295
|
-
|
296
|
-
In order to avoid interference with existing class method definitions, there is an exception list for what not to record, such as <code>:included_super_modules, :class_eval, :singleton_method_added</code> and any other "__" prefixed class methods defined in [SuperModule](https://rubygems.org/gems/super_module), such as <code>__super_module_class_method_calls</code>.
|
297
|
-
|
298
|
-
Also, the recorded class method sources are altered to handle recording of method calls as well, which is used in the second step explained next.
|
299
|
-
|
300
|
-
#### 2) Invoke super module class method calls on the including base class (or module).
|
301
|
-
|
302
|
-
For example, suppose we have a super module called `Locatable`:
|
303
|
-
|
304
|
-
```ruby
|
305
|
-
module Locatable
|
306
|
-
include SuperModule
|
307
|
-
|
308
|
-
validates :x_coordinate, numericality: true
|
309
|
-
validates :y_coordinate, numericality: true
|
310
|
-
|
311
|
-
def move(x, y)
|
312
|
-
self.x_coordinate += x
|
313
|
-
self.y_coordinate += y
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
class Vehicle < ActiveRecord::Base
|
318
|
-
include Locatable
|
319
|
-
# … more code follows
|
320
|
-
end
|
321
|
-
```
|
262
|
+
V2 has a much simpler algorithm than V1 that goes as follows:
|
322
263
|
|
323
|
-
|
324
|
-
|
325
|
-
|
264
|
+
1. Handle invocation of `super_module(name, &super_module_body)` method anywhere in the Ruby code where the block it receives represents the super module body, including instance methods, and class methods, and class body invocations.
|
265
|
+
2. Clone `SuperModule` and store in it the passed in `super_module_body` block
|
266
|
+
3. Assign the cloned `SuperModule` to a new constant as defined by name (e.g. 'Utilities::Printer') under a class, module, or the top-level Ruby scope
|
267
|
+
4. When calling `include` on the module later on, its stored super_module_body attribute is retrieved and run in the including class or module body via `class_eval`
|
326
268
|
|
327
269
|
## Limitations and Caveats
|
328
270
|
|
329
|
-
* [SuperModule](https://rubygems.org/gems/super_module) has been designed to be used only in the code definition of a module
|
330
|
-
|
331
|
-
* Initial Ruby runtime load of a class or module mixing in [SuperModule](https://rubygems.org/gems/super_module) will incur a very marginal performance hit (in the order of nano-to-milliseconds). However, class usage (instantiation and method invocation) will not incur any performance hit, running as fast as any other Ruby class.
|
271
|
+
* [SuperModule](https://rubygems.org/gems/super_module) has been designed to be used only in the initial code definition of a module (not supporting later re-opening of the module.)
|
332
272
|
|
333
273
|
* Given [SuperModule](https://rubygems.org/gems/super_module)'s implementation relies on `self.included(base)`, if an including super module (or a super module including another super module) must hook into <code>self.included(base)</code> for meta-programming cases that require it, such as conditional `include` statements or method definitions, it would have to alias <code>self.included(base)</code> and then invoke the aliased version in every super module that needs it like in this example:
|
334
274
|
```ruby
|
335
|
-
|
336
|
-
include SuperModule
|
275
|
+
super_module :AdminIdentifiable do
|
337
276
|
include UserIdentifiable
|
338
277
|
|
339
278
|
class << self
|
@@ -345,11 +284,19 @@ module AdminIdentifiable
|
|
345
284
|
# or conditional definition of methods
|
346
285
|
end
|
347
286
|
end
|
287
|
+
end
|
348
288
|
```
|
349
289
|
In the future, [SuperModule](https://rubygems.org/gems/super_module) could perhaps provide robust built-in facilities for allowing super modules to easily hook into <code>self.included(base)</code> without interfering with [SuperModule](https://rubygems.org/gems/super_module) behavior.
|
350
290
|
|
351
291
|
## What's New?
|
352
292
|
|
293
|
+
### v2 Beta (v1.2.0)
|
294
|
+
|
295
|
+
* New `super_module(name)` syntax
|
296
|
+
* Much simpler implementation with guaranteed correctness and no performance hit
|
297
|
+
* Less memory footprint by not requiring method_source Ruby gem for v2 syntax
|
298
|
+
* Backwards compatibility with v1 syntax
|
299
|
+
|
353
300
|
### v1.1.1
|
354
301
|
|
355
302
|
* Added support for private and protected methods
|
@@ -378,5 +325,5 @@ The library is quite new and can use all the feedback and help it can get. So, p
|
|
378
325
|
|
379
326
|
## Copyright
|
380
327
|
|
381
|
-
Copyright (c) 2014-
|
328
|
+
Copyright (c) 2014-2016 Andy Maleh. See LICENSE.txt for
|
382
329
|
further details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.2.0
|
data/lib/super_module.rb
CHANGED
@@ -7,35 +7,50 @@
|
|
7
7
|
# This module allows defining class methods and method invocations the same way a super class does without using def included(base).
|
8
8
|
|
9
9
|
# Avoiding require_relative for backwards compatibility with Ruby 1.8.7
|
10
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'super_module', 'module_body_method_call_recorder'))
|
11
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'super_module', 'singleton_method_definition_store'))
|
12
10
|
|
13
11
|
module SuperModule
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
12
|
+
class << self
|
13
|
+
V1_LIBRARY = File.expand_path(File.join(File.dirname(__FILE__), 'super_module', 'v1'))
|
14
|
+
|
15
|
+
attr_accessor :super_module_body
|
16
|
+
|
17
|
+
def define(&super_module_body)
|
18
|
+
clone.tap { |super_module| super_module.super_module_body = super_module_body }
|
19
|
+
end
|
20
|
+
|
21
|
+
def included(original_base)
|
22
|
+
if super_module_body
|
23
|
+
original_base.class_eval(&super_module_body)
|
24
|
+
else
|
25
|
+
require V1_LIBRARY
|
26
|
+
original_base.send(:include, SuperModule::V1)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def __super_module_parent(name, ancestor)
|
31
|
+
name_tokens = name.to_s.split('::')
|
32
|
+
name_token_count = name_tokens.size
|
33
|
+
if name_token_count == 1
|
34
|
+
ancestor
|
35
|
+
else
|
36
|
+
top_ancestor = ancestor.const_get(name_tokens.first)
|
37
|
+
sub_module_name = name_tokens[1, name_token_count].join('::')
|
38
|
+
__super_module_parent(sub_module_name, top_ancestor)
|
38
39
|
end
|
39
40
|
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def super_module(name=nil, &super_module_body)
|
47
|
+
initial_ancestor = self.class == Object ? Object : self
|
48
|
+
SuperModule.define(&super_module_body).tap do |new_super_module|
|
49
|
+
if name
|
50
|
+
parent = SuperModule.__super_module_parent(name, initial_ancestor)
|
51
|
+
module_name = name.to_s.split('::').last
|
52
|
+
parent.const_set(module_name, new_super_module)
|
53
|
+
end
|
40
54
|
end
|
41
55
|
end
|
56
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'v1', 'module_body_method_call_recorder'))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'v1', 'singleton_method_definition_store'))
|
3
|
+
|
4
|
+
module SuperModule
|
5
|
+
module V1
|
6
|
+
class << self
|
7
|
+
def included(original_base)
|
8
|
+
original_base.class_eval do
|
9
|
+
extend SuperModule::V1::ModuleBodyMethodCallRecorder
|
10
|
+
extend SuperModule::V1::SingletonMethodDefinitionStore
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def __define_super_module_singleton_methods(base)
|
14
|
+
__super_module_singleton_methods.each do |method_name, method_body|
|
15
|
+
# The following is needed for cases where a method is declared public/protected/private after it was added
|
16
|
+
refreshed_access_level_method_body = method_body.sub(/class << self\n(public|protected|private)\n/, "class << self\n#{__singleton_method_access_level(method_name)}\n")
|
17
|
+
base.class_eval(refreshed_access_level_method_body)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def __invoke_module_body_method_calls(base)
|
22
|
+
__all_module_body_method_calls_in_definition_order.each do |method_name, args, block|
|
23
|
+
base.send(method_name, *args, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def included(base)
|
28
|
+
__define_super_module_singleton_methods(base)
|
29
|
+
__invoke_module_body_method_calls(base)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module SuperModule
|
2
|
+
module V1
|
3
|
+
module ModuleBodyMethodCallRecorder
|
4
|
+
def __super_module_singleton_methods_excluded_from_call_recording
|
5
|
+
@__super_module_singleton_methods_excluded_from_call_recording ||= [
|
6
|
+
:__record_method_call,
|
7
|
+
:__method_signature
|
8
|
+
]
|
9
|
+
end
|
10
|
+
|
11
|
+
def __module_body_method_calls
|
12
|
+
@__module_body_method_calls ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
def __method_signature(method_name, args)
|
16
|
+
"#{method_name}(#{args.to_a.map(&:to_s).join(",")})"
|
17
|
+
end
|
18
|
+
|
19
|
+
#TODO handle case of a method call being passed a block (e.g. validates do custom validator end )
|
20
|
+
def __record_method_call(method_name, *args, &block)
|
21
|
+
return if self.is_a?(Class)
|
22
|
+
__module_body_method_calls << [method_name, args, block]
|
23
|
+
end
|
24
|
+
|
25
|
+
def __all_module_body_method_calls_in_definition_order
|
26
|
+
ancestor_module_body_method_calls = included_super_modules.map(&:__module_body_method_calls).flatten(1)
|
27
|
+
all_module_body_method_calls = __module_body_method_calls + ancestor_module_body_method_calls
|
28
|
+
all_module_body_method_calls.reverse
|
29
|
+
end
|
30
|
+
|
31
|
+
def __singleton_method_call_recorder(method_name, method_args)
|
32
|
+
unless __super_module_singleton_methods_excluded_from_call_recording.include?(method_name)
|
33
|
+
method_call_recorder_args = "'#{method_name}'"
|
34
|
+
method_call_recorder_args << ", #{method_args}" unless method_args.to_s.strip == ''
|
35
|
+
"self.__record_method_call(#{method_call_recorder_args})"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'method_source'
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'module_body_method_call_recorder')) # backwards compatible with Ruby 1.8.7
|
3
|
+
|
4
|
+
module SuperModule
|
5
|
+
module V1
|
6
|
+
module SingletonMethodDefinitionStore
|
7
|
+
# excluded list of singleton methods to define (perhaps give a better name)
|
8
|
+
def __super_module_singleton_methods_excluded_from_base_definition
|
9
|
+
@__super_module_singleton_methods_excluded_from_base_definition ||= [
|
10
|
+
:__all_module_body_method_calls_in_definition_order,
|
11
|
+
:__build_singleton_method_body_source,
|
12
|
+
:__define_super_module_singleton_methods,
|
13
|
+
:__invoke_module_body_method_calls,
|
14
|
+
:__module_body_method_calls,
|
15
|
+
:__overwrite_singleton_method_from_current_super_module,
|
16
|
+
:__singleton_method_args,
|
17
|
+
:__singleton_method_body,
|
18
|
+
:__singleton_method_body_for,
|
19
|
+
:__singleton_method_call_recorder,
|
20
|
+
:__singleton_method_definition_regex,
|
21
|
+
:__super_module_having_method,
|
22
|
+
:__super_module_singleton_methods,
|
23
|
+
:__super_module_singleton_methods_excluded_from_base_definition,
|
24
|
+
:__super_module_singleton_methods_excluded_from_call_recording,
|
25
|
+
:class_eval,
|
26
|
+
:dbg_print, #debugger library friendly exclusion
|
27
|
+
:dbg_puts, #debugger library friendly exclusion
|
28
|
+
:define,
|
29
|
+
:included,
|
30
|
+
:included_super_modules,
|
31
|
+
:singleton_method_added,
|
32
|
+
:super_module_body
|
33
|
+
]
|
34
|
+
end
|
35
|
+
|
36
|
+
def __super_module_singleton_methods
|
37
|
+
@__super_module_singleton_methods ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
def __singleton_method_body_for(super_module, method_name)
|
41
|
+
super_module.__super_module_singleton_methods.detect {|sm_method_name, sm_method_body| sm_method_name == method_name}[1]
|
42
|
+
end
|
43
|
+
|
44
|
+
def included_super_modules
|
45
|
+
included_modules.select {|m| m.include?(SuperModule)}
|
46
|
+
end
|
47
|
+
|
48
|
+
def __all_methods(object)
|
49
|
+
object.public_methods + object.protected_methods + object.private_methods
|
50
|
+
end
|
51
|
+
|
52
|
+
def __super_module_having_method(method_name)
|
53
|
+
included_super_modules.detect {|included_super_module| __all_methods(included_super_module).map(&:to_s).include?(method_name.to_s)}
|
54
|
+
end
|
55
|
+
|
56
|
+
def __singleton_method_definition_regex(method_name)
|
57
|
+
/(public|protected|private)?(send)?[ \t(:"']*def(ine_method)?[ \t,:"']+(self\.)?#{method_name}\)?[ \tdo{(|]*([^\n)|;]*)?[ \t)|;]*/m
|
58
|
+
end
|
59
|
+
|
60
|
+
def __singleton_method_args(method_name, method_body)
|
61
|
+
method_arg_match = method_body.match(__singleton_method_definition_regex(method_name)).to_a[5]
|
62
|
+
end
|
63
|
+
|
64
|
+
def __singleton_method_access_level(method_name)
|
65
|
+
%w(private protected public).detect do |method_access|
|
66
|
+
method_group = "#{method_access}_methods"
|
67
|
+
send(method_group).map(&:to_s).include?(method_name.to_s)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def __build_singleton_method_body_source(method_name)
|
72
|
+
method_body = self.method(method_name).source
|
73
|
+
method_args = __singleton_method_args(method_name, method_body)
|
74
|
+
method_body = "def #{method_name}\n#{method_body}\nend" if method_args.nil?
|
75
|
+
class_self_method_def_enclosure = "class << self\n#{__singleton_method_access_level(method_name)}\ndef #{method_name}(#{method_args})\n#{__singleton_method_call_recorder(method_name, method_args)}\n"
|
76
|
+
method_body.sub(__singleton_method_definition_regex(method_name), class_self_method_def_enclosure) + "\nend\n"
|
77
|
+
end
|
78
|
+
|
79
|
+
def __singleton_method_body(method_name)
|
80
|
+
super_module_having_method = __super_module_having_method(method_name)
|
81
|
+
super_module_having_method ? __singleton_method_body_for(super_module_having_method, method_name) : __build_singleton_method_body_source(method_name)
|
82
|
+
end
|
83
|
+
|
84
|
+
def __overwrite_singleton_method_from_current_super_module(method_name, method_body)
|
85
|
+
if __super_module_having_method(method_name).nil?
|
86
|
+
__super_module_singleton_methods_excluded_from_base_definition << method_name
|
87
|
+
class_eval(method_body)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def singleton_method_added(method_name)
|
92
|
+
unless __super_module_singleton_methods_excluded_from_base_definition.include?(method_name)
|
93
|
+
method_body = __singleton_method_body(method_name)
|
94
|
+
__super_module_singleton_methods << [method_name, method_body]
|
95
|
+
__overwrite_singleton_method_from_current_super_module(method_name, method_body)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.extended(base)
|
100
|
+
base.extend(SuperModule::ModuleBodyMethodCallRecorder) unless base.respond_to?(:__record_method_call)
|
101
|
+
base.singleton_method_added(:__method_signature)
|
102
|
+
base.singleton_method_added(:__record_method_call)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|