much-plugin 0.0.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fbaad5b7dab33dc311e475f220cfbfc341f7def78de5fbf58d0f73b1663d6824
4
+ data.tar.gz: 6fad28bbd725bd87257f6c6e60e9cce2467a0f5ae5ab81b4030b7c266a419abd
5
+ SHA512:
6
+ metadata.gz: b2fce8bb616549534a12f8532dcf4efcc23306ea1cfba943ec7360c7ee07645fef72ac412e983369685c6df477c51f3be04c0b583b3a7357a6db026db6c7858f
7
+ data.tar.gz: 4321146b24ba6f7cdebecfa094f63bb3244996188df4d03b609a14c1cbc876cb3f06dc8d747ca1256aae0c7ba077ba3b68b7197c851fe86dae0639d11de1a11b
data/Gemfile CHANGED
@@ -2,5 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'rake', "~> 10.4.0"
6
- gem 'pry', "~> 0.9.0"
5
+ gem "pry", "~> 0.12.2"
6
+
7
+ # to release, uncomment this and run `bundle exec ggem r -f`
8
+ gem 'ggem'
File without changes
data/README.md CHANGED
@@ -1,10 +1,112 @@
1
1
  # MuchPlugin
2
2
 
3
- TODO: Write a gem description
3
+ An API to ensure mixin included logic (the "plugin") only runs once.
4
4
 
5
5
  ## Usage
6
6
 
7
- TODO: Write code samples and usage instructions here
7
+ ```ruby
8
+ requre "much-plugin"
9
+
10
+ module MyPluginMixin
11
+ include MuchPlugin
12
+
13
+ plugin_included do
14
+ # do some stuff ...
15
+ # - will be class eval'd in the scope of the receiver of `MyPluginMixin`
16
+ # - will only be executed once per receiver, no matter how many times
17
+ # `MyPluginMixin` is included in that receiver
18
+ end
19
+ end
20
+ ```
21
+
22
+ Mix `MuchPlugin` in on other mixins that act as "plugins" to other components. Define included hooks using `plugin_included` that will be class eval'd in the scope of the receiver.
23
+
24
+ This allows you to define multiple hooks separately and ensures each hook will only be executed once - even if your plugin is mixed in multiple times on the same receiver.
25
+
26
+ ### `plugin_class_methods` / `plugin_instance_methods`
27
+
28
+ MuchPlugin provides convenience methods for defining instance/class methods on plugin receivers:
29
+
30
+ ```ruby
31
+ requre "much-plugin"
32
+
33
+ module MyPluginMixin
34
+ include MuchPlugin
35
+
36
+ plugin_class_methods do
37
+ # define some methods ...
38
+ # - these methods will become class methods on the receiver
39
+ end
40
+
41
+ plugin_instance_methods do
42
+ # define some methods ...
43
+ # - these methods will become instance methods on the receiver
44
+ end
45
+ end
46
+ ```
47
+
48
+ ### `after_plugin_included`
49
+
50
+ These hooks work just like the `plugin_included` hooks, except they are evaluated _after_ any plugin class/instance methods have been evaluated. E.g. use this to call a class method that the plugin defines.
51
+
52
+ ```ruby
53
+ requre "much-plugin"
54
+
55
+ module MyPluginMixin
56
+ include MuchPlugin
57
+
58
+ after_plugin_included do
59
+ configure_the_plugin
60
+ end
61
+
62
+ plugin_class_methods do
63
+ def configure_the_plugin
64
+ # ...
65
+ end
66
+ end
67
+ end
68
+ ```
69
+
70
+ ## Example
71
+
72
+ ```ruby
73
+ requre "much-plugin"
74
+
75
+ module AnotherMixin
76
+ def another
77
+ "another"
78
+ end
79
+ end
80
+
81
+ module MyPlugin
82
+ include MuchPlugin
83
+
84
+ plugin_included do
85
+ include AnotherMixin
86
+ end
87
+
88
+ plugin_class_methods do
89
+ def a_class_method
90
+ "a-class-method"
91
+ end
92
+ end
93
+
94
+ plugin_instance_methods do
95
+ def an_instance_method
96
+ "an-instance-method"
97
+ end
98
+ end
99
+ end
100
+
101
+ class MyClass
102
+ include MyPlugin
103
+ end
104
+
105
+ my_class = MyClass.new
106
+ my_class.another # => "another"
107
+ my_class.an_instance_method # => "an-instance-method"
108
+ MyClass.a_class_method # => "a-class-method"
109
+ ```
8
110
 
