decouple 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +3 -0
- data/Gemfile +3 -0
- data/README.md +51 -0
- data/Rakefile +5 -0
- data/decouple.gemspec +23 -0
- data/lib/decouple.rb +40 -0
- data/lib/decouple/decoupler.rb +53 -0
- data/spec/lib/decouple/decoupler_spec.rb +92 -0
- data/spec/lib/decouple_spec.rb +66 -0
- data/spec/spec_helper.rb +7 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 26a5008be5d896e8dbd276ff7d37616b8195460d
|
4
|
+
data.tar.gz: e2d79c11d93de57ff509af2c4ab1a0881e53cdeb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d894c8eba20e0904d6b02927b35f8b34b781064159819645e67c9c47804850fd074b83e1b89199e654196c52cdb9a4cab0e46aea704550e5bc395806eefb49ad
|
7
|
+
data.tar.gz: f206104c9626789f754e13a70581da3702fdabc77d4acb514b72bd60e4e46113e77b06405576e2a8e9a292c675c240ecaafe900d92458594bb07284d8b1be8de
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Decouple FTW!
|
2
|
+
[](http://badge.fury.io/rb/decouple)
|
3
|
+
[](https://travis-ci.org/einzige/decouple)
|
4
|
+
[](https://gemnasium.com/einzige/decouple)
|
5
|
+
|
6
|
+
Decouples long methods in a pretty weird unnatural way. Please use _PRIVATE_ methods instead, extract classes, objects, do whatever is possible not to use it. That's a really bad idea, NEVER use it. I DO ALWAYS USE NATURAL LANGUAGE CONSTRUCTIONS.
|
7
|
+
|
8
|
+
## Anyways... :)
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
class MyClass
|
12
|
+
include Decouple
|
13
|
+
|
14
|
+
def send_email
|
15
|
+
# 100 lines of code
|
16
|
+
proceed_action
|
17
|
+
end
|
18
|
+
|
19
|
+
def send_letter
|
20
|
+
# ...
|
21
|
+
# Another thougsand lines code
|
22
|
+
# ...
|
23
|
+
proceed_action :to_grandma
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def burn_paper
|
29
|
+
# bazillion lines of code
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# app/callbacks/safe_actions.rb
|
34
|
+
MyClass.decouple do
|
35
|
+
on :send_email do
|
36
|
+
Mailer.notify
|
37
|
+
end
|
38
|
+
|
39
|
+
on :send_letter do |receiver|
|
40
|
+
Mailer.notify(receipient: receiver)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# app/callbacks/burn_paper.rb
|
45
|
+
MyClass.decouple do
|
46
|
+
on :send_email do
|
47
|
+
burn_paper
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
data/Rakefile
ADDED
data/decouple.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{decouple}
|
5
|
+
s.version = "0.0.1"
|
6
|
+
|
7
|
+
s.date = %q{2014-03-10}
|
8
|
+
s.authors = ["Sergei Zinin (einzige)"]
|
9
|
+
s.email = %q{szinin@gmail.com}
|
10
|
+
s.homepage = %q{http://github.com/einzige/decouple}
|
11
|
+
|
12
|
+
s.licenses = ["MIT"]
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
s.extra_rdoc_files = ["README.md"]
|
17
|
+
|
18
|
+
s.description = %q{Decouples long methods in a pretty weird unnatural way. Please use PRIVATE methods instead. That's a really bad idea, NEVER use it.}
|
19
|
+
s.summary = %q{Organizes your code by decoupling long methods into separate declarations}
|
20
|
+
|
21
|
+
s.add_development_dependency 'rspec'
|
22
|
+
end
|
23
|
+
|
data/lib/decouple.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'decouple/decoupler'
|
2
|
+
|
3
|
+
module Decouple
|
4
|
+
VERSION = '0.0.1'
|
5
|
+
|
6
|
+
# @param base [Class]
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(Decouple::ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Runs callbacks for calling context (decoupled method is a context)
|
12
|
+
# @param arguments [Array]
|
13
|
+
def proceed_action(*arguments)
|
14
|
+
self.class.decouplings.each { |decoupler| decoupler.run(self, *arguments) }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Runs callbacks for a specific method
|
18
|
+
# @param action [String] Method name to proceed with
|
19
|
+
# @param arguments [Array]
|
20
|
+
def proceed_with(action, *arguments)
|
21
|
+
self.class.decouplings.each do |decoupler|
|
22
|
+
decoupler.run_on(self, action, *arguments)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
|
28
|
+
# Attaches callbacks
|
29
|
+
# @yield
|
30
|
+
def decouple(&block)
|
31
|
+
decouplings << Decouple::Decoupler.new(self, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns a list of attached callback containers
|
35
|
+
# @return [Array<Decouple::Decoupler>]
|
36
|
+
def decouplings
|
37
|
+
@decouplings ||= []
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Decouple
|
2
|
+
class Decoupler < Hash
|
3
|
+
|
4
|
+
# @param klass [Class] A class to decouple
|
5
|
+
# @yield Decoupling block
|
6
|
+
def initialize(klass, &block)
|
7
|
+
@klass = klass
|
8
|
+
instance_eval(&block) if block
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param action [Symbol] Name of the method being decoupled
|
12
|
+
# @yield Method body extension
|
13
|
+
def on(action, &block)
|
14
|
+
if has_key?(action)
|
15
|
+
raise ArgumentError, "#{action} action is already callbacked"
|
16
|
+
end
|
17
|
+
|
18
|
+
unless @klass.instance_methods.include?(action)
|
19
|
+
raise ArgumentError, "No such action #{action}"
|
20
|
+
end
|
21
|
+
|
22
|
+
self[action] = block
|
23
|
+
end
|
24
|
+
|
25
|
+
# Runs callbacks (decouplings) attached to decoupling methods
|
26
|
+
# @param klass_instance [Object] An object where the decoupling will be called
|
27
|
+
# @param arguments [Array]
|
28
|
+
def run(klass_instance, *arguments)
|
29
|
+
unless klass_instance.is_a?(@klass)
|
30
|
+
raise ArgumentError, "Running #{klass_instance.class.name} on #{@klass.name} Decoupler"
|
31
|
+
end
|
32
|
+
|
33
|
+
action = nil
|
34
|
+
depth = 0 # Can be optimized by seeting to 3
|
35
|
+
|
36
|
+
while action.nil?
|
37
|
+
location = caller_locations(depth+=1, 1)[0] or raise "No action to proceed (invalid context) [#{depth}]"
|
38
|
+
location = location.label.to_sym
|
39
|
+
action = location if self[location]
|
40
|
+
end
|
41
|
+
|
42
|
+
run_on(klass_instance, action, *arguments)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Runs callback attached to method
|
46
|
+
# @param klass_instance [Object] An object where the decoupling will be called
|
47
|
+
# @param action [Symbol, String] Name of decoupled method
|
48
|
+
def run_on(klass_instance, action, *arguments)
|
49
|
+
block = self[action.to_sym] or raise ArgumentError, "No callback for #{action}"
|
50
|
+
klass_instance.instance_exec(*arguments, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Decouple::Decoupler do
|
4
|
+
subject(:decoupler) { Decouple::Decoupler.new(klass) }
|
5
|
+
|
6
|
+
let(:klass) do
|
7
|
+
Class.new do
|
8
|
+
def action
|
9
|
+
proceed_action :argument
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def perform_job(param)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#run' do
|
20
|
+
context 'calling in a correct context' do
|
21
|
+
let(:klass) do
|
22
|
+
Class.new do
|
23
|
+
def action
|
24
|
+
decoupler = Decouple::Decoupler.new(self.class) do
|
25
|
+
on(:action) { |param| perform_job(param) }
|
26
|
+
end
|
27
|
+
decoupler.run(self, :argument)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def perform_job(param)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:klass_instance) { klass.new }
|
38
|
+
|
39
|
+
specify do
|
40
|
+
expect(klass_instance).to receive(:perform_job).with(:argument)
|
41
|
+
klass_instance.action
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'context is invalid' do
|
46
|
+
let(:random_context) { 'random_context' }
|
47
|
+
|
48
|
+
specify do
|
49
|
+
expect(random_context).not_to receive(:perform_job)
|
50
|
+
expect { decoupler.run(random_context) }.to raise_error(ArgumentError, /Running String/)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'calling out of context' do
|
55
|
+
before do
|
56
|
+
decoupler.on :action do
|
57
|
+
perform_job(:param)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
let(:klass_instance) { klass.new }
|
62
|
+
|
63
|
+
specify do
|
64
|
+
expect(klass_instance).not_to receive(:perform_job)
|
65
|
+
expect { decoupler.run(klass_instance) }.to raise_error /No action to proceed/
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#run_on' do
|
71
|
+
context 'registered action' do
|
72
|
+
before do
|
73
|
+
decoupler.on :action do
|
74
|
+
perform_job(:param)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
let(:klass_instance) { klass.new }
|
79
|
+
|
80
|
+
specify do
|
81
|
+
expect(klass_instance).to receive(:perform_job).with(:param)
|
82
|
+
decoupler.run_on(klass_instance, :action, :param)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'unregistered action' do
|
87
|
+
specify do
|
88
|
+
expect { decoupler.run_on(:anything, :unregistered_action) }.to raise_error ArgumentError, /No callback/
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Decouple do
|
4
|
+
let(:klass) do
|
5
|
+
Class.new do
|
6
|
+
include Decouple
|
7
|
+
|
8
|
+
def action
|
9
|
+
proceed_action :param, :another_param
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def perform_job(param)
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform_another_job(another_param)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
before do
|
23
|
+
klass.decouple do
|
24
|
+
on :action do |param, another_param|
|
25
|
+
perform_job param
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
before do
|
31
|
+
klass.decouple do
|
32
|
+
on :action do |param, another_param|
|
33
|
+
perform_another_job another_param
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:klass_instance) { klass.new }
|
39
|
+
|
40
|
+
it 'calls all registered callbacks on method call' do
|
41
|
+
expect(klass_instance).to receive(:perform_job).with(:param)
|
42
|
+
expect(klass_instance).to receive(:perform_another_job).with(:another_param)
|
43
|
+
klass_instance.action
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'registering the same callback twice' do
|
47
|
+
specify do
|
48
|
+
expect {
|
49
|
+
klass.decouple do
|
50
|
+
on(:action) {}
|
51
|
+
on(:action) {}
|
52
|
+
end
|
53
|
+
}.to raise_error(ArgumentError, /already callbacked/)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'registering callback on unexisting method' do
|
58
|
+
specify do
|
59
|
+
expect {
|
60
|
+
klass.decouple do
|
61
|
+
on(:unexisting_action) {}
|
62
|
+
end
|
63
|
+
}.to raise_error(ArgumentError, /No such action/)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: decouple
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sergei Zinin (einzige)
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Decouples long methods in a pretty weird unnatural way. Please use PRIVATE
|
28
|
+
methods instead. That's a really bad idea, NEVER use it.
|
29
|
+
email: szinin@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files:
|
33
|
+
- README.md
|
34
|
+
files:
|
35
|
+
- .gitignore
|
36
|
+
- .travis.yml
|
37
|
+
- Gemfile
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- decouple.gemspec
|
41
|
+
- lib/decouple.rb
|
42
|
+
- lib/decouple/decoupler.rb
|
43
|
+
- spec/lib/decouple/decoupler_spec.rb
|
44
|
+
- spec/lib/decouple_spec.rb
|
45
|
+
- spec/spec_helper.rb
|
46
|
+
homepage: http://github.com/einzige/decouple
|
47
|
+
licenses:
|
48
|
+
- MIT
|
49
|
+
metadata: {}
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 2.1.5
|
67
|
+
signing_key:
|
68
|
+
specification_version: 4
|
69
|
+
summary: Organizes your code by decoupling long methods into separate declarations
|
70
|
+
test_files: []
|