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 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
@@ -0,0 +1,9 @@
1
+ *.gem
2
+ .yardoc
3
+ rdoc
4
+ coverage
5
+ .bundle
6
+ .ruby-version
7
+ .rvmrc
8
+ .idea
9
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.0.0"
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
3
+ gem 'rake'
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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new :spec
5
+ task :default => :spec
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
@@ -0,0 +1,7 @@
1
+ require 'decouple'
2
+
3
+ RSpec.configure do |config|
4
+ config.mock_with :rspec
5
+ config.color_enabled = true
6
+ config.formatter = :documentation
7
+ end
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: []