9
111
  ## Installation
10
112
 
@@ -0,0 +1,27 @@
1
+ require 'much-plugin'
2
+ require 'benchmark'
3
+
4
+ module Methods; end
5
+
6
+ module MyMixin
7
+ def self.included(receiver)
8
+ receiver.class_eval{ include Methods }
9
+ end
10
+ end
11
+
12
+ module MyPlugin
13
+ include MuchPlugin
14
+
15
+ plugin_included do
16
+ include Methods
17
+ end
18
+ end
19
+
20
+ Benchmark.bmbm do |x|
21
+ x.report("MyMixin") do
22
+ 10_000.times{ Class.new{ include MyMixin } }
23
+ end
24
+ x.report("MyPlugin") do
25
+ 10_000.times{ Class.new{ include MyPlugin } }
26
+ end
27
+ end
@@ -1,5 +1,90 @@
1
1
  require "much-plugin/version"
2
2
 
3
3
  module MuchPlugin
4
- # TODO: your code goes here...
4
+ def self.included(receiver)
5
+ receiver.class_eval{ extend ClassMethods }
6
+ end
7
+
8
+ module ClassMethods
9
+ # install an included block that first checks if this plugin's receiver mixin
10
+ # has already been included. If it has not been, include the receiver mixin
11
+ # and run all of the `plugin_included` blocks
12
+ def included(plugin_receiver)
13
+ return if plugin_receiver.include?(self.much_plugin_included_detector)
14
+ plugin_receiver.send(:include, self.much_plugin_included_detector)
15
+
16
+ self.much_plugin_included_blocks.each do |block|
17
+ plugin_receiver.class_eval(&block)
18
+ end
19
+
20
+ self.much_plugin_class_method_blocks.each do |block|
21
+ self.much_plugin_class_methods_module.class_eval(&block)
22
+ end
23
+ plugin_receiver.send(:extend, self.much_plugin_class_methods_module)
24
+
25
+ self.much_plugin_instance_method_blocks.each do |block|
26
+ self.much_plugin_instance_methods_module.class_eval(&block)
27
+ end
28
+ plugin_receiver.send(:include, self.much_plugin_instance_methods_module)
29
+
30
+ self.much_plugin_after_included_blocks.each do |block|
31
+ plugin_receiver.class_eval(&block)
32
+ end
33
+ end
34
+
35
+ # the included detector is an empty module that is only used to detect if
36
+ # the plugin has been included or not, it doesn't add any behavior or
37
+ # methods to the object receiving the plugin; we use `const_set` to name the
38
+ # module so if its seen in the ancestors it doesn't look like some random
39
+ # module and it can be tracked back to much-plugin
40
+ def much_plugin_included_detector
41
+ @much_plugin_included_detector ||= Module.new.tap do |m|
42
+ self.const_set("MuchPluginIncludedDetector", m)
43
+ end
44
+ end
45
+
46
+ def much_plugin_class_methods_module
47
+ @much_plugin_class_methods_module ||= Module.new.tap do |m|
48
+ self.const_set("MuchPluginClassMethods", m)
49
+ end
50
+ end
51
+
52
+ def much_plugin_instance_methods_module
53
+ @much_plugin_instance_methods_module ||= Module.new.tap do |m|
54
+ self.const_set("MuchPluginInstanceMethods", m)
55
+ end
56
+ end
57
+
58
+ def much_plugin_included_blocks
59
+ @much_plugin_included_blocks ||= []
60
+ end
61
+
62
+ def much_plugin_after_included_blocks
63
+ @much_plugin_after_included_blocks ||= []
64
+ end
65
+
66
+ def much_plugin_class_method_blocks
67
+ @much_plugin_class_method_blocks ||= []
68
+ end
69
+
70
+ def much_plugin_instance_method_blocks
71
+ @much_plugin_instance_method_blocks ||= []
72
+ end
73
+
74
+ def plugin_included(&block)
75
+ self.much_plugin_included_blocks << block
76
+ end
77
+
78
+ def after_plugin_included(&block)
79
+ self.much_plugin_after_included_blocks << block
80
+ end
81
+
82
+ def plugin_class_methods(&block)
83
+ self.much_plugin_class_method_blocks << block
84
+ end
85
+
86
+ def plugin_instance_methods(&block)
87
+ self.much_plugin_instance_method_blocks << block
88
+ end
89
+ end
5
90
  end
