nanoboy 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +6 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +39 -0
- data/Rakefile +34 -0
- data/VERSION +1 -0
- data/lib/nanoboy.rb +63 -0
- data/lib/nanoboy/hook.rb +72 -0
- data/lib/nanoboy/version.rb +13 -0
- data/nanoboy.gemspec +21 -0
- data/test/nanoboy_test.rb +51 -0
- metadata +90 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Bernat Foj Capell
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Nanoboy
|
2
|
+
|
3
|
+
nanoboy is a little ruby gem that allows you to extend classes and/or modules with several advantages over the usual ruby include/extend:
|
4
|
+
|
5
|
+
* lazy extension -- the extended class is not loaded if it's not already in the memory. The extension will be applied once the class is loaded.
|
6
|
+
* persistent on class reloading -- if you are developing in Rails, where classes are reloaded on each request, use nanoboy and forget about reloading problems and callbacks.
|
7
|
+
|
8
|
+
Due to the lazy extension feature, this gem is specially useful when you have loading order problems. For example, if you are extending a lib that is extending another lib. Yes, these things happen.
|
9
|
+
|
10
|
+
|
11
|
+
Syntax
|
12
|
+
------
|
13
|
+
|
14
|
+
There are two possible syntaxes:
|
15
|
+
|
16
|
+
* Explicit
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
Nanoboy.include! :ClassToExtend, MyAwesomeModule
|
20
|
+
```
|
21
|
+
|
22
|
+
* Sugared
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
:ClassToExtend.include! MyAwesomeModule
|
26
|
+
```
|
27
|
+
|
28
|
+
Note that the class to be extended is named with its symbol, not the constant. If you use the constant, it gets loaded and you lose.
|
29
|
+
|
30
|
+
And yes it works with namespaces too:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
:"CrazyModule::ClassToExtend".include! MyAwesomeModule
|
34
|
+
```
|
35
|
+
|
36
|
+
About the name
|
37
|
+
--------------
|
38
|
+
|
39
|
+
As @jondeandres said, if there is a well-known gem called FactoryGirl, what's the problem with Nanoboy?
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test nanoboy'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def update_version(function, name)
|
17
|
+
major, minor, tiny = File.read("VERSION").strip.split(".").map { |i| i.to_i }
|
18
|
+
eval "#{name} #{function}= 1"
|
19
|
+
File.open("VERSION", "w") { |f| f.puts [major, minor, tiny].join(".") }
|
20
|
+
puts `cat VERSION`
|
21
|
+
end
|
22
|
+
|
23
|
+
{ :bump => "+", :debump => "-"}.each do |key, value|
|
24
|
+
namespace key do
|
25
|
+
[ :major, :minor, :tiny].each do |position|
|
26
|
+
eval <<-CODE
|
27
|
+
desc "#{key.to_s.capitalize} #{position} number by 1"
|
28
|
+
task :#{position} do
|
29
|
+
update_version("#{value}", "#{position}")
|
30
|
+
end
|
31
|
+
CODE
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/nanoboy.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'nanoboy/hook'
|
2
|
+
|
3
|
+
module Nanoboy
|
4
|
+
class << self
|
5
|
+
attr_accessor :extensions, :methods
|
6
|
+
end
|
7
|
+
|
8
|
+
self.extensions ||= {}
|
9
|
+
self.methods = %w{include extend helper}
|
10
|
+
|
11
|
+
# Returns true if the +sym+ class has scheduled extensions to be included
|
12
|
+
def self.has_extensions?(sym)
|
13
|
+
extensions[sym.to_s]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns a Module that, when included, will provide all the scheduled funcionality for +sym+
|
17
|
+
def self.extensions_for(sym)
|
18
|
+
Module.new{
|
19
|
+
block = ::Proc.new{|recipient|
|
20
|
+
Nanoboy.methods.each do |method|
|
21
|
+
Array(Nanoboy.extensions[sym.to_s][method]).each do |k|
|
22
|
+
recipient.send(method, k)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
}
|
26
|
+
define_method :included, block
|
27
|
+
module_function :included
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.load_extensions_for klass, recipient = klass
|
32
|
+
if has_extensions?(klass.name)
|
33
|
+
recipient.send :include, extensions_for(klass.name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
methods.each do |method|
|
38
|
+
|
39
|
+
self.class_eval <<-EOS
|
40
|
+
# Schedules the inclusion of +klass+ inside +recipient+
|
41
|
+
# Use this instead of sending direct includes, extends or helpers
|
42
|
+
def self.#{method}!(recipient, klass) # def self.append_include('ClassToExtend', MyModule)
|
43
|
+
extensions[recipient.to_s] ||= {} # extensions['ClassToExtend'.to_s] ||= {}
|
44
|
+
extensions[recipient.to_s]["#{method}"] ||= [] # extensions['ClassToExtend'.to_s]["include"] ||= []
|
45
|
+
extensions[recipient.to_s]["#{method}"] << klass # extensions['ClassToExtend'.to_s]["include"] << klass
|
46
|
+
# use class_eval to avoid evaluation of recipient # # use class_eval to avoid evaluation of ClassToExtend
|
47
|
+
class_eval <<-EOCONSTANTIZE # class_eval <<-EOCONSTANTIZE
|
48
|
+
if defined?(\#{recipient}) # if defined?(ClassToExtend)
|
49
|
+
\#{recipient}.send("#{method}", klass) # ClassToExtend.send("include", MyModule)
|
50
|
+
end # end
|
51
|
+
EOCONSTANTIZE
|
52
|
+
end # end
|
53
|
+
EOS
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.enable
|
57
|
+
Nanoboy::Hook.activate
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
# Go!
|
63
|
+
Nanoboy.enable
|
data/lib/nanoboy/hook.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module Nanoboy
|
2
|
+
# This module acts as a hook to enable Nanoboy in the class that includes it
|
3
|
+
module Hook
|
4
|
+
|
5
|
+
# This is the main Nanoboy hook, that is fired in the usual class creation
|
6
|
+
# class MyClass (< MyParent); end
|
7
|
+
module InheritedHook
|
8
|
+
def inherited_with_nanoboy(subclass)
|
9
|
+
inherited_without_nanoboy(subclass)
|
10
|
+
Nanoboy.load_extensions_for subclass
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included klass
|
14
|
+
Hook.nanoboy_alias klass, :inherited
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# This module allows to also fire Nanoboy when defining new classes using
|
19
|
+
# the Module#const_set method
|
20
|
+
module ConstSetHook
|
21
|
+
def const_set_with_nanoboy(name, klass)
|
22
|
+
set_object = const_set_without_nanoboy(name, klass)
|
23
|
+
if set_object.is_a? Class
|
24
|
+
Nanoboy.load_extensions_for set_object
|
25
|
+
end
|
26
|
+
set_object
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.included klass
|
30
|
+
Hook.nanoboy_alias klass, :const_set
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# This is the module that allows the sugared syntax: :ClassToExtend.include!
|
35
|
+
module SymbolHook
|
36
|
+
def self.included(klass)
|
37
|
+
klass.class_eval do
|
38
|
+
Nanoboy.methods.each do |method|
|
39
|
+
define_method "#{method}!" do |extension|
|
40
|
+
Nanoboy.send("#{method}!", self, extension)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# You can include Nanoboy::Hook manually in your classes.
|
48
|
+
# Useful if it's activated too late for you
|
49
|
+
def self.included(klass)
|
50
|
+
klass.singleton_class.instance_eval { include InheritedHook, ConstSetHook }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Similar to ActiveSupport alias_method_chain, but ensuring the alias
|
54
|
+
# is created only once
|
55
|
+
def self.nanoboy_alias klass, name
|
56
|
+
klass.class_eval do
|
57
|
+
unless (instance_methods + private_methods).include? :"#{name}_without_nanoboy"
|
58
|
+
alias_method "#{name}_without_nanoboy", name
|
59
|
+
alias_method name, "#{name}_with_nanoboy"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Performs the hooking into the system that will make Nanoboy work
|
65
|
+
def self.activate
|
66
|
+
# Modules do not have Class#inherited since it is only defined for classes
|
67
|
+
Class.instance_eval { include InheritedHook }
|
68
|
+
Module.instance_eval { include ConstSetHook }
|
69
|
+
Symbol.instance_eval { include SymbolHook }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Nanoboy
|
2
|
+
def self.version
|
3
|
+
VERSION::STRING
|
4
|
+
end
|
5
|
+
module VERSION #:nodoc:
|
6
|
+
file = File.join(File.dirname(__FILE__), "..", "..", "VERSION")
|
7
|
+
version = File.read(file).strip.split(".")
|
8
|
+
|
9
|
+
MAJOR, MINOR, *TINY = version
|
10
|
+
|
11
|
+
STRING = [MAJOR, MINOR, TINY].join('.')
|
12
|
+
end
|
13
|
+
end
|
data/nanoboy.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "nanoboy/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "nanoboy"
|
7
|
+
s.version = Nanoboy.version
|
8
|
+
s.authors = ["Bernat Foj"]
|
9
|
+
s.homepage = "http://www.github.com/bfcapell/nanoboy"
|
10
|
+
s.summary = %q{Lazy extend ruby classes and modules}
|
11
|
+
s.description = %q{Lazy extend ruby classes and modules}
|
12
|
+
|
13
|
+
s.rubyforge_project = "nanoboy"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_development_dependency "mocha", "~> 0.10.0"
|
21
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'nanoboy'
|
4
|
+
require 'mocha'
|
5
|
+
|
6
|
+
class NanoboyTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
# Mock classes, with namespace to test the general case
|
9
|
+
class Test::MyClass; end
|
10
|
+
class Test::OtherClass; end
|
11
|
+
module Test::MyModule
|
12
|
+
def my_method; end
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_load_methods_should_trigger_include_when_defined
|
16
|
+
Nanoboy.methods.each do |method|
|
17
|
+
Test::MyClass.expects(method).with(Test::MyModule)
|
18
|
+
Nanoboy.send("#{method}!", 'Test::MyClass', Test::MyModule)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_load_methods_should_trigger_include_with_sugared_syntax
|
23
|
+
Nanoboy.methods.each do |method|
|
24
|
+
Test::MyClass.expects(method).with(Test::MyModule)
|
25
|
+
:'Test::MyClass'.send("#{method}!", Test::MyModule)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_load_methods_should_schedule_automatic_inclusion_when_defined_after_with_const_set
|
30
|
+
Test.send('remove_const', 'MyClass')
|
31
|
+
Nanoboy.include!('Test::MyClass', Test::MyModule)
|
32
|
+
Test.const_set('MyClass', Class.new)
|
33
|
+
assert Test::MyClass.instance_methods.map(&:to_sym).include?(:my_method)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_load_methods_should_schedule_automatic_inclusion_when_defined_after_with_normal_def
|
37
|
+
Test.send('remove_const', 'MyClass')
|
38
|
+
Nanoboy.include!('Test::MyClass', Test::MyModule)
|
39
|
+
Test.class_eval "class MyClass; end"
|
40
|
+
assert Test::MyClass.instance_methods.map(&:to_sym).include?(:my_method)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_load_methods_should_allow_inclusion_in_other_classes
|
44
|
+
Nanoboy.include!('Test::MyClass', Test::MyModule)
|
45
|
+
Test::OtherClass.class_eval do
|
46
|
+
Nanoboy.load_extensions_for Test::MyClass, self
|
47
|
+
end
|
48
|
+
assert Test::OtherClass.instance_methods.map(&:to_sym).include?(:my_method)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nanoboy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Bernat Foj
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-07-18 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: mocha
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 55
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
- 10
|
32
|
+
- 0
|
33
|
+
version: 0.10.0
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
description: Lazy extend ruby classes and modules
|
37
|
+
email:
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- Gemfile
|
47
|
+
- MIT-LICENSE
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- VERSION
|
51
|
+
- lib/nanoboy.rb
|
52
|
+
- lib/nanoboy/hook.rb
|
53
|
+
- lib/nanoboy/version.rb
|
54
|
+
- nanoboy.gemspec
|
55
|
+
- test/nanoboy_test.rb
|
56
|
+
homepage: http://www.github.com/bfcapell/nanoboy
|
57
|
+
licenses: []
|
58
|
+
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 3
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
hash: 3
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
requirements: []
|
83
|
+
|
84
|
+
rubyforge_project: nanoboy
|
85
|
+
rubygems_version: 1.8.10
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: Lazy extend ruby classes and modules
|
89
|
+
test_files: []
|
90
|
+
|