much-plugin 0.0.1 → 0.2.2

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.
@@ -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"