@@ -1,3 +1,3 @@
1
1
  module MuchPlugin
2
- VERSION = "0.0.1"
2
+ VERSION = "0.2.2"
3
3
  end
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
8
8
  gem.version = MuchPlugin::VERSION
9
9
  gem.authors = ["Kelly Redding", "Collin Redding"]
10
10
  gem.email = ["kelly@kellyredding.com", "collin.redding@me.com"]
11
- gem.description = %q{Much plugin.}
12
- gem.summary = %q{Much plugin.}
11
+ gem.summary = %q{An API to ensure mixin included logic (the "plugin") only runs once.}
12
+ gem.description = %q{An API to ensure mixin included logic (the "plugin") only runs once.}
13
13
  gem.homepage = "http://github.com/redding/much-plugin"
14
14
  gem.license = 'MIT'
15
15
 
@@ -18,6 +18,6 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
20
 
21
- gem.add_development_dependency("assert", ["~> 2.14"])
21
+ gem.add_development_dependency("assert", ["~> 2.18.1"])
22
22
 
23
23
  end
@@ -9,4 +9,11 @@ require 'pry'
9
9
 
10
10
  require 'test/support/factory'
11
11
 
12
- # TODO: put test helpers here...
12
+ # 1.8.7 backfills
13
+
14
+ # Array#sample
15
+ if !(a = Array.new).respond_to?(:sample) && a.respond_to?(:choice)
16
+ class Array
17
+ alias_method :sample, :choice
18
+ end
19
+ end
@@ -1,4 +1,4 @@
1
- require 'assert/factory'
1
+ require "assert/factory"
2
2
 
3
3
  module Factory
4
4
  extend Assert::Factory
