decouple 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/decouple.png)](http://badge.fury.io/rb/decouple)
|
3
|
+
[![Build Status](http://travis-ci.org/einzige/decouple.png?branch=master)](https://travis-ci.org/einzige/decouple)
|
4
|
+
[![Dependency Status](https://gemnasium.com/einzige/decouple.png)](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: []
|