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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9bd0e7f146de1366ca97c760ab283ca3f3623c44
4
- data.tar.gz: ce3dad2bf81e80135bfaaea2fbdf2e305359e8b6
3
+ metadata.gz: 2417fe4ac87bcf7e975d971de2f86caa01f1f357
4
+ data.tar.gz: 305eda58e8250ce45e24af27325ba09f2f34c9f1
5
5
  SHA512:
6
- metadata.gz: 6f28bdb35c1cf93c1a0b4a98d3f22726ca95e02e1f95458b1965fa2d097f6497da7c2359ae6dcb656db49c0443f5f8fe0c1f270efffee6a5ee0da91571247547
7
- data.tar.gz: d698ce744051e84c772eb32c38fed978e8b4711e846012a2c6cd8b3cc3058f8a95de27f2a28b01cdb7ca3ef01f44c5f249f282086d6187830adfbff7a6e96c8b
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" /> &nbsp; SuperModule v1.1.1 [2015-04-09]
1
+ # <img src="https://raw.githubusercontent.com/AndyObtiva/super_module/master/SuperModule.jpg" alt="SuperModule" align="left" height="50" /> &nbsp; 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. Including `SuperModule` at the top of a Ruby module's body automatically ensures inclusion of class methods whenever a developer mixes it in via [`Module#include`](http://ruby-doc.org/core-2.2.1/Module.html#method-i-include).
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
- module UserIdentifiable
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
- With `include SuperModule` declared on top, 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.
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) Include [`SuperModule`](https://rubygems.org/gems/super_module) at the top of the module
125
+ #### 2) Call `super_module(name)` and pass it the super module body in a block
119
126
 
120
127
  ```ruby
121
- module UserIdentifiable
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
- module Accountable
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
- module RequiresAttributes
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
- Here is the general algorithm from the implementation:
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
- This step guarantees invocation of the two `Locatable` <code>validates</code> method calls on the `Vehicle` object class.
324
-
325
- It does so by relying on an interally defined method `__record_method_call(method_name, *args, &block)` to record every class method call that happens in the super module class body, and later replaying those calls on the including base class during `self.included(base)` by using Ruby's `send(method_name, *args, &block)` method introspection.
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, not to be mixed in at run-time.
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
- module AdminIdentifiable
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-2015 Andy Maleh. See LICENSE.txt for
328
+ Copyright (c) 2014-2016 Andy Maleh. See LICENSE.txt for
382
329
  further details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.1
1
+ 1.2.0
@@ -1,7 +1,6 @@
1
1
  require_relative '../../../lib/super_module'
2
2
 
3
- module Foo
4
- include SuperModule
3
+ super_module :Foo
5
4
  def self.hello
6
5
  self
7
6
  end
@@ -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
- def self.included(original_base)
15
- original_base.class_eval do
16
- extend SuperModule::ModuleBodyMethodCallRecorder
17
- extend SuperModule::SingletonMethodDefinitionStore
18
-
19
- class << self
20
- def __define_super_module_singleton_methods(base)
21
- __super_module_singleton_methods.each do |method_name, method_body|
22
- # The following is needed for cases where a method is declared public/protected/private after it was added
23
- 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")
24
- base.class_eval(refreshed_access_level_method_body)
25
- end
26
- end
27
-
28
- def __invoke_module_body_method_calls(base)
29
- __all_module_body_method_calls_in_definition_order.each do |method_name, args, block|
30
- base.send(method_name, *args, &block)
31
- end
32
- end
33
-
34
- def included(base)
35
- __define_super_module_singleton_methods(base)
36
- __invoke_module_body_method_calls(base)
37
- end
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