@@ -0,0 +1,54 @@
1
+ require "assert"
2
+ require "much-plugin"
3
+
4
+ module MuchPlugin
5
+ class SystemTests < Assert::Context
6
+ desc "MuchPlugin"
7
+ setup do
8
+ @my_class = MyClass.new
9
+ end
10
+ subject{ @my_class }
11
+
12
+ should "class eval the plugin included block on MyClass" do
13
+ assert_equal "another", subject.another
14
+ end
15
+
16
+ should "add the plugin class methods to MyClass" do
17
+ assert_equal "a-class-method", MyClass.a_class_method
18
+ end
19
+
20
+ should "add the plugin instance methods to MyClass" do
21
+ assert_equal "an-instance-method", subject.an_instance_method
22
+ end
23
+
24
+ module AnotherMixin
25
+ def another
26
+ "another"
27
+ end
28
+ end
29
+
30
+ module MyPlugin
31
+ include MuchPlugin
32
+
33
+ plugin_included do
34
+ include AnotherMixin
35
+ end
36
+
37
+ plugin_class_methods do
38
+ def a_class_method
39
+ "a-class-method"
40
+ end
41
+ end
42
+
43
+ plugin_instance_methods do
44
+ def an_instance_method
45
+ "an-instance-method"
46
+ end
47
+ end
48
+ end
49
+
50
+ class MyClass
51
+ include MyPlugin
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,122 @@
1
+ require "assert"
2
+ require "much-plugin"
3
+
4
+ module MuchPlugin
5
+ class UnitTests < Assert::Context
6
+ desc "MuchPlugin"
7
+ setup do
8
+ @block1 = proc{ 1 }
9
+ @block2 = proc{ 2 }
10
+
11
+ @plugin = Module.new{ include MuchPlugin }
12
+ end
13
+ subject{ @plugin }
14
+
15
+ should have_imeths :much_plugin_included_detector, :much_plugin_included_blocks
16
+ should have_imeths :plugin_included, :after_plugin_included
17
+
18
+ should "know its included detector" do
19
+ mixin = subject.much_plugin_included_detector
20
+ assert_instance_of Module, mixin
21
+ assert_same mixin, subject.much_plugin_included_detector
22
+ exp = subject::MuchPluginIncludedDetector
23
+ assert_same exp, subject.much_plugin_included_detector
24
+ end
25
+
26
+ should "have no plugin included blocks by default" do
27
+ assert_empty subject.much_plugin_included_blocks
28
+ assert_empty subject.much_plugin_after_included_blocks
29
+ end
30
+
31
+ should "append blocks" do
32
+ subject.plugin_included(&@block1)
33
+ subject.plugin_included(&@block2)
34
+
35
+ assert_equal @block1, subject.much_plugin_included_blocks.first
36
+ assert_equal @block2, subject.much_plugin_included_blocks.last
37
+ end
38
+ end
39
+
40
+ class MixedInTests < UnitTests
41
+ desc "when mixed in"
42
+ setup do
43
+ @receiver = Class.new do
44
+ def self.inc_block1; @block1_count ||= 0; @block1_count += 1; end
45
+ def self.block1_count; @block1_count ||= 0; end
46
+ def self.inc_block2; @block2_count ||= 0; @block2_count += 1; end
47
+ def self.block2_count; @block2_count ||= 0; end
48
+
49
+ def self.do_something_count; @do_something_count ||= 0; end
50
+ end
51
+ end
52
+
53
+ should "call the plugin included blocks" do
54
+ assert_equal 0, @receiver.block1_count
55
+ assert_equal 0, @receiver.block2_count
56
+ assert_equal 0, @receiver.do_something_count
57
+
58
+ @receiver.send(:include, TestPlugin)
59
+
60
+ assert_equal 1, @receiver.block1_count
61
+ assert_equal 1, @receiver.block2_count
62
+ assert_equal 1, @receiver.do_something_count
63
+ end
64
+
65
+ should "call blocks only once no matter even if previously mixed in" do
66
+ @receiver.send(:include, TestPlugin)
67
+
68
+ assert_equal 1, @receiver.block1_count
69
+ assert_equal 1, @receiver.block2_count
70
+ assert_equal 1, @receiver.do_something_count
71
+
72
+ @receiver.send(:include, TestPlugin)
73
+
74
+ assert_equal 1, @receiver.block1_count
75
+ assert_equal 1, @receiver.block2_count
76
+ assert_equal 1, @receiver.do_something_count
77
+ end
78
+
79
+ should "call blocks only once even if mixed in by a 3rd party" do
80
+ third_party = Module.new do
81
+ def self.included(receiver)
82
+ receiver.send(:include, TestPlugin)
83
+ end
84
+ end
85
+ @receiver.send(:include, third_party)
86
+
87
+ assert_equal 1, @receiver.block1_count
88
+ assert_equal 1, @receiver.block2_count
89
+ assert_equal 1, @receiver.do_something_count
90
+
91
+ @receiver.send(:include, TestPlugin)
92
+
93
+ assert_equal 1, @receiver.block1_count
94
+ assert_equal 1, @receiver.block2_count
95
+ assert_equal 1, @receiver.do_something_count
96
+
97
+ @receiver.send(:include, third_party)
98
+
99
+ assert_equal 1, @receiver.block1_count
100
+ assert_equal 1, @receiver.block2_count
101
+ assert_equal 1, @receiver.do_something_count
102
+ end
103
+
104
+ TestPlugin =
105
+ Module.new do
106
+ include MuchPlugin
107
+
108
+ plugin_included{ inc_block1 }
109
+ after_plugin_included{
110
+ inc_block2
111
+ do_something
112
+ }
113
+
114
+ plugin_class_methods do
115
+ def do_something
116
+ @do_something_count ||= 0
117
+ @do_something_count += 1
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
metadata CHANGED
@@ -1,94 +1,77 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: much-plugin
3
- version: !ruby/object:Gem::Version
4
- hash: 29
5
- prerelease:
6
- segments:
7
- - 0
8
- - 0
9
- - 1
10
- version: 0.0.1
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
11
5
  platform: ruby
