much-mixin 0.0.1

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: 28fac94ed0a9f228774a872d2a0300825f5eb92c0e7b382af265936f21abd30b
4
+ data.tar.gz: 42992da0c84c834ac2b864fb08a02fa9dd4c252d143512052cdc9cfbde46e8e4
5
+ SHA512:
6
+ metadata.gz: c2229eca4fda26cb23d960904055554436682e86a7bf2c760042727024f65d84691d721313a67c35226af2032034ae89065cd8a7105294644ddc19e0cc592f7e
7
+ data.tar.gz: c3acfab039cea2a7de5177b46fa4e47603626f0890927960dd59bbb4c38d1292b69188b2d783a52bd4f077a8c14c576e60fc159ae00602ee5c3108f11a6dbae8
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.log
3
+ *.rbc
4
+ .rbx/
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ ruby "~> 2.5"
6
+
7
+ gemspec
8
+
9
+ gem "pry"
10
+
11
+ # to release, uncomment this and run `bundle exec ggem r -f`
12
+ # gem "ggem"
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015-Present Kelly Redding and Collin Redding
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,131 @@
1
+ # MuchMixin
2
+
3
+ Enhanced mix-in API.
4
+
5
+ ## Usage
6
+
7
+ ```ruby
8
+ requre "much-mixin"
9
+
10
+ module MyMuchMixin
11
+ include MuchMixin
12
+
13
+ mixin_included do
14
+ # do some stuff ...
15
+ # - will be class eval'd in the scope of the receiver of `MyMuchMixin`
16
+ # - will only be executed once per receiver, no matter how many times
17
+ # `MyMuchMixin` is included in that receiver
18
+ end
19
+ end
20
+ ```
21
+
22
+ Mix `MuchMixin` in on other mixins that act as "plugins" to other components. Define included hooks using `mixin_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
+ ### `mixin_class_methods` / `mixin_instance_methods`
27
+
28
+ MuchMixin provides convenience methods for defining instance/class methods on plugin receivers:
29
+
30
+ ```ruby
31
+ requre "much-mixin"
32
+
33
+ module MyMuchMixin
34
+ include MuchMixin
35
+
36
+ mixin_class_methods do
37
+ # define some methods ...
38
+ # - these methods will become class methods on the receiver
39
+ end
40
+
41
+ mixin_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_mixin_included`
49
+
50
+ These hooks work just like the `mixin_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-mixin"
54
+
55
+ module MyMuchMixin
56
+ include MuchMixin
57
+
58
+ after_mixin_included do
59
+ configure_the_plugin
60
+ end
61
+
62
+ mixin_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-mixin"
74
+
75
+ module AnotherMixin
76
+ def another
77
+ "another"
78
+ end
79
+ end
80
+
81
+ module MyMuchMixin
82
+ include MuchMixin
83
+
84
+ mixin_included do
85
+ include AnotherMixin
86
+ end
87
+
88
+ mixin_class_methods do
89
+ def a_class_method
90
+ "a-class-method"
91
+ end
92
+ end
93
+
94
+ mixin_instance_methods do
95
+ def an_instance_method
96
+ "an-instance-method"
97
+ end
98
+ end
99
+ end
100
+
101
+ class MyClass
102
+ include MyMuchMixin
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
+ ```
110
+
111
+ ## Installation
112
+
113
+ Add this line to your application's Gemfile:
114
+
115
+ gem "much-mixin"
116
+
117
+ And then execute:
118
+
119
+ $ bundle
120
+
121
+ Or install it yourself as:
122
+
123
+ $ gem install much-mixin
124
+
125
+ ## Contributing
126
+
127
+ 1. Fork it
128
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
129
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
130
+ 4. Push to the branch (`git push origin my-new-feature`)
131
+ 5. Create new Pull Request
@@ -0,0 +1,27 @@
1
+ require "much-mixin"
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 MyMuchMixin
13
+ include MuchMixin
14
+
15
+ mixin_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("MyMuchMixin") do
25
+ 10_000.times{ Class.new{ include MyMuchMixin } }
26
+ end
27
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-mixin/version"
4
+
5
+ module MuchMixin
6
+ def self.included(receiver)
7
+ receiver.class_eval{ extend ClassMethods }
8
+ end
9
+
10
+ module ClassMethods
11
+ # install an included block that first checks if this plugin's receiver mixin
12
+ # has already been included. If it has not been, include the receiver mixin
13
+ # and run all of the `mixin_included` blocks
14
+ def included(plugin_receiver)
15
+ return if plugin_receiver.include?(self.much_mixin_included_detector)
16
+ plugin_receiver.send(:include, self.much_mixin_included_detector)
17
+
18
+ self.much_mixin_included_blocks.each do |block|
19
+ plugin_receiver.class_eval(&block)
20
+ end
21
+
22
+ self.much_plugin_class_method_blocks.each do |block|
23
+ self.much_mixin_class_methods_module.class_eval(&block)
24
+ end
25
+ plugin_receiver.send(:extend, self.much_mixin_class_methods_module)
26
+
27
+ self.much_plugin_instance_method_blocks.each do |block|
28
+ self.much_mixin_instance_methods_module.class_eval(&block)
29
+ end
30
+ plugin_receiver.send(:include, self.much_mixin_instance_methods_module)
31
+
32
+ self.much_plugin_after_included_blocks.each do |block|
33
+ plugin_receiver.class_eval(&block)
34
+ end
35
+ end
36
+
37
+ # the included detector is an empty module that is only used to detect if
38
+ # the plugin has been included or not, it doesn't add any behavior or
39
+ # methods to the object receiving the plugin; we use `const_set` to name the
40
+ # module so if its seen in the ancestors it doesn't look like some random
41
+ # module and it can be tracked back to much-mixin
42
+ def much_mixin_included_detector
43
+ @much_mixin_included_detector ||= Module.new.tap do |m|
44
+ self.const_set("MuchMixinIncludedDetector", m)
45
+ end
46
+ end
47
+
48
+ def much_mixin_class_methods_module
49
+ @much_mixin_class_methods_module ||= Module.new.tap do |m|
50
+ self.const_set("MuchMixinClassMethods", m)
51
+ end
52
+ end
53
+
54
+ def much_mixin_instance_methods_module
55
+ @much_mixin_instance_methods_module ||= Module.new.tap do |m|
56
+ self.const_set("MuchMixinInstanceMethods", m)
57
+ end
58
+ end
59
+
60
+ def much_mixin_included_blocks
61
+ @much_mixin_included_blocks ||= []
62
+ end
63
+
64
+ def much_plugin_after_included_blocks
65
+ @much_plugin_after_included_blocks ||= []
66
+ end
67
+
68
+ def much_plugin_class_method_blocks
69
+ @much_plugin_class_method_blocks ||= []
70
+ end
71
+
72
+ def much_plugin_instance_method_blocks
73
+ @much_plugin_instance_method_blocks ||= []
74
+ end
75
+
76
+ def mixin_included(&block)
77
+ self.much_mixin_included_blocks << block
78
+ end
79
+ alias_method :plugin_included, :mixin_included
80
+
81
+ def after_mixin_included(&block)
82
+ self.much_plugin_after_included_blocks << block
83
+ end
84
+ alias_method :after_plugin_included, :after_mixin_included
85
+
86
+ def mixin_class_methods(&block)
87
+ self.much_plugin_class_method_blocks << block
88
+ end
89
+ alias_method :plugin_class_methods, :mixin_class_methods
90
+
91
+ def mixin_instance_methods(&block)
92
+ self.much_plugin_instance_method_blocks << block
93
+ end
94
+ alias_method :plugin_instance_methods, :mixin_instance_methods
95
+ end
96
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuchMixin
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,3 @@
1
+ require "much-mixin"
2
+
3
+ MuchPlugin = MuchMixin
File without changes
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "much-mixin/version"
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = "much-mixin"
9
+ gem.version = MuchMixin::VERSION
10
+ gem.authors = ["Kelly Redding", "Collin Redding"]
11
+ gem.email = ["kelly@kellyredding.com", "collin.redding@me.com"]
12
+ gem.summary = %q{Enhanced mix-in API.}
13
+ gem.description = %q{Enhanced mix-in API.}
14
+ gem.homepage = "http://github.com/redding/much-mixin"
15
+ gem.license = "MIT"
16
+
17
+ gem.files = `git ls-files`.split($/)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ["lib"]
21
+
22
+ gem.required_ruby_version = "~> 2.5"
23
+
24
+ gem.add_development_dependency("assert", ["~> 2.19.2"])
25
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # this file is automatically required when you run `assert`
4
+ # put any test helpers here
5
+
6
+ # add the root dir to the load path
7
+ $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
8
+
9
+ # require pry for debugging (`binding.pry`)
10
+ require "pry"
11
+
12
+ require "test/support/factory"
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert/factory"
4
+
5
+ module Factory
6
+ extend Assert::Factory
7
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert"
4
+ require "much-mixin"
5
+
6
+ module MuchMixin
7
+ class SystemTests < Assert::Context
8
+ desc "MuchMixin"
9
+ setup do
10
+ @my_class = MyClass.new
11
+ end
12
+ subject{ @my_class }
13
+
14
+ should "class eval the plugin included block on MyClass" do
15
+ assert_equal "another", subject.another
16
+ end
17
+
18
+ should "add the plugin class methods to MyClass" do
19
+ assert_equal "a-class-method", MyClass.a_class_method
20
+ end
21
+
22
+ should "add the plugin instance methods to MyClass" do
23
+ assert_equal "an-instance-method", subject.an_instance_method
24
+ end
25
+
26
+ module AnotherMixin
27
+ def another
28
+ "another"
29
+ end
30
+ end
31
+
32
+ module MyMuchMixin
33
+ include MuchMixin
34
+
35
+ mixin_included do
36
+ include AnotherMixin
37
+ end
38
+
39
+ mixin_class_methods do
40
+ def a_class_method
41
+ "a-class-method"
42
+ end
43
+ end
44
+
45
+ mixin_instance_methods do
46
+ def an_instance_method
47
+ "an-instance-method"
48
+ end
49
+ end
50
+ end
51
+
52
+ class MyClass
53
+ include MyMuchMixin
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert"
4
+ require "much-plugin"
5
+
6
+ module MuchMixin
7
+ class SystemTests < Assert::Context
8
+ desc "MuchMixin"
9
+ setup do
10
+ @my_class = MyClass.new
11
+ end
12
+ subject{ @my_class }
13
+
14
+ should "class eval the plugin included block on MyClass" do
15
+ assert_equal "another", subject.another
16
+ end
17
+
18
+ should "add the plugin class methods to MyClass" do
19
+ assert_equal "a-class-method", MyClass.a_class_method
20
+ end
21
+
22
+ should "add the plugin instance methods to MyClass" do
23
+ assert_equal "an-instance-method", subject.an_instance_method
24
+ end
25
+
26
+ module AnotherMixin
27
+ def another
28
+ "another"
29
+ end
30
+ end
31
+
32
+ module MyMuchMixin
33
+ include MuchMixin
34
+
35
+ mixin_included do
36
+ include AnotherMixin
37
+ end
38
+
39
+ mixin_class_methods do
40
+ def a_class_method
41
+ "a-class-method"
42
+ end
43
+ end
44
+
45
+ mixin_instance_methods do
46
+ def an_instance_method
47
+ "an-instance-method"
48
+ end
49
+ end
50
+ end
51
+
52
+ class MyClass
53
+ include MyMuchMixin
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert"
4
+ require "much-mixin"
5
+
6
+ module MuchMixin
7
+ class UnitTests < Assert::Context
8
+ desc "MuchMixin"
9
+ setup do
10
+ @block1 = proc{ 1 }
11
+ @block2 = proc{ 2 }
12
+
13
+ @muchmixin = Module.new{ include MuchMixin }
14
+ end
15
+ subject{ @muchmixin }
16
+
17
+ should have_imeths :much_mixin_included_detector, :much_mixin_included_blocks
18
+ should have_imeths :mixin_included, :after_mixin_included
19
+
20
+ should "know its included detector" do
21
+ mixin = subject.much_mixin_included_detector
22
+ assert_instance_of Module, mixin
23
+ assert_same mixin, subject.much_mixin_included_detector
24
+ exp = subject::MuchMixinIncludedDetector
25
+ assert_same exp, subject.much_mixin_included_detector
26
+ end
27
+
28
+ should "have no plugin included blocks by default" do
29
+ assert_empty subject.much_mixin_included_blocks
30
+ assert_empty subject.much_plugin_after_included_blocks
31
+ end
32
+
33
+ should "append blocks" do
34
+ subject.mixin_included(&@block1)
35
+ subject.mixin_included(&@block2)
36
+
37
+ assert_equal @block1, subject.much_mixin_included_blocks.first
38
+ assert_equal @block2, subject.much_mixin_included_blocks.last
39
+ end
40
+ end
41
+
42
+ class MixedInTests < UnitTests
43
+ desc "when mixed in"
44
+ setup do
45
+ @receiver = Class.new do
46
+ def self.inc_block1; @block1_count ||= 0; @block1_count += 1; end
47
+ def self.block1_count; @block1_count ||= 0; end
48
+ def self.inc_block2; @block2_count ||= 0; @block2_count += 1; end
49
+ def self.block2_count; @block2_count ||= 0; end
50
+
51
+ def self.do_something_count; @do_something_count ||= 0; end
52
+ end
53
+ end
54
+
55
+ should "call the plugin included blocks" do
56
+ assert_equal 0, @receiver.block1_count
57
+ assert_equal 0, @receiver.block2_count
58
+ assert_equal 0, @receiver.do_something_count
59
+
60
+ @receiver.send(:include, TestMuchMixin)
61
+
62
+ assert_equal 1, @receiver.block1_count
63
+ assert_equal 1, @receiver.block2_count
64
+ assert_equal 1, @receiver.do_something_count
65
+ end
66
+
67
+ should "call blocks only once no matter even if previously mixed in" do
68
+ @receiver.send(:include, TestMuchMixin)
69
+
70
+ assert_equal 1, @receiver.block1_count
71
+ assert_equal 1, @receiver.block2_count
72
+ assert_equal 1, @receiver.do_something_count
73
+
74
+ @receiver.send(:include, TestMuchMixin)
75
+
76
+ assert_equal 1, @receiver.block1_count
77
+ assert_equal 1, @receiver.block2_count
78
+ assert_equal 1, @receiver.do_something_count
79
+ end
80
+
81
+ should "call blocks only once even if mixed in by a 3rd party" do
82
+ third_party = Module.new do
83
+ def self.included(receiver)
84
+ receiver.send(:include, TestMuchMixin)
85
+ end
86
+ end
87
+ @receiver.send(:include, third_party)
88
+
89
+ assert_equal 1, @receiver.block1_count
90
+ assert_equal 1, @receiver.block2_count
91
+ assert_equal 1, @receiver.do_something_count
92
+
93
+ @receiver.send(:include, TestMuchMixin)
94
+
95
+ assert_equal 1, @receiver.block1_count
96
+ assert_equal 1, @receiver.block2_count
97
+ assert_equal 1, @receiver.do_something_count
98
+
99
+ @receiver.send(:include, third_party)
100
+
101
+ assert_equal 1, @receiver.block1_count
102
+ assert_equal 1, @receiver.block2_count
103
+ assert_equal 1, @receiver.do_something_count
104
+ end
105
+ end
106
+
107
+ TestMuchMixin =
108
+ Module.new do
109
+ include MuchMixin
110
+
111
+ mixin_included{ inc_block1 }
112
+ after_mixin_included{
113
+ inc_block2
114
+ do_something
115
+ }
116
+
117
+ mixin_class_methods do
118
+ def do_something
119
+ @do_something_count ||= 0
120
+ @do_something_count += 1
121
+ end
122
+ end
123
+ end
124
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: much-mixin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Kelly Redding
8
+ - Collin Redding
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2021-01-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: assert
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 2.19.2
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 2.19.2
28
+ description: Enhanced mix-in API.
29
+ email:
30
+ - kelly@kellyredding.com
31
+ - collin.redding@me.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".gitignore"
37
+ - Gemfile
38
+ - LICENSE
39
+ - README.md
40
+ - bench/script.rb
41
+ - lib/much-mixin.rb
42
+ - lib/much-mixin/version.rb
43
+ - lib/much-plugin.rb
44
+ - log/.gitkeep
45
+ - much-mixin.gemspec
46
+ - test/helper.rb
47
+ - test/support/factory.rb
48
+ - test/system/much-mixin_tests.rb
49
+ - test/system/much-plugin_tests.rb
50
+ - test/unit/much-mixin_tests.rb
51
+ - tmp/.gitkeep
52
+ homepage: http://github.com/redding/much-mixin
53
+ licenses:
54
+ - MIT
55
+ metadata: {}
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - "~>"
63
+ - !ruby/object:Gem::Version
64
+ version: '2.5'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubygems_version: 3.1.2
72
+ signing_key:
73
+ specification_version: 4
74
+ summary: Enhanced mix-in API.
75
+ test_files:
76
+ - test/helper.rb
77
+ - test/support/factory.rb
78
+ - test/system/much-mixin_tests.rb
79
+ - test/system/much-plugin_tests.rb
80
+ - test/unit/much-mixin_tests.rb