missile_emitter 0.1.1

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
+ SHA256:
3
+ metadata.gz: 5a38fa6f5f01a264e3fc00d72befbe12a29e05aec1acf6ef2a79bfe5d4c0373f
4
+ data.tar.gz: 2e3a55d5fcf2b5d1e2c11d8bebda500e5908ee59bb44d142483e3a31efe288be
5
+ SHA512:
6
+ metadata.gz: dafcb49660a89127302f3f93710155594937aec1aff16a10d183b4aa609a77a8c4356e001cba112c9edd891057b37c76da2f7680854925789e94fc11d1434e58
7
+ data.tar.gz: 1c2bea2657c924bab830ead79cc0b48a7f7155e53f6caa0c389a6c241501a570f50ea209b87688726c9924581d61b0ac1edd231f1d9d1c160c6be2d8f4f81133
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.4
7
+ before_install: gem install bundler -v 2.0.2
data/CHANGELOG.md ADDED
File without changes
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at chen.ruijie@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in missile_emitter.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ missile_emitter (0.1.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.2)
10
+ diff-lcs (1.3)
11
+ method_source (0.9.2)
12
+ pry (0.12.2)
13
+ coderay (~> 1.1.0)
14
+ method_source (~> 0.9.0)
15
+ rake (10.5.0)
16
+ rspec (3.9.0)
17
+ rspec-core (~> 3.9.0)
18
+ rspec-expectations (~> 3.9.0)
19
+ rspec-mocks (~> 3.9.0)
20
+ rspec-core (3.9.0)
21
+ rspec-support (~> 3.9.0)
22
+ rspec-expectations (3.9.0)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.9.0)
25
+ rspec-mocks (3.9.0)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.9.0)
28
+ rspec-support (3.9.0)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bundler (~> 2.0)
35
+ missile_emitter!
36
+ pry
37
+ rake (~> 10.0)
38
+ rspec (~> 3.0)
39
+
40
+ BUNDLED WITH
41
+ 2.0.2
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 coding-class-com
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # MissileEmitter 导弹发射器
2
+
3
+ Ruby元编程小工具,让你能在类定义(class definition)级别触发 method_missing 事件,同时不用担心潜在的命名冲突(此工具来源于一次本地Ruby集会上的主题分享《[小题大做:Ruby元编程探秘](https://pan.baidu.com/s/1hs5hj04)》)。
4
+
5
+ Ruby 提供了 `method_missing` 钩子,利用它可以实现很多功能,但其使用场景也是有限的,基本上常见的用法都是在对象级别来触发(BasicObject的实例 就特别合适)。那有没有什么办法,能让我们在类定义级别来实现相同的目标呢?让我们尝试一下:
6
+
7
+ ```ruby
8
+ class MyClass
9
+ def self.method_missing(message, *args, &block)
10
+ # 做爱做的事情
11
+ end
12
+ end
13
+
14
+ MyClass.ooxx # 触发类级别的 method_missing
15
+ ```
16
+
17
+ 看似可行?可惜并不完美,大家都知道 `MyClass` 其实是 `Class` 类的实例,因此本身还是会带有很多与生俱来的方法:
18
+
19
+ ```ruby
20
+ p MyClass.methods.size # => 111
21
+ ```
22
+
23
+ 其中不乏有 `name`、`trust`这些很常见的名称,导致无法正常触发 `method_missing`,实用性大打折扣。当然,我们可以借助 `undef_method` 来移除不需要的内置方法。以下是 `builder` 的 [BlankSlate](https://github.com/jimweirich/builder/blob/c80100f8205b2e918dbff605682b01ab0fabb866/lib/blankslate.rb#L41) 实现:
24
+
25
+ ```ruby
26
+ class BlankSlate
27
+ # Hide the method named +name+ in the BlankSlate class. Don't
28
+ # hide +instance_eval+ or any method beginning with "__".
29
+ def self.hide(name)
30
+ # ...
31
+ if instance_methods.include?(name._blankslate_as_name) &&
32
+ name !~ /^(__|instance_eval$)/
33
+ # ...
34
+ undef_method name # 利用 undef_method 移除内置方法定义
35
+ end
36
+ # ...
37
+ end
38
+ # ...
39
+ instance_methods.each { |m| hide(m) }
40
+ end
41
+ ```
42
+
43
+ 当然,这个方案也不完美,很多时候我们并不能简单粗暴地把所有类方法都取消定义,特别是 `name` 这样的(返回类的字符串名称),你懂的。那该怎么办呢?嗯,采用 Missile Emitter 可以做到,在类方法级别绕开内置方法的名称冲突,顺利触发 `method_missing` !有了它我们就能实现更多有趣的DSL。
44
+
45
+ ## 安装说明
46
+
47
+ 添加下面这行代码到项目的Gemfile文件:
48
+
49
+ ```ruby
50
+ gem 'missile_emitter'
51
+ ```
52
+
53
+ 然后命令行执行:
54
+
55
+ $ bundle
56
+
57
+ 也可以通过下面的方式直接安装 gem 包:
58
+
59
+ $ gem install missile_emitter
60
+
61
+ ## 使用说明
62
+
63
+ `missile emitter` 需要先定义模块,然后才能在目标类中使用。
64
+
65
+ 1. 定义模块:
66
+
67
+ ```ruby
68
+ module Attributes
69
+ MissileEmitter do |klass, field, value, *, &block|
70
+ klass.define_method(field) { value || instance_eval(&block) }
71
+ end
72
+ end
73
+ ```
74
+
75
+ 2. 扩展目标类(同时声明配置项):
76
+
77
+ ```ruby
78
+ require 'date'
79
+
80
+ class Person
81
+ include Attributes {
82
+ name 'Jerry Chen'
83
+ sex 'male'
84
+ birthday Date.parse('1983-08-08')
85
+ age do
86
+ (Date.today.strftime('%Y%m%d').to_i - birthday.strftime('%Y%m%d').to_i) / 10000
87
+ end
88
+ }
89
+ end
90
+ ```
91
+
92
+ 如此一来,我们就实现了在定义类的同时,配置好需要的属性,测试一下:
93
+
94
+ ```ruby
95
+ me = Person.new
96
+ me.name # => 'Jerry Chen'
97
+ me.sex # => 'male'
98
+ me.age # => 36
99
+ ```
100
+
101
+ 上面的例子确实不太有趣,来个实用点的如何?让我们为 `ActiveRecord` 模型类实现声明式搜索。首先,定义模块:
102
+
103
+ ```ruby
104
+ module Searchable
105
+
106
+ extend ActiveSupport::Concern
107
+
108
+ included do
109
+ # 使用 class attribute 保存搜索条件
110
+ class_attribute :conditions, {}
111
+ end
112
+
113
+ MissileEmitter do |klass, key, *, &block|
114
+ # 保存声明的每一个关键字,以及搜索算法
115
+ klass.conditions[key] = block
116
+ end
117
+
118
+ class_methods do
119
+
120
+ def search(hash)
121
+ hash.reduce all do |relation, (key, value)|
122
+ # Just for fun :)
123
+ relation = relation.extending do
124
+ define_method(:_) { value }
125
+ end
126
+
127
+ if value.blank?
128
+ relation
129
+ elsif filter = mapping[key]
130
+ relation.instance_exec(value, &filter)
131
+ elsif column_names.include?(key)
132
+ relation.where key => value
133
+ else
134
+ relation
135
+ end
136
+ end
137
+ end
138
+
139
+ end
140
+
141
+ end
142
+ ```
143
+
144
+ 然后,Mixin 模块:
145
+
146
+ ```ruby
147
+ class Person < ApplicationRecord
148
+ include Searchable {
149
+ name_like { |keyword| where 'name like ?', "%#{keyword}%" }
150
+ older_than { where 'age >= ?', _ }
151
+ }
152
+ end
153
+ ```
154
+
155
+ 最后,在业务代码中使用:
156
+
157
+ ```ruby
158
+ # params: {name: 'Jerry', older_than: 18}
159
+ Person.search params.slice(:name_like, :older_than, :sex)
160
+ # 参数值值不为空的情况下,等价于:
161
+ Person.where('name like ?', "%#{params[:name_like]}%")
162
+ .where('age >= ?', params[:older_than])
163
+ .where(sex: params[:sex])
164
+ ```
165
+
166
+ 总而言之,使用导弹发射器之后,可以很方便的在类定义级别实现声明式DSL,具体怎么用,就留给你自己慢慢挖掘啦。
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "missile_emitter"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,15 @@
1
+ module MissileEmitter
2
+ class BattleField < BasicObject
3
+
4
+ def initialize(context, callable)
5
+ @context, @handler = context, callable
6
+ end
7
+
8
+ def method_missing(*args, &block)
9
+ @handler.call @context, *args, &block
10
+ end
11
+
12
+ alias_method :emit!, :instance_eval
13
+
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module MissileEmitter
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,47 @@
1
+ require "missile_emitter/version"
2
+ require "missile_emitter/battle_field"
3
+
4
+ require "pry" rescue nil
5
+
6
+ module MissileEmitter
7
+
8
+ class Error < StandardError; end
9
+
10
+ @mapping = {}
11
+
12
+ class << self
13
+ attr_accessor :mapping
14
+
15
+ def exec(&block)
16
+ raise Error.new("需要提供代码块") unless block_given?
17
+
18
+ context = block.binding.eval 'self'
19
+
20
+ raise Error.new("只能再具名模块中调用") unless context.instance_of?(Module) && context.name
21
+
22
+ mimic_method context
23
+
24
+ mapping[context] = block
25
+ end
26
+
27
+ private
28
+
29
+ def mimic_method(context)
30
+ # TODO:处理多层命名空间的情况
31
+ Kernel.define_method context.name do |&missile|
32
+ klass = missile.binding.eval 'self'
33
+ battle_field = BattleField.new klass, MissileEmitter.mapping[context]
34
+ battle_field.emit! &missile
35
+
36
+ context
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ module Kernel
44
+ def MissileEmitter(&block)
45
+ MissileEmitter.exec &block
46
+ end
47
+ end
@@ -0,0 +1,35 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "missile_emitter/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "missile_emitter"
7
+ spec.version = MissileEmitter::VERSION
8
+ spec.authors = ["coding-class.com"]
9
+ spec.email = ["code@coding-class.com"]
10
+
11
+ spec.summary = %q{Ruby元编程辅助工具}
12
+ spec.description = %q{Ruby元编程小工具,让你能在类定义(class definition)级别触发 method_missing 事件,同时不用担心潜在的命名冲突。 }
13
+ spec.homepage = "https://github.com/coding-class-club/missile_emitter"
14
+ spec.license = "MIT"
15
+
16
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "https://github.com/coding-class-club/missile_emitter"
20
+ spec.metadata["changelog_uri"] = "https://github.com/coding-class-club/missile_emitter/blob/master/CHANGELOG.md"
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_development_dependency "bundler", "~> 2.0"
32
+ spec.add_development_dependency "rake", "~> 10.0"
33
+ spec.add_development_dependency "rspec", "~> 3.0"
34
+ spec.add_development_dependency "pry"
35
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: missile_emitter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - coding-class.com
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-11-16 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: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
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: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: 'Ruby元编程小工具,让你能在类定义(class definition)级别触发 method_missing 事件,同时不用担心潜在的命名冲突。 '
70
+ email:
71
+ - code@coding-class.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - ".travis.yml"
79
+ - CHANGELOG.md
80
+ - CODE_OF_CONDUCT.md
81
+ - Gemfile
82
+ - Gemfile.lock
83
+ - LICENSE
84
+ - README.md
85
+ - Rakefile
86
+ - bin/console
87
+ - bin/setup
88
+ - lib/missile_emitter.rb
89
+ - lib/missile_emitter/battle_field.rb
90
+ - lib/missile_emitter/version.rb
91
+ - missile_emitter.gemspec
92
+ homepage: https://github.com/coding-class-club/missile_emitter
93
+ licenses:
94
+ - MIT
95
+ metadata:
96
+ allowed_push_host: https://rubygems.org
97
+ homepage_uri: https://github.com/coding-class-club/missile_emitter
98
+ source_code_uri: https://github.com/coding-class-club/missile_emitter
99
+ changelog_uri: https://github.com/coding-class-club/missile_emitter/blob/master/CHANGELOG.md
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubygems_version: 3.0.6
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Ruby元编程辅助工具
119
+ test_files: []