12
- authors:
6
+ authors:
13
7
  - Kelly Redding
14
8
  - Collin Redding
15
9
  autorequire:
16
10
  bindir: bin
17
11
  cert_chain: []
18
-
19
- date: 2015-07-17 00:00:00 Z
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- requirement: &id001 !ruby/object:Gem::Requirement
23
- none: false
24
- requirements:
25
- - - ~>
26
- - !ruby/object:Gem::Version
27
- hash: 31
28
- segments:
29
- - 2
30
- - 14
31
- version: "2.14"
32
- type: :development
12
+ date: 2020-06-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
33
15
  name: assert
34
- version_requirements: *id001
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 2.18.1
21
+ type: :development
35
22
  prerelease: false
36
- description: Much plugin.
37
- email:
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 2.18.1
28
+ description: An API to ensure mixin included logic (the "plugin") only runs once.
29
+ email:
38
30
  - kelly@kellyredding.com
39
31
  - collin.redding@me.com
40
32
  executables: []
41
-
42
33
  extensions: []
43
-
44
34
  extra_rdoc_files: []
45
-
46
- files:
47
- - .gitignore
35
+ files:
36
+ - ".gitignore"
48
37
  - Gemfile
49
- - LICENSE.txt
38
+ - LICENSE
50
39
  - README.md
51
- - Rakefile
40
+ - bench/script.rb
52
41
  - lib/much-plugin.rb
53
42
  - lib/much-plugin/version.rb
54
43
  - log/.gitkeep
55
44
  - much-plugin.gemspec
56
45
  - test/helper.rb
57
46
  - test/support/factory.rb
47
+ - test/system/much-plugin_tests.rb
48
+ - test/unit/much-plugin_tests.rb
58
49
  - tmp/.gitkeep
59
50
  homepage: http://github.com/redding/much-plugin
60
- licenses:
51
+ licenses:
61
52
  - MIT
53
+ metadata: {}
62
54
  post_install_message:
63
55
  rdoc_options: []
64
-
65
- require_paths:
56
+ require_paths:
66
57
  - lib
67
- required_ruby_version: !ruby/object:Gem::Requirement
68
- none: false
69
- requirements:
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
70
60
  - - ">="
71
- - !ruby/object:Gem::Version
72
- hash: 3
73
- segments:
74
- - 0
75
- version: "0"
76
- required_rubygems_version: !ruby/object:Gem::Requirement
77
- none: false
78
- requirements:
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
79
65
  - - ">="
80
- - !ruby/object:Gem::Version
81
- hash: 3
82
- segments:
83
- - 0
84
- version: "0"
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
85
68
  requirements: []
86
-
87
- rubyforge_project:
88
- rubygems_version: 1.8.29
69
+ rubygems_version: 3.1.2
89
70
  signing_key:
90
- specification_version: 3
91
- summary: Much plugin.
92
- test_files:
71
+ specification_version: 4
72
+ summary: An API to ensure mixin included logic (the "plugin") only runs once.
73
+ test_files:
93
74
  - test/helper.rb
94
75
  - test/support/factory.rb
76
+ - test/system/much-plugin_tests.rb
77
+ - test/unit/much-plugin_tests.rb
data/Rakefile DELETED
@@ -1 +0,0 @@
1
- require "bundler/gem_tasks"