super_module 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +102 -86
- data/VERSION +1 -1
- data/lib/super_module.rb +3 -1
- data/lib/super_module/singleton_method_definition_store.rb +15 -4
- data/spec/lib/super_module_spec.rb +68 -6
- data/spec/support/fake_active_model.rb +6 -4
- data/spec/support/foo.rb +70 -5
- data/super_module.gemspec +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9bd0e7f146de1366ca97c760ab283ca3f3623c44
|
4
|
+
data.tar.gz: ce3dad2bf81e80135bfaaea2fbdf2e305359e8b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f28bdb35c1cf93c1a0b4a98d3f22726ca95e02e1f95458b1965fa2d097f6497da7c2359ae6dcb656db49c0443f5f8fe0c1f270efffee6a5ee0da91571247547
|
7
|
+
data.tar.gz: d698ce744051e84c772eb32c38fed978e8b4711e846012a2c6cd8b3cc3058f8a95de27f2a28b01cdb7ca3ef01f44c5f249f282086d6187830adfbff7a6e96c8b
|
data/README.md
CHANGED
@@ -1,17 +1,14 @@
|
|
1
|
-
# SuperModule
|
2
|
-
[![Gem Version](https://badge.fury.io/rb/super_module.
|
3
|
-
[![Build Status](https://api.travis-ci.org/AndyObtiva/super_module.
|
4
|
-
[![Coverage Status](https://coveralls.io/repos/AndyObtiva/super_module/badge.
|
5
|
-
[![Code Climate](https://codeclimate.com/github/AndyObtiva/super_module.
|
1
|
+
# <img src="https://raw.githubusercontent.com/AndyObtiva/super_module/master/SuperModule.jpg" alt="SuperModule" align="left" height="50" /> SuperModule v1.1.1 [2015-04-09]
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/super_module.svg)](http://badge.fury.io/rb/super_module)
|
3
|
+
[![Build Status](https://api.travis-ci.org/AndyObtiva/super_module.svg?branch=master)](https://travis-ci.org/AndyObtiva/super_module)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/AndyObtiva/super_module/badge.svg?branch=master)](https://coveralls.io/r/AndyObtiva/super_module?branch=master)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/AndyObtiva/super_module.svg)](https://codeclimate.com/github/AndyObtiva/super_module)
|
6
6
|
|
7
|
-
|
8
|
-
Tired of writing complex `self.included(base)` code or using over-engineered solutions like [`ActiveSupport::Concern`](http://api.rubyonrails.org/classes/ActiveSupport/Concern.html) to accomplish that goal?
|
7
|
+
Calling [Ruby](https://www.ruby-lang.org/en/)'s [`Module#include`](http://ruby-doc.org/core-2.2.1/Module.html#method-i-include) to mix in a module does not bring in class methods by default. This can come as quite a surprise whenever a developer attempts to include class methods via a module. Fortunately, Ruby does offer a solution in the form of implementing the hook method [`Module.included(base)`](http://ruby-doc.org/core-2.2.1/Module.html#method-i-included) [following a certain boilerplate code idiom](http://www.railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/). However, this solution can hinder code maintainability and productivity flow in a big production-environment project that takes advantage of many [mixins](http://en.wikipedia.org/wiki/Mixin) to model the business domain via composable object [traits](http://en.wikipedia.org/wiki/Trait_(computer_programming)).
|
9
8
|
|
10
|
-
|
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.
|
11
10
|
|
12
|
-
[SuperModule](https://rubygems.org/gems/super_module)
|
13
|
-
|
14
|
-
This succeeds [`ActiveSupport::Concern`](http://api.rubyonrails.org/classes/ActiveSupport/Concern.html) by offering lighter syntax and simpler module dependency support.
|
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).
|
15
12
|
|
16
13
|
## Introductory Comparison
|
17
14
|
|
@@ -170,7 +167,8 @@ CourseEnrollment.new(course_id: course.id).valid?
|
|
170
167
|
|
171
168
|
* SuperModule: name of the library and Ruby module that provides functionality via mixin
|
172
169
|
* Super module: any Ruby module that mixes in SuperModule
|
173
|
-
*
|
170
|
+
* Singleton class: also known as the [metaclass](https://rubymonk.com/learning/books/4-ruby-primer-ascent/chapters/39-ruby-s-object-model/lessons/131-singleton-methods-and-metaclasses) or [eigenclass](http://eigenjoy.com/2008/05/29/railsconf08-meta-programming-ruby-for-fun-and-profit/), it is the object-instance-associated class copy available to every object in Ruby (e.g. every `Object.new` instance has a singleton class that is a copy of the `Object` class, which can house instance-specific behavior if needed)
|
171
|
+
* Singleton method: an instance method defined on an object's singleton class. Often used to refer to a class or module method defined on the [Ruby class object or module object singleton class](http://ruby-doc.com/docs/ProgrammingRuby/html/classes.html) via `def self.method_name(...)` or `class << self` enclosing `def method_name(...)`
|
174
172
|
* Class method invocation: Inherited Ruby class or module method invoked in the body of a class or module (e.g. <code>validates :username, presence: true</code>)
|
175
173
|
* Code-time: Time of writing code in a Ruby file as opposed to Run-time
|
176
174
|
* Run-time: Time of executing Ruby code
|
@@ -182,78 +180,78 @@ CourseEnrollment.new(course_id: course.id).valid?
|
|
182
180
|
* A super module can only be included in a class or another super module
|
183
181
|
* SuperModule adds <b>zero cost</b> to instantiation of including classes and invocation of included methods (both class and instance)
|
184
182
|
|
185
|
-
##
|
183
|
+
## IRB Example
|
186
184
|
|
187
|
-
|
185
|
+
Create a ruby file called super_module_irb_example.rb with the following content:
|
188
186
|
|
189
187
|
```ruby
|
188
|
+
require 'rubygems' # to be backwards compatible with Ruby 1.8.7
|
190
189
|
require 'super_module'
|
191
190
|
|
192
|
-
module
|
191
|
+
module RequiresAttributes
|
193
192
|
include SuperModule
|
194
193
|
|
195
|
-
|
196
|
-
|
197
|
-
def foo
|
198
|
-
puts 'foo'
|
199
|
-
'foo'
|
194
|
+
def self.requires(*attributes)
|
195
|
+
attributes.each {|attribute| required_attributes << attribute}
|
200
196
|
end
|
201
197
|
|
202
|
-
def self.
|
203
|
-
|
204
|
-
'self.foo'
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
module Bar
|
209
|
-
include SuperModule
|
210
|
-
include Foo
|
211
|
-
|
212
|
-
validates :user_id, presence: true
|
213
|
-
|
214
|
-
def bar
|
215
|
-
puts 'bar'
|
216
|
-
'bar'
|
198
|
+
def self.required_attributes
|
199
|
+
@required_attributes ||= []
|
217
200
|
end
|
218
|
-
|
219
|
-
def
|
220
|
-
|
221
|
-
'self.bar'
|
201
|
+
|
202
|
+
def requirements_satisfied?
|
203
|
+
!!self.class.required_attributes.reduce(true) { |result, required_attribute| result && send(required_attribute) }
|
222
204
|
end
|
223
205
|
end
|
224
206
|
|
225
|
-
class MediaAuthorization
|
226
|
-
include
|
207
|
+
class MediaAuthorization
|
208
|
+
include RequiresAttributes
|
209
|
+
attr_accessor :user_id, :credit_card_id
|
210
|
+
requires :user_id, :credit_card_id
|
227
211
|
end
|
228
|
-
|
229
|
-
MediaAuthorization.create.errors.messages.inspect
|
230
212
|
```
|
231
213
|
|
232
|
-
|
214
|
+
Open `irb` ([Interactive Ruby](https://www.ruby-lang.org/en/documentation/quickstart/)) and paste the following code snippets in. You should get the output denoted by the rockets (`=>`).
|
233
215
|
|
234
216
|
```ruby
|
235
|
-
|
217
|
+
require './super_module_irb_example.rb'
|
236
218
|
```
|
219
|
+
=> true
|
237
220
|
|
238
|
-
|
221
|
+
```ruby
|
222
|
+
MediaAuthorization.required_attributes
|
223
|
+
```
|
224
|
+
=> [:user_id, :credit_card_id]
|
239
225
|
|
240
226
|
```ruby
|
241
|
-
MediaAuthorization.new
|
227
|
+
media_authorization = MediaAuthorization.new # resulting object print-out varies
|
242
228
|
```
|
229
|
+
=> #<MediaAuthorization:0x832b36be1>
|
243
230
|
|
244
|
-
|
231
|
+
```ruby
|
232
|
+
media_authorization.requirements_satisfied?
|
233
|
+
```
|
234
|
+
=> false
|
245
235
|
|
246
236
|
```ruby
|
247
|
-
|
237
|
+
media_authorization.user_id = 387
|
248
238
|
```
|
239
|
+
=> 387
|
249
240
|
|
250
|
-
|
241
|
+
```ruby
|
242
|
+
media_authorization.requirements_satisfied?
|
243
|
+
```
|
244
|
+
=> false
|
251
245
|
|
252
246
|
```ruby
|
253
|
-
|
247
|
+
media_authorization.credit_card_id = 37
|
254
248
|
```
|
249
|
+
=> 37
|
255
250
|
|
256
|
-
|
251
|
+
```ruby
|
252
|
+
media_authorization.requirements_satisfied?
|
253
|
+
```
|
254
|
+
=> true
|
257
255
|
|
258
256
|
## How Does It Work?
|
259
257
|
|
@@ -261,68 +259,70 @@ Here is the general algorithm from the implementation:
|
|
261
259
|
|
262
260
|
```ruby
|
263
261
|
def included(base)
|
264
|
-
__invoke_super_module_class_method_calls(base)
|
265
262
|
__define_super_module_class_methods(base)
|
263
|
+
__invoke_super_module_class_method_calls(base)
|
266
264
|
end
|
267
265
|
```
|
268
266
|
|
269
|
-
#### 1)
|
267
|
+
#### 1) Defines super module class methods on the including base class
|
270
268
|
|
271
|
-
For example, suppose we have a super module called
|
269
|
+
For example, suppose we have a super module called Addressable:
|
272
270
|
|
273
271
|
```ruby
|
274
|
-
module
|
272
|
+
module Addressable
|
275
273
|
include SuperModule
|
276
274
|
|
277
|
-
|
278
|
-
validates :
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
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
283
|
end
|
284
284
|
end
|
285
285
|
|
286
|
-
class
|
287
|
-
include
|
286
|
+
class Contact < ActiveRecord::Base
|
287
|
+
include Addressable
|
288
288
|
# … more code follows
|
289
289
|
end
|
290
290
|
```
|
291
291
|
|
292
|
-
This
|
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
293
|
|
294
|
-
It does so by
|
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
295
|
|
296
|
-
|
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
297
|
|
298
|
-
|
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`:
|
299
303
|
|
300
304
|
```ruby
|
301
|
-
module
|
305
|
+
module Locatable
|
302
306
|
include SuperModule
|
303
307
|
|
304
|
-
|
305
|
-
validates :
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
# 2. Identify duplicates
|
311
|
-
# 3. Merge duplicate addressables
|
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
|
312
314
|
end
|
313
315
|
end
|
314
316
|
|
315
|
-
class
|
316
|
-
include
|
317
|
+
class Vehicle < ActiveRecord::Base
|
318
|
+
include Locatable
|
317
319
|
# … more code follows
|
318
320
|
end
|
319
321
|
```
|
320
322
|
|
321
|
-
|
323
|
+
This step guarantees invocation of the two `Locatable` <code>validates</code> method calls on the `Vehicle` object class.
|
322
324
|
|
323
|
-
It does so by
|
324
|
-
|
325
|
-
In order to avoid interference with existing class method definitions, there is an exception list for what not to record, such as <code>:included, :method_missing, :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>.
|
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.
|
326
326
|
|
327
327
|
## Limitations and Caveats
|
328
328
|
|
@@ -330,7 +330,7 @@ In order to avoid interference with existing class method definitions, there is
|
|
330
330
|
|
331
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.
|
332
332
|
|
333
|
-
* Given [SuperModule](https://rubygems.org/gems/super_module) relies on
|
333
|
+
* 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
334
|
```ruby
|
335
335
|
module AdminIdentifiable
|
336
336
|
include SuperModule
|
@@ -348,6 +348,22 @@ module AdminIdentifiable
|
|
348
348
|
```
|
349
349
|
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
350
|
|
351
|
+
## What's New?
|
352
|
+
|
353
|
+
### v1.1.1
|
354
|
+
|
355
|
+
* Added support for private and protected methods
|
356
|
+
* Added many more RSpec test cases, including testing of empty and comment containing singleton methods
|
357
|
+
|
358
|
+
### v1.1.0
|
359
|
+
|
360
|
+
* Brand new `self`-friendly algorithm that ensures true mixing of super module singleton methods into the including base class or module, thus always returning the actual base class or module `self` when invoking a super module inherited singleton method (thanks to [Banister](https://github.com/banister) for [reporting previous limitation on Reddit and providing suggestions](http://www.reddit.com/r/ruby/comments/30j66y/step_aside_activesupportconcern_supermodule_is/))
|
361
|
+
* New `included_super_modules` inherited singleton method that provides developer with a list of all included super modules similar to the Ruby `included_modules` method.
|
362
|
+
* No more use for method_missing (Thanks to Marc-André Lafortune for bringing up as a previous limitation in [AirPair article reviews](https://www.airpair.com/ruby/posts/step-aside-activesupportconcern-supermodule-is-the-new-sheriff-in-town))
|
363
|
+
* New dependency on [Banister](https://github.com/banister)'s [method_source](https://github.com/banister/method_source) library to have the self-friendly algorithm eval inherited class method sources into the including base class or module.
|
364
|
+
* Refactorings, including break-up of the original SuperModule into 3 modules in separate files
|
365
|
+
* More RSpec test coverage, including additional method definition scenarios, such as when adding dynamically via `class_eval` and `define_method`
|
366
|
+
|
351
367
|
## Feedback and Contribution
|
352
368
|
|
353
369
|
[SuperModule](https://rubygems.org/gems/super_module) is written in a very clean and maintainable test-first approach, so you are welcome to read through the code on GitHub for more in-depth details:
|
@@ -355,8 +371,8 @@ https://github.com/AndyObtiva/super_module
|
|
355
371
|
|
356
372
|
The library is quite new and can use all the feedback and help it can get. So, please do not hesitate to add comments if you have any, and please fork [the project on GitHub](https://github.com/AndyObtiva/super_module#fork-destination-box) in order to [make contributions via Pull Requests](https://github.com/AndyObtiva/super_module/pulls).
|
357
373
|
|
358
|
-
## Articles and Blog Posts
|
359
|
-
|
374
|
+
## Articles, Publications, and Blog Posts
|
375
|
+
* 2015-04-05 - [Ruby Weekly](http://rubyweekly.com): [Issue 240](http://rubyweekly.com/issues/240)
|
360
376
|
* 2015-03-27 - [AirPair](http://www.airpair.com) Article: [Step aside ActiveSupport::Concern. SuperModule is the new sheriff in town!](https://www.airpair.com/ruby/posts/step-aside-activesupportconcern-supermodule-is-the-new-sheriff-in-town)
|
361
377
|
* 2014-03-27 - [Code Painter](http://andymaleh.blogspot.com) Blog Post: [Ruby SuperModule Comes To The Rescue!!](http://andymaleh.blogspot.ca/2014/03/ruby-supermodule-comes-to-rescue.html)
|
362
378
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.1.
|
1
|
+
1.1.1
|
data/lib/super_module.rb
CHANGED
@@ -19,7 +19,9 @@ module SuperModule
|
|
19
19
|
class << self
|
20
20
|
def __define_super_module_singleton_methods(base)
|
21
21
|
__super_module_singleton_methods.each do |method_name, method_body|
|
22
|
-
|
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)
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -42,23 +42,34 @@ module SuperModule
|
|
42
42
|
included_modules.select {|m| m.include?(SuperModule)}
|
43
43
|
end
|
44
44
|
|
45
|
+
def __all_methods(object)
|
46
|
+
object.public_methods + object.protected_methods + object.private_methods
|
47
|
+
end
|
48
|
+
|
45
49
|
def __super_module_having_method(method_name)
|
46
|
-
included_super_modules.detect {|included_super_module| included_super_module.
|
50
|
+
included_super_modules.detect {|included_super_module| __all_methods(included_super_module).map(&:to_s).include?(method_name.to_s)}
|
47
51
|
end
|
48
52
|
|
49
53
|
def __singleton_method_definition_regex(method_name)
|
50
|
-
/(send)?[ \t(:"']*def(ine_method)?[ \t,:"']+(self\.)?#{method_name}\)?[ \tdo{(|]*([^\n)|;]*)?[ \t)|;]*/m
|
54
|
+
/(public|protected|private)?(send)?[ \t(:"']*def(ine_method)?[ \t,:"']+(self\.)?#{method_name}\)?[ \tdo{(|]*([^\n)|;]*)?[ \t)|;]*/m
|
51
55
|
end
|
52
56
|
|
53
57
|
def __singleton_method_args(method_name, method_body)
|
54
|
-
method_arg_match = method_body.match(__singleton_method_definition_regex(method_name)).to_a[
|
58
|
+
method_arg_match = method_body.match(__singleton_method_definition_regex(method_name)).to_a[5]
|
59
|
+
end
|
60
|
+
|
61
|
+
def __singleton_method_access_level(method_name)
|
62
|
+
%w(private protected public).detect do |method_access|
|
63
|
+
method_group = "#{method_access}_methods"
|
64
|
+
send(method_group).map(&:to_s).include?(method_name.to_s)
|
65
|
+
end
|
55
66
|
end
|
56
67
|
|
57
68
|
def __build_singleton_method_body_source(method_name)
|
58
69
|
method_body = self.method(method_name).source
|
59
70
|
method_args = __singleton_method_args(method_name, method_body)
|
60
71
|
method_body = "def #{method_name}\n#{method_body}\nend" if method_args.nil?
|
61
|
-
class_self_method_def_enclosure = "class << self\
|
72
|
+
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"
|
62
73
|
method_body.sub(__singleton_method_definition_regex(method_name), class_self_method_def_enclosure) + "\nend\n"
|
63
74
|
end
|
64
75
|
|
@@ -26,10 +26,77 @@ describe SuperModule do
|
|
26
26
|
expect(subject.validations).to include(['foo', {:presence => true}])
|
27
27
|
end
|
28
28
|
|
29
|
-
it 'includes class
|
29
|
+
it 'includes class method declared via "self.method_name"' do
|
30
30
|
expect(subject.foo).to eq('self.foo')
|
31
31
|
end
|
32
32
|
|
33
|
+
it 'includes class method declared via "self.method_name" taking a single parameter' do
|
34
|
+
expect(subject.foo_single_param('param1_value')).to eq('self.foo(param1_value)')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'includes class method declared via "self.method_name" taking multiple parameters' do
|
38
|
+
expect(subject.foo_multi_params('param1_value', 'param2_value', 'param3_value')).to eq('self.foo(param1_value,param2_value,param3_value)')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'includes class method declared via "self.method_name" taking a block' do
|
42
|
+
formatter = Proc.new {|value| "Block formatted #{value}"}
|
43
|
+
expect(subject.foo_block(&formatter)).to eq('Block formatted self.foo')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'includes class method declared via "self.method_name" taking a single paramter and a block' do
|
47
|
+
formatter = Proc.new {|value, param1| "Block formatted #{value} with #{param1}"}
|
48
|
+
expect(subject.foo_single_param_block('param1_value', &formatter)).to eq('Block formatted self.foo with param1_value')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'includes class method declared via "self.method_name" taking multiple paramters and a block' do
|
52
|
+
formatter = Proc.new {|value, param1, param2, param3| "Block formatted #{value} with #{param1},#{param2},#{param3}"}
|
53
|
+
expect(subject.foo_multi_params_block('param1_value', 'param2_value', 'param3_value', &formatter)).to eq('Block formatted self.foo with param1_value,param2_value,param3_value')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'includes class method declared via "self.method_name" on one line' do
|
57
|
+
expect(subject.foo_one_line).to eq('self.foo_one_line')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'includes class method declared via "class < self"' do
|
61
|
+
expect(subject.foo_class_self).to eq('self.foo_class_self')
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'includes class method declared via "class < self" using define_method' do
|
65
|
+
expect(subject.foo_class_self_define_method).to eq('self.foo_class_self_define_method')
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'includes private class method' do
|
69
|
+
expect{subject.foo_private}.to raise_error
|
70
|
+
expect(subject.private_methods.map(&:to_s)).to include('foo_private')
|
71
|
+
expect(subject.send(:foo_private)).to eq('self.foo_private')
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'includes protected class method (declared using protected :method_name)' do
|
75
|
+
expect{subject.foo_protected}.to raise_error
|
76
|
+
expect(subject.protected_methods.map(&:to_s)).to include('foo_protected')
|
77
|
+
expect(subject.send(:foo_protected)).to eq('self.foo_protected')
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'includes empty class method' do
|
81
|
+
expect(subject.empty).to eq(nil)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'includes empty class method with one empty line' do
|
85
|
+
expect(subject.empty_one_empty_line).to eq(nil)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'includes empty class method with comment' do
|
89
|
+
expect(subject.empty_with_comment).to eq(nil)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'includes empty class method one line definition' do
|
93
|
+
expect(subject.empty_one_line_definition).to eq(nil)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'includes empty class method one line definition with spaces' do
|
97
|
+
expect(subject.empty_one_line_definition_with_spaces).to eq(nil)
|
98
|
+
end
|
99
|
+
|
33
100
|
it 'includes instance methods' do
|
34
101
|
instance = subject.new
|
35
102
|
|
@@ -84,11 +151,6 @@ describe SuperModule do
|
|
84
151
|
it 'provides class method self as the including base class as in the class method (meh)' do
|
85
152
|
expect(subject.meh).to eq(subject)
|
86
153
|
end
|
87
|
-
|
88
|
-
#TODO explicitly declare test cases in support files as it statements
|
89
|
-
#TODO test empty method definition
|
90
|
-
#TODO test empty method definition with comment
|
91
|
-
#TODO test case of method call receiving a block
|
92
154
|
end
|
93
155
|
|
94
156
|
context "included by a module (Foo), included by another module (Bar), included by a third module (Baz) that is included by a class (BazActiveRecord)" do
|
@@ -4,11 +4,13 @@ module FakeActiveModel
|
|
4
4
|
|
5
5
|
include SuperModule
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
def self.validates(attribute, options)
|
8
|
+
validations << [attribute, options]
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
def self.validations
|
12
|
+
@validations ||= []
|
13
|
+
end
|
12
14
|
|
13
15
|
end
|
14
16
|
|
data/spec/support/foo.rb
CHANGED
@@ -4,17 +4,82 @@ module Foo
|
|
4
4
|
include FakeActiveModel
|
5
5
|
validates 'foo', {:presence => true}
|
6
6
|
|
7
|
+
class << self
|
8
|
+
def foo_class_self
|
9
|
+
'self.foo_class_self'
|
10
|
+
end
|
11
|
+
|
12
|
+
def foo_class_self_define_method; 'self.foo_class_self_define_method'; end
|
13
|
+
|
14
|
+
def foo_private_declaration_follow_up
|
15
|
+
'self.foo_private_declaration_follow_up'
|
16
|
+
end
|
17
|
+
private :foo_private_declaration_follow_up
|
18
|
+
|
19
|
+
def foo_protected_declaration_follow_up
|
20
|
+
'self.foo_protected_declaration_follow_up'
|
21
|
+
end
|
22
|
+
protected :foo_protected_declaration_follow_up
|
23
|
+
|
24
|
+
private
|
25
|
+
def foo_private
|
26
|
+
'self.foo_private'
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
def foo_protected
|
31
|
+
'self.foo_protected'
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.meh
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
7
40
|
def self.foo
|
8
41
|
'self.foo'
|
9
42
|
end
|
10
43
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
44
|
+
def self.foo_one_line; 'self.foo_one_line'; end
|
45
|
+
|
46
|
+
def self.foo_single_param(param1)
|
47
|
+
"self.foo(#{param1})"
|
16
48
|
end
|
17
49
|
|
50
|
+
def self.foo_multi_params(param1, param2, param3)
|
51
|
+
"self.foo(#{param1},#{param2},#{param3})"
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.foo_block(&formatter)
|
55
|
+
formatter.call('self.foo')
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.foo_single_param_block(param1, &formatter)
|
59
|
+
formatter.call('self.foo', param1)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.foo_multi_params_block(param1, param2, param3, &formatter)
|
63
|
+
formatter.call('self.foo', param1, param2, param3)
|
64
|
+
end
|
65
|
+
|
66
|
+
public
|
67
|
+
|
68
|
+
def self.empty
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.empty_one_empty_line
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.empty_with_comment
|
76
|
+
# no op
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.empty_one_line_definition; end
|
80
|
+
|
81
|
+
def self.empty_one_line_definition_with_spaces; end
|
82
|
+
|
18
83
|
def foo
|
19
84
|
'foo'
|
20
85
|
end
|
data/super_module.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: super_module 1.1.
|
5
|
+
# stub: super_module 1.1.1 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "super_module"
|
9
|
-
s.version = "1.1.
|
9
|
+
s.version = "1.1.1"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Andy Maleh"]
|
14
|
-
s.date = "2015-04-
|
14
|
+
s.date = "2015-04-09"
|
15
15
|
s.description = "SuperModule allows defining class methods and method invocations the same way a super class does without using def included(base). This also succeeds ActiveSupport::Concern by offering lighter syntax"
|
16
16
|
s.extra_rdoc_files = [
|
17
17
|
"LICENSE.txt",
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: super_module
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Maleh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: method_source
|