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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5ed999facd85a8593e0e0af6a036c625a68f51ae
4
- data.tar.gz: 27219be2a1966b18e253d839b76232e118d27824
3
+ metadata.gz: 9bd0e7f146de1366ca97c760ab283ca3f3623c44
4
+ data.tar.gz: ce3dad2bf81e80135bfaaea2fbdf2e305359e8b6
5
5
  SHA512:
6
- metadata.gz: 6bb3130d30bb2bf2baf04c657a828020375f2b3bb0a5504cae2d8da924ba5184deee3aace7cc4f2359c4bb8f85cc603189734e27fecdee6461b75fb873a4144c
7
- data.tar.gz: bc3d4b819dbd682c75c8a26b5d58da614615f60f14f5e880f6128dcd2ab15feb947b571d79aed3f95a7907c3ab7fbe68d71a71f398ca72836f638ba011de5ab8
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.png)](http://badge.fury.io/rb/super_module)
3
- [![Build Status](https://api.travis-ci.org/AndyObtiva/super_module.png?branch=master)](https://travis-ci.org/AndyObtiva/super_module)
4
- [![Coverage Status](https://coveralls.io/repos/AndyObtiva/super_module/badge.png?branch=master)](https://coveralls.io/r/AndyObtiva/super_module?branch=master)
5
- [![Code Climate](https://codeclimate.com/github/AndyObtiva/super_module.png)](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" /> &nbsp; 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
- Tired of [Ruby](https://www.ruby-lang.org/en/)'s modules not allowing you to mix in class methods easily?
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
- Well, worry no more! [SuperModule](https://rubygems.org/gems/super_module) comes to the rescue!
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) allows defining class methods and method invocations the same way a super class does without using [`self.included(base)`](http://ruby-doc.org/core-2.2.1/Module.html#method-i-included).
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
- * Class method definition: Ruby class or module method declared with <code>self.method_name</code> or <code>class << self</code>
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
- ## Another Example
183
+ ## IRB Example
186
184
 
187
- Copy and paste the following code snippets in <code>irb</code> and you should get the output denoted by double arrows (<code>=></code>).
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 Foo
191
+ module RequiresAttributes
193
192
  include SuperModule
194
193
 
195
- validates :credit_card_id, presence: true
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.foo
203
- puts 'self.foo'
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 self.bar
220
- puts 'self.bar'
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 < ActiveRecord::Base
226
- include Bar
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
- => "{:credit_card_id=>[\"can't be blank\"], :user_id=>[\"can't be blank\"]}"
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
- MediaAuthorization.new.foo
217
+ require './super_module_irb_example.rb'
236
218
  ```
219
+ => true
237
220
 
238
- => "foo"
221
+ ```ruby
222
+ MediaAuthorization.required_attributes
223
+ ```
224
+ => [:user_id, :credit_card_id]
239
225
 
240
226
  ```ruby
241
- MediaAuthorization.new.bar
227
+ media_authorization = MediaAuthorization.new # resulting object print-out varies
242
228
  ```
229
+ => #<MediaAuthorization:0x832b36be1>
243
230
 
244
- => "bar"
231
+ ```ruby
232
+ media_authorization.requirements_satisfied?
233
+ ```
234
+ => false
245
235
 
246
236
  ```ruby
247
- MediaAuthorization.foo
237
+ media_authorization.user_id = 387
248
238
  ```
239
+ => 387
249
240
 
250
- => "self.foo"
241
+ ```ruby
242
+ media_authorization.requirements_satisfied?
243
+ ```
244
+ => false
251
245
 
252
246
  ```ruby
253
- MediaAuthorization.bar
247
+ media_authorization.credit_card_id = 37
254
248
  ```
249
+ => 37
255
250
 
256
- => "self.bar"
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) Invoke super module class method calls on the including base class.
267
+ #### 1) Defines super module class methods on the including base class
270
268
 
271
- For example, suppose we have a super module called `Locatable`:
269
+ For example, suppose we have a super module called Addressable:
272
270
 
273
271
  ```ruby
274
- module Locatable
272
+ module Addressable
275
273
  include SuperModule
276
274
 
277
- validates :x_coordinate, numericality: true
278
- validates :y_coordinate, numericality: true
279
-
280
- def move(x, y)
281
- self.x_coordinate += x
282
- self.y_coordinate += y
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 Vehicle < ActiveRecord::Base
287
- include Locatable
286
+ class Contact < ActiveRecord::Base
287
+ include Addressable
288
288
  # … more code follows
289
289
  end
290
290
  ```
291
291
 
292
- This first step guarantees invocation of the two `Locatable` <code>validates</code> method calls on the `Vehicle` object class.
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 relying on `method_missing(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.
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
- #### 2) Defines super module class methods on the including base class
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
- For example, suppose we have a super module called Addressable:
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 Addressable
305
+ module Locatable
302
306
  include SuperModule
303
307
 
304
- include Locatable
305
- validates :city, presence: true, length: { maximum: 255 }
306
- validates :state, presence: true, length: { is: 2 }
307
-
308
- def self.merge_duplicates
309
- # 1. Look through all Addressable instances in the database
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 Contact < ActiveRecord::Base
316
- include Addressable
317
+ class Vehicle < ActiveRecord::Base
318
+ include Locatable
317
319
  # … more code follows
318
320
  end
319
321
  ```
320
322
 
321
- The second step ensures that <code>merge_duplicates</code> is included in Contact as a class method, allowing the call <code>Contact.merge_duplicates</code>
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 recording every class method defined using the <code>self.singleton_method_added(method_name)</code> added hook, and then later replaying these class definitions on the including base class during invocation of <code>self.included(base)</code>.
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 <code>self.included(base)</code> in its implementation, 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:
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.0
1
+ 1.1.1
@@ -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
- base.class_eval(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)
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.methods.map(&:to_s).include?(method_name.to_s)}
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[4]
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\ndefine_method('#{method_name}') do |#{method_args}|\n#{__singleton_method_call_recorder(method_name, method_args)}\n"
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 methods declared via "self.method_name"' do
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
- # Defines method on a single line to provide as a test case for SuperModule
8
- def self.validates(attribute, options); validations << [attribute, options]; end
7
+ def self.validates(attribute, options)
8
+ validations << [attribute, options]
9
+ end
9
10
 
10
- # Defines method on a single line to provide as a test case for SuperModule
11
- def self.validations; @validations ||= []; end
11
+ def self.validations
12
+ @validations ||= []
13
+ end
12
14
 
13
15
  end
14
16
 
@@ -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
- # Defines singleton method via define_method to provide as a test case for SuperModule
12
- class << self
13
- send(:define_method, :meh) do
14
- self
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
@@ -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.0 ruby lib
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.0"
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-06"
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.0
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-06 00:00:00.000000000 Z
11
+ date: 2015-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: method_source