include_module 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: de37cd68ed47d17ca31835c5e3e413a66f4a9b13
4
+ data.tar.gz: 163712749ec437588906a13f530ff4978c02918a
5
+ SHA512:
6
+ metadata.gz: 29cb5b888d56f91b354662b3e62df476ef39932bced2a5f67a060e2d68a53e8c2d80b87ee324fb966b7e8cf5d761f860853efa57cd0d019d1be2bceaed740ea3
7
+ data.tar.gz: 775acf4c01dcf37fb824412839259cf0cb1cd4c986bbb39daba8af0fd6e4f1b9f58b971e546443f1487f9baae4d8f7f01edaa97855c33d90f4307f823cd82d58
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.10
4
+ - 2.2.5
5
+ - 2.3.1
6
+ env:
7
+ - CI=true
8
+ before_install: gem install bundler
9
+ install: bundle install
10
+ script: make test
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ The following are lists of the notable changes included with each release.
4
+ This is intended to help keep people informed about notable changes between
5
+ versions, as well as provide a rough history. Each item is prefixed with
6
+ one of the following labels: `Added`, `Changed`, `Deprecated`,
7
+ `Removed`, `Fixed`, `Security`. We also use [Semantic
8
+ Versioning](http://semver.org) to manage the versions of this gem so
9
+ that you can set version constraints properly.
10
+
11
+ ### [Unreleased](https://github.com/exAspArk/include_module/compare/9bfa492...HEAD)
12
+
13
+ * `Added`: initial functional version with docs
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in include_module.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'codecov', require: false
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 exAspArk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Makefile ADDED
@@ -0,0 +1,6 @@
1
+ .PHONY: test
2
+
3
+ test:
4
+ bundle exec m test
5
+ release:
6
+ bundle exec rake release
data/README.md ADDED
@@ -0,0 +1,278 @@
1
+ # include_module
2
+
3
+ [![Build Status](https://travis-ci.org/exAspArk/include_module.svg)](https://travis-ci.org/exAspArk/include_module)
4
+ [![codecov](https://codecov.io/gh/exAspArk/include_module/branch/master/graph/badge.svg)](https://codecov.io/gh/exAspArk/include_module)
5
+ [![Code Climate](https://codeclimate.com/github/exAspArk/include_module/badges/gpa.svg)](https://codeclimate.com/github/exAspArk/include_module)
6
+
7
+ There are some general problems with mixins, and a lot of people complain about
8
+ them:
9
+
10
+ * [7 Patterns to Refactor Fat ActiveRecord Models](http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/) (Ruby)
11
+ > Using mixins like this is akin to “cleaning” a messy room by dumping the
12
+ > clutter into six separate junk drawers and slamming them shut. Sure, it looks
13
+ > cleaner at the surface, but the junk drawers actually make it harder to
14
+ > identify and
15
+ > implement the decompositions and extractions necessary to clarify the domain
16
+ > model.
17
+
18
+ * [Mixins considered harmful/1](http://www.artima.com/weblogs/viewpost.jsp?thread=246341) (Python)
19
+ > Injecting methods into a class namespace is a bad idea for a very simple
20
+ > reason: every time you use a mixin, you are actually polluting your class
21
+ > namespace and losing track of the origin of your methods.
22
+
23
+ * [Mixins Considered Harmful](https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html) (JavaScript)
24
+ > * Mixins introduce implicit dependencies
25
+ > * Mixins cause name clashes
26
+ > * Mixins cause snowballing complexity
27
+
28
+ The **include_module** gem was created to help you to use mixing (aka `module` in Ruby)
29
+ explicitly.
30
+ It is a simple library with just 100 LOC, with
31
+ zero dependencies, without any monkey patches and method overridings.
32
+
33
+ ## Usage
34
+
35
+ There are 2 common ways to use mixins in Ruby.
36
+
37
+ 1) [Module.included(base)](http://ruby-doc.org/core-2.3.1/Module.html#method-i-included)
38
+
39
+ ```ruby
40
+ module UserMixin
41
+ def self.included(base)
42
+ base.extend(ClassMethods)
43
+ base.class_eval do
44
+ belongs_to :account
45
+ end
46
+ end
47
+
48
+ module ClassMethods
49
+ def top_user
50
+ User.order(rating: :desc).first
51
+ end
52
+ end
53
+
54
+ def name
55
+ "#{first_name} #{last_name}"
56
+ end
57
+ end
58
+
59
+ class User
60
+ include UserMixin
61
+ end
62
+ ```
63
+
64
+ This is a standard way of using mixins in Ruby. The problem here is that when
65
+ you look at the `User` you have no idea which methods the `UserMixin`
66
+ defines in the class.
67
+
68
+ 2) [ActiveSupport::Concern](http://api.rubyonrails.org/classes/ActiveSupport/Concern.html)
69
+
70
+ ```ruby
71
+ module UserMixin
72
+ extend ActiveSupport::Concern
73
+
74
+ included do
75
+ belongs_to :account
76
+ end
77
+
78
+ class_methods do
79
+ def top_user
80
+ User.order(rating: :desc).first
81
+ end
82
+ end
83
+
84
+ def name
85
+ "#{first_name} #{last_name}"
86
+ end
87
+ end
88
+
89
+ class User
90
+ include UserMixin
91
+ end
92
+ ```
93
+
94
+ By extending your module, you add `.class_methods` which basically hides
95
+ `ClassMethods` module inside, overrides `.append_features` and `.included` methods.
96
+ Additionally it keeps tracking how many times `.included`
97
+ was called to raise a custom exception about multiple "included" blocks.
98
+
99
+ But mostly `ActiveSupport::Concern` has the same problem as standard `Module.included`.
100
+ Why can't we define explicitly which methods we would like to use from a mixin?
101
+
102
+ ### **include_module**
103
+
104
+ ```ruby
105
+ module UserMixin
106
+ INCLUDED = proc do
107
+ belongs_to :account
108
+ end
109
+
110
+ module ClassMethods
111
+ def top_user
112
+ User.order(rating: :desc).first
113
+ end
114
+ end
115
+
116
+ def name
117
+ "#{first_name} #{last_name}"
118
+ end
119
+ end
120
+
121
+ class User
122
+ extend IncludeModule
123
+
124
+ include_module UserMixin, included: true, class_methods: [:top_user], instance_methods: [:name]
125
+ end
126
+ ```
127
+
128
+ Now when you look at the class you can see which methods "included_module" adds in it.
129
+ And it's all done without any monkey patches or method overridings.
130
+
131
+ It is almost in-place replacement for `ActiveSupport::Concern`. Here is a diff:
132
+
133
+ ```ruby
134
+ module UserMixin
135
+ - extend ActiveSupport::Concern
136
+ -
137
+ - included do
138
+ + INCLUDED = proc do
139
+ belongs_to :account
140
+ end
141
+
142
+ ...
143
+
144
+ class User
145
+ + extend IncludeModule
146
+ +
147
+ - include_module UserMixin
148
+ + include_module UserMixin, included: true, class_methods: [:top_user], instance_methods: [:name]
149
+ end
150
+ ```
151
+
152
+ You can also control which methods you would like to include, rename them
153
+ while including in order to avoid name clashes, etc. Please see more examples below.
154
+
155
+ ## How Does It Work?
156
+
157
+ * Include no methods
158
+
159
+ When you add `extend IncludeModule`, you add just one public method called `include_module` in
160
+ your class.
161
+
162
+ ```ruby
163
+ class User
164
+ extend IncludeModule
165
+ include_module UserMixin
166
+ end
167
+ ```
168
+
169
+ By default when you call `include_module` without any options, it won't load anything.
170
+
171
+ * Include all methods
172
+
173
+ If you want to include everything from your module, you can pass the following options:
174
+
175
+ ```ruby
176
+ class User
177
+ extend IncludeModule
178
+ include_module UserMixin, included: true, class_methods: :all, instance_methods: :all
179
+ end
180
+ ```
181
+
182
+ It will `include` you module, `extend` your `ClassMethods` and `class_eval` your
183
+ `INCLUDED` proc without any changes.
184
+
185
+ * Include some methods
186
+
187
+ You can define explicitly which methods you want to include from you module:
188
+
189
+ ```ruby
190
+ module TestModule
191
+ def foo
192
+ :foo
193
+ end
194
+
195
+ def bar
196
+ :bar
197
+ end
198
+ end
199
+
200
+ class TestClass
201
+ extend IncludeModule
202
+ include_module TestModule, instance_methods: [:foo]
203
+ end
204
+ ```
205
+
206
+ In this case we have only `TestClass#foo` method implemented.
207
+ It basically creates a new anonymous module which contains only specified methods and includes it as usual.
208
+
209
+ * Rename included method
210
+
211
+ ```ruby
212
+ class User
213
+ extend IncludeModule
214
+ include_module UserMixin, instance_methods: [name: :full_name]
215
+ end
216
+ ```
217
+
218
+ We can rename methods while including a mixin by creating a new anonymous module as in the previous example.
219
+ So `User#full_name` method will be available instead of `User#name`.
220
+
221
+ ## Installation
222
+
223
+ Install Ruby version >= 2.1.
224
+
225
+ Add this line to your application's Gemfile:
226
+
227
+ ```ruby
228
+ gem 'include_module'
229
+ ```
230
+
231
+ And then execute:
232
+
233
+ $ bundle
234
+
235
+ Or install it yourself as:
236
+
237
+ $ gem install include_module
238
+
239
+ ## Development
240
+
241
+ Install development dependencies:
242
+
243
+ gem install bundler
244
+ bundle install
245
+
246
+ Run tests:
247
+
248
+ make test
249
+
250
+ To release a new version, update the version number in `include_module.gemspec`,
251
+ and then run:
252
+
253
+ make release
254
+
255
+ Which will create a git tag for the version, push git commits and tags, and push
256
+ the `.gem` file to [rubygems.org](https://rubygems.org).
257
+
258
+ ## What's New?
259
+
260
+ Please read the [changelog](./CHANGELOG.md).
261
+
262
+ ## Contributing
263
+
264
+ Bug reports and pull requests are welcome on GitHub at
265
+ https://github.com/exAspArk/include_module.
266
+
267
+ To open a pull request:
268
+
269
+ 1. [Fork it](https://github.com/exAspArk/include_module/fork)
270
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
271
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
272
+ 4. Push to the branch (`git push origin my-new-feature`)
273
+ 5. Create [a new Pull
274
+ Request](https://github.com/exAspArk/include_module/compare)
275
+
276
+ ## License
277
+
278
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "include_module"
7
+ spec.version = "0.1.0"
8
+ spec.authors = ["exAspArk"]
9
+ spec.email = ["exaspark@gmail.com"]
10
+
11
+ spec.summary = %q{Include your modules explicitly}
12
+ spec.description = %q{Include your modules explicitly}
13
+ spec.homepage = "https://github.com/exAspArk/include_module"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.required_ruby_version = '~> 2.1'
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.10"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "minitest", "~> 5.9"
26
+ spec.add_development_dependency "pry-byebug", "~> 3.4"
27
+ spec.add_development_dependency "m", "~> 1.5"
28
+ end
@@ -0,0 +1,99 @@
1
+ module IncludeModule
2
+ def include_module(new_module, instance_methods: [], class_methods: [], included: false)
3
+ if is_a?(Class)
4
+ __include_class_methods_in_class(new_module: new_module, method_names: class_methods)
5
+ __include_included_blocks_in_class(new_module: new_module, included: included)
6
+ else
7
+ __include_class_methods_in_module(new_module: new_module, method_names: class_methods)
8
+ __store_included_block_in_module(new_module: new_module, included: included)
9
+ end
10
+
11
+ __include_instance_methods(new_module: new_module, method_names: instance_methods)
12
+ end
13
+
14
+ private
15
+
16
+ def __include_class_methods_in_class(new_module:, method_names:)
17
+ return if method_names.empty?
18
+ class_methods_module = __class_methods_module(new_module)
19
+
20
+ if method_names == :all
21
+ extend class_methods_module
22
+ else
23
+ extend __map_module_instance_methods!(new_module: class_methods_module.dup, method_names: method_names)
24
+ end
25
+ end
26
+
27
+ def __include_class_methods_in_module(new_module:, method_names:)
28
+ return if method_names.empty?
29
+
30
+ __include_instance_methods(
31
+ new_module: __class_methods_module(new_module),
32
+ method_names: method_names,
33
+ include_in: __class_methods_module(self)
34
+ )
35
+ end
36
+
37
+ def __include_included_blocks_in_class(new_module:, included:)
38
+ return unless included
39
+
40
+ included_blocks = new_module.instance_variable_get(:@__included_blocks) || []
41
+ included_blocks << new_module::INCLUDED if new_module.const_defined?(:INCLUDED)
42
+ included_blocks.each { |included_block| class_eval(&included_block) }
43
+ end
44
+
45
+ def __store_included_block_in_module(new_module:, included:)
46
+ return unless included
47
+
48
+ @__included_blocks = new_module.instance_variable_get(:@__included_blocks) || []
49
+ @__included_blocks << new_module::INCLUDED if new_module.const_defined?(:INCLUDED)
50
+ end
51
+
52
+ def __include_instance_methods(new_module:, method_names:, include_in: self)
53
+ if method_names == :all
54
+ include_in.include(new_module)
55
+ else
56
+ new_mapped_module = __map_module_instance_methods!(new_module: new_module.dup, method_names: method_names)
57
+ include_in.include(new_mapped_module)
58
+ end
59
+ end
60
+
61
+ def __map_module_instance_methods!(new_module:, method_names:)
62
+ new_method_name_by_original_method_name = __new_method_name_by_original_method_name(method_names)
63
+
64
+ new_module.instance_methods.each do |original_method_name|
65
+ new_method_name = new_method_name_by_original_method_name[original_method_name]
66
+
67
+ case new_method_name
68
+ when original_method_name # leave it as is
69
+ when nil # remove
70
+ new_module.class_eval { remove_method(original_method_name) }
71
+ else # rename
72
+ new_module.class_eval do
73
+ alias_method(new_method_name, original_method_name)
74
+ remove_method(original_method_name)
75
+ end
76
+ end
77
+ end
78
+
79
+ new_module
80
+ end
81
+
82
+ def __new_method_name_by_original_method_name(method_names)
83
+ method_names.each_with_object({}) do |method_name, result|
84
+ if method_name.is_a?(Hash)
85
+ result.merge!(method_name)
86
+ else
87
+ result[method_name] = method_name
88
+ end
89
+ end
90
+ end
91
+
92
+ def __class_methods_module(new_module)
93
+ if new_module.const_defined?(:ClassMethods)
94
+ new_module.const_get(:ClassMethods)
95
+ else
96
+ new_module.const_set(:ClassMethods, Module.new)
97
+ end
98
+ end
99
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: include_module
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - exAspArk
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-08-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.9'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: m
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.5'
83
+ description: Include your modules explicitly
84
+ email:
85
+ - exaspark@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".travis.yml"
92
+ - CHANGELOG.md
93
+ - CODE_OF_CONDUCT.md
94
+ - Gemfile
95
+ - LICENSE.txt
96
+ - Makefile
97
+ - README.md
98
+ - Rakefile
99
+ - include_module.gemspec
100
+ - lib/include_module.rb
101
+ homepage: https://github.com/exAspArk/include_module
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - "~>"
112
+ - !ruby/object:Gem::Version
113
+ version: '2.1'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.4.5.1
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: Include your modules explicitly
125
+ test_files: []