method_decorator 3.0.2 → 4.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9db54a7da140b414c3e9ab1d1c922b1f9fadf9c0
4
- data.tar.gz: 61af7d43a268354cb151b6c461bd41467b352b21
3
+ metadata.gz: 24035e46f4f4e95da3c06a525d08c07cb0652541
4
+ data.tar.gz: 7671f1cfe76a192254cf902a8373e7a33fc6619f
5
5
  SHA512:
6
- metadata.gz: 2635c19dda2211e44cfbb1b3920af969e68e36d2c31b5091fe119aaefd37453e67b9b2d28a6abc848638b76400a117c737b8fbaadd1b6dd4685f268aee0be420
7
- data.tar.gz: 519499c0731fffb1fdfd2fdd2f03d70ebcd0e917c9544ad1b4a7c9a4fbd524f9169f3038e4f32f89077547c26b3e810265fcf8a4de847b8b718de670cfb676b4
6
+ metadata.gz: ef0319cef3a7d8645a849b744b44bbb36cbd61bafdc343aa87a7a39c2e2674f6ae7d115f0dd8b7610389cead5e23ee0547681e3ee5498d74839232da3dc7ba0b
7
+ data.tar.gz: ebed4eae693f0f1a52c9d5dcd7f765735bf1e42b044fbb0a67378b191248a39cf81f0b119a7d74a4b133a335b37a48cd40b2010ec8105f157cfbf046b3d59f07
data/.rubocop.yml ADDED
@@ -0,0 +1,24 @@
1
+ Documentation:
2
+ Enabled: false
3
+
4
+ Style/PredicateName:
5
+ Enabled: false
6
+
7
+ Metrics/LineLength:
8
+ Max: 99
9
+
10
+ Metrics/MethodLength:
11
+ Max: 20
12
+
13
+ Metrics/BlockLength:
14
+ ExcludedMethods: ['describe', 'context']
15
+
16
+ AllCops:
17
+ Exclude:
18
+ - 'vendor/**/*'
19
+ - 'spec/fixtures/**/*'
20
+ - 'db/**/*'
21
+ - 'config/**/*'
22
+ - 'script/**/*'
23
+ - !ruby/regexp /old_and_unused\.rb$/
24
+ TargetRubyVersion: 2.4
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.3.0@method_decorator
1
+ ruby-2.4.0@method_decorator
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.0
5
+ before_install: gem install bundler -v 1.15.1
6
+ addons:
7
+ code_climate:
8
+ repo_token: d4cbcb425982b9f55ea7987bbe97c332550f433610298384819d5dcabed6c334
9
+ after_success:
10
+ - bundle exec codeclimate-test-reporter
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
- gemspec
5
+ gemspec
data/README.rdoc CHANGED
@@ -1,4 +1,8 @@
1
1
  = Method Decorator
2
+ {<img src="https://travis-ci.org/r4z3c/method_decorator.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/r4z3c/method_decorator]
3
+ {<img src="https://codeclimate.com/github/r4z3c/method_decorator/badges/gpa.svg" alt="Code Climate" />}[https://codeclimate.com/github/r4z3c/method_decorator]
4
+ {<img src="https://codeclimate.com/github/r4z3c/method_decorator/badges/coverage.svg" alt="Code Coverage" />}[https://codeclimate.com/github/r4z3c/method_decorator/coverage]
5
+ {<img src="https://codeclimate.com/github/r4z3c/method_decorator/badges/issue_count.svg" alt="Issue Count" />}[https://codeclimate.com/github/r4z3c/method_decorator]
2
6
 
3
7
  Provides a way to dynamically override methods without losing original behavior.
4
8
 
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Rakefile for rake -*- ruby -*-
2
4
 
3
5
  # Copyright 2003, 2004, 2005 by Jim Weirich (jim@weirichhouse.org)
@@ -11,7 +13,7 @@ require 'rake/testtask'
11
13
  require 'rdoc/task'
12
14
 
13
15
  Rake::TestTask.new(:test) do |t|
14
- t.libs << "test"
16
+ t.libs << 'test'
15
17
  t.verbose = true
16
18
  t.test_files = FileList['test/**/test_*.rb']
17
19
  end
@@ -27,7 +29,7 @@ end
27
29
  require 'rspec/core/rake_task'
28
30
 
29
31
  RSpec::Core::RakeTask.new(:spec) do |t|
30
- t.rspec_opts = %w(--color)
32
+ t.rspec_opts = %w[--color]
31
33
  end
32
34
 
33
- task :default => :spec
35
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'method_decorator'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -1,84 +1,44 @@
1
- require 'method_decorator/repository'
2
- require 'active_support/concern'
3
-
4
- module MethodDecorator extend ActiveSupport::Concern
5
-
6
- included do
1
+ # frozen_string_literal: true
7
2
 
8
- class << self
3
+ require 'active_support/concern'
4
+ require 'method_decorator/services/decorations_service'
9
5
 
10
- def decorate(target_method_name, &decoration)
11
- MethodDecorator.decorate self, target_method_name, &decoration
12
- end
6
+ module MethodDecorator
7
+ extend ActiveSupport::Concern
13
8
 
9
+ class_methods do
10
+ def decorate(method_name, &decoration)
11
+ MethodDecorator.decorate self, method_name, &decoration
14
12
  end
15
-
16
13
  end
17
14
 
18
15
  class << self
19
-
20
- def decorate(target_class, target_method_name, &decoration)
21
- raise 'target_method_name must be a symbol' unless target_method_name.is_a? Symbol
22
- added = add_to_repository(decoration, target_class, target_method_name)
23
- override_method(decoration, target_class, target_method_name) if added
24
- added
16
+ def decorate(klass, method_name, &decoration)
17
+ decorations_service.decorate(klass, method_name, &decoration)
25
18
  end
26
19
 
27
- protected
28
-
29
- def add_to_repository(decoration, target_class, target_method_name)
30
- Repository.add(
31
- target_class,
32
- target_method_name,
33
- target_class.instance_method(target_method_name),
34
- &decoration
35
- )
20
+ def add_call(klass, method_name, *args, &block)
21
+ decorations_service.add_call(klass, method_name, *args, &block)
36
22
  end
37
23
 
38
- def override_method(decoration, target_class, target_method_name)
39
- is_protected = target_class.protected_instance_methods.include? target_method_name
40
- is_private = target_class.private_instance_methods.include? target_method_name
41
- define_decorated_method decoration, target_class, target_method_name
42
- target_class.instance_eval { protected target_method_name } if is_protected
43
- target_class.instance_eval { private target_method_name } if is_private
24
+ def call_original_method(context, method_name, *args, &block)
25
+ decorations_service.call_original_method(context, method_name, *args, &block)
44
26
  end
45
27
 
46
- def define_decorated_method(decoration, target_class, target_method_name)
47
- target_class.send :define_method, target_method_name do |*args, &block|
48
- target_class.send :define_method, :call_args do return *args end
49
- target_class.send :define_method, :call_block do return block end
50
- target_class.send :define_method, :call_original do
51
- MethodDecorator.call_original_method self, target_method_name, *call_args, &call_block
52
- end
53
- target_class.send :define_method, :call_original_with do |*desired_args, &desired_block|
54
- MethodDecorator.call_original_method self, target_method_name, *desired_args, &desired_block
55
- end
56
- target_class.instance_eval { protected :call_args }
57
- target_class.instance_eval { protected :call_block }
58
- instance_eval &decoration
59
- end
28
+ def last_call_args(context)
29
+ decorations_service.last_call_args(context)
60
30
  end
61
31
 
62
- public
63
-
64
- def call_original_method(target_context, target_method_name, *original_args, &original_block)
65
- target_class = target_class_from_context(target_context)
66
- target_method = Repository.original_target_method_of(target_class, target_method_name)
67
- target_method.bind(target_context).call(*original_args, &original_block)
32
+ def last_call_block(context)
33
+ decorations_service.last_call_block(context)
68
34
  end
69
35
 
70
- def target_class_from_context(target_context)
71
- if target_type_from_context(target_context).eql? :singleton
72
- target_context.singleton_class
73
- else
74
- target_context.class
75
- end
36
+ def last_call_method_name(context)
37
+ decorations_service.last_call_method_name(context)
76
38
  end
77
39
 
78
- def target_type_from_context(target_context)
79
- target_context.class.eql?(Class) ? :singleton : :class
40
+ def decorations_service
41
+ MethodDecorator::Services::DecorationsService
80
42
  end
81
-
82
43
  end
83
-
84
- end
44
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MethodDecorator
4
+ module Errors
5
+ class AlreadyDecoratedError < StandardError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'method_decorator/models/model'
4
+
5
+ module MethodDecorator
6
+ module Models
7
+ class Call < Model
8
+ attr_accessor :klass, :method_name, :args, :block
9
+
10
+ def initialize(klass, method_name, *args, &block)
11
+ self.klass = klass
12
+ self.method_name = method_name
13
+ self.args = *args
14
+ self.block = block
15
+ validate!
16
+ end
17
+
18
+ protected
19
+
20
+ def validate!
21
+ validate_attr!(:klass, Class)
22
+ validate_attr!(:method_name, Symbol)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'method_decorator/models/model'
4
+
5
+ module MethodDecorator
6
+ module Models
7
+ class Decoration < Model
8
+ attr_accessor :klass, :method_name, :method_instance, :decoration
9
+
10
+ def initialize(klass, method_name, method_instance)
11
+ self.klass = klass
12
+ self.method_name = method_name
13
+ self.method_instance = method_instance
14
+ self.decoration = block_given? ? Proc.new : nil
15
+ validate!
16
+ end
17
+
18
+ protected
19
+
20
+ def validate!
21
+ validate_attr!(:klass, Class)
22
+ validate_attr!(:method_name, Symbol)
23
+ validate_attr!(:method_instance, UnboundMethod)
24
+ validate_attr!(:decoration, Proc)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MethodDecorator
4
+ module Models
5
+ class Model
6
+ protected
7
+
8
+ def validate!
9
+ raise(NotImplementedError, "`#{self.class}` must override `validate!`")
10
+ end
11
+
12
+ def validate_attr!(attr, type)
13
+ value = send(attr)
14
+ invalid_attr!(attr) unless value
15
+ invalid_attr!(attr, "must be a `#{type}`") unless value.is_a?(type)
16
+ end
17
+
18
+ def invalid_attr!(attr, error = 'is required')
19
+ raise(ArgumentError, "`#{attr}` attribute #{error}")
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'method_decorator/models/call'
4
+
5
+ module MethodDecorator
6
+ module Repositories
7
+ class CallsRepository
8
+ attr_writer :calls
9
+
10
+ def add(klass, method_name, *args, &block)
11
+ call = find_call(klass)
12
+ if call
13
+ update_existing_call(call, method_name, *args, &block)
14
+ else
15
+ push_new_call(klass, method_name, *args, &block)
16
+ end
17
+ end
18
+
19
+ def find_call(klass)
20
+ calls.find { |call| call.klass.eql?(klass) }
21
+ end
22
+
23
+ def update_existing_call(call, method_name, *args, &block)
24
+ call.method_name = method_name
25
+ call.args = *args
26
+ call.block = block
27
+ call
28
+ end
29
+
30
+ def push_new_call(klass, method_name, *args, &block)
31
+ calls.push(MethodDecorator::Models::Call.new(klass, method_name, *args, &block))
32
+ end
33
+
34
+ def calls
35
+ @calls ||= []
36
+ end
37
+
38
+ class << self
39
+ def singleton
40
+ @singleton ||= new
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'method_decorator/models/decoration'
4
+ require 'method_decorator/models/call'
5
+ require 'method_decorator/errors/already_decorated_error'
6
+
7
+ module MethodDecorator
8
+ module Repositories
9
+ class DecorationsRepository
10
+ attr_writer :decorations
11
+
12
+ def add!(klass, method_name, method_instance, &decoration)
13
+ added = add(klass, method_name, method_instance, &decoration)
14
+ self.class.raise_already_decorated_error(klass, method_name) unless added
15
+ added
16
+ end
17
+
18
+ def add(klass, method_name, method_instance, &decoration)
19
+ existing = original_method_instance_of(klass, method_name)
20
+ unless existing
21
+ decorations.push(
22
+ Models::Decoration.new(klass, method_name, method_instance, &decoration)
23
+ )
24
+ end
25
+ !existing
26
+ end
27
+
28
+ def original_method_instance_of(klass, method_name)
29
+ decoration = find_decoration(klass, method_name)
30
+ decoration ? decoration.method_instance : nil
31
+ end
32
+
33
+ def find_decoration(klass, method_name)
34
+ decorations.select do |d|
35
+ d.klass.eql?(klass) && d.method_name.eql?(method_name)
36
+ end.first
37
+ end
38
+
39
+ def decorations
40
+ @decorations ||= []
41
+ end
42
+
43
+ class << self
44
+ def raise_already_decorated_error(klass, method_name)
45
+ raise(
46
+ MethodDecorator::Errors::AlreadyDecoratedError,
47
+ "`#{klass}` already has a decorated `#{method_name}`"
48
+ )
49
+ end
50
+
51
+ def singleton
52
+ @singleton ||= new
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'method_decorator/repositories/decorations_repository'
4
+ require 'method_decorator/repositories/calls_repository'
5
+ require 'method_decorator/models/call'
6
+
7
+ module MethodDecorator
8
+ module Services
9
+ class DecorationsService
10
+ attr_accessor :klass, :method_name, :method_instance, :decoration
11
+
12
+ def initialize(klass, method_name, &decoration)
13
+ self.klass = klass
14
+ self.method_name = method_name
15
+ self.method_instance = klass.instance_method(method_name)
16
+ self.decoration = decoration
17
+ self
18
+ end
19
+
20
+ def decorate
21
+ add_decoration_to_repository
22
+ override_original_klass_method
23
+ define_helper_methods
24
+ self
25
+ end
26
+
27
+ protected
28
+
29
+ def add_decoration_to_repository
30
+ self.class.decorations_repository.add!(klass, method_name, method_instance, &decoration)
31
+ end
32
+
33
+ def override_original_klass_method # rubocop:disable Metrics/AbcSize
34
+ is_protected = klass.protected_instance_methods.include?(method_name)
35
+ is_private = klass.private_instance_methods.include?(method_name)
36
+ define_decoration_method
37
+ method_name_ = method_name
38
+ klass.instance_eval { protected method_name_ } if is_protected
39
+ klass.instance_eval { private method_name_ } if is_private
40
+ end
41
+
42
+ def define_decoration_method
43
+ klass_ = klass
44
+ method_name_ = method_name
45
+ decoration_ = decoration
46
+ klass.send(:define_method, method_name) do |*args, &block|
47
+ MethodDecorator.add_call(klass_, method_name_, *args, &block)
48
+ instance_eval(&decoration_)
49
+ end
50
+ end
51
+
52
+ def define_helper_methods
53
+ klass.send(:define_method, :call_args) { MethodDecorator.last_call_args(self) }
54
+ klass.send(:define_method, :call_block) { MethodDecorator.last_call_block(self) }
55
+ define_original_call_helper_method
56
+ define_original_call_with_helper_method
57
+ klass.instance_eval { protected :call_args }
58
+ klass.instance_eval { protected :call_block }
59
+ end
60
+
61
+ def define_original_call_helper_method
62
+ klass.send :define_method, :call_original do
63
+ method_name = MethodDecorator.last_call_method_name(self)
64
+ MethodDecorator.call_original_method self, method_name, *call_args, &call_block
65
+ end
66
+ end
67
+
68
+ def define_original_call_with_helper_method
69
+ klass.send :define_method, :call_original_with do |*desired_args, &desired_block|
70
+ method_name = MethodDecorator.last_call_method_name(self)
71
+ MethodDecorator.call_original_method self, method_name, *desired_args, &desired_block
72
+ end
73
+ end
74
+
75
+ class << self
76
+ def decorate(klass, method_name, &decoration)
77
+ new(klass, method_name, &decoration).decorate
78
+ end
79
+
80
+ def add_call(klass, method_name, *args, &block)
81
+ calls_repository.add(klass, method_name, *args, &block)
82
+ end
83
+
84
+ def call_original_method(context, method_name, *args, &block)
85
+ klass = get_class_from_context(context)
86
+ method_instance = decorations_repository.original_method_instance_of(klass, method_name)
87
+ method_instance.bind(context).call(*args, &block)
88
+ end
89
+
90
+ def last_call_args(context)
91
+ call = last_call_from_context(context)
92
+ call && call.args
93
+ end
94
+
95
+ def last_call_block(context)
96
+ call = last_call_from_context(context)
97
+ call && call.block
98
+ end
99
+
100
+ def last_call_method_name(context)
101
+ call = last_call_from_context(context)
102
+ call && call.method_name
103
+ end
104
+
105
+ def last_call_from_context(context)
106
+ klass = get_class_from_context(context)
107
+ calls_repository.find_call(klass)
108
+ end
109
+
110
+ def get_class_from_context(context)
111
+ if get_type_from_context(context).eql? :singleton
112
+ context.singleton_class
113
+ else
114
+ context.class
115
+ end
116
+ end
117
+
118
+ def get_type_from_context(context)
119
+ context.class.eql?(Class) ? :singleton : :class
120
+ end
121
+
122
+ def decorations_repository
123
+ MethodDecorator::Repositories::DecorationsRepository.singleton
124
+ end
125
+
126
+ def calls_repository
127
+ MethodDecorator::Repositories::CallsRepository.singleton
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MethodDecorator
2
- VERSION = '3.0.2'
3
- end
4
+ VERSION = '4.0.0'
5
+ end
@@ -1,4 +1,6 @@
1
- $:.push File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
4
 
3
5
  require 'method_decorator/version'
4
6
 
@@ -8,18 +10,22 @@ Gem::Specification.new do |s|
8
10
  s.authors = ['r4z3c']
9
11
  s.email = ['r4z3c.43@gmail.com']
10
12
  s.homepage = 'https://github.com/r4z3c/method_decorator.git'
11
- s.summary = 'Overwrite methods preserving the original behavior'
12
- s.description = 'Provides a way to dynamically overwrite methods without losing original behavior'
13
- s.licenses = %w(MIT)
13
+ s.summary = 'Override methods preserving the original behavior'
14
+ s.description = 'Provides a way to dynamically override methods without losing original behavior'
15
+ s.licenses = %w[MIT]
14
16
 
15
17
  s.files = `git ls-files`.split("\n")
16
18
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
17
19
 
18
- s.require_paths = %w(lib)
20
+ s.require_paths = %w[lib]
19
21
 
20
22
  s.add_dependency 'bundler', '>= 1'
21
23
  s.add_dependency 'activesupport', '>= 4'
22
24
 
23
25
  s.add_development_dependency 'rspec', '>= 3'
24
26
  s.add_development_dependency 'simplecov', '>= 0'
27
+ s.add_development_dependency 'codeclimate-test-reporter'
28
+ s.add_development_dependency 'rake'
29
+ s.add_development_dependency 'rubocop'
30
+ s.add_development_dependency 'byebug'
25
31
  end
@@ -1,159 +1,181 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'support/dummy_class'
3
5
 
4
6
  describe MethodDecorator do
5
-
6
7
  let(:dummy_arg) { :dummy_arg }
7
8
 
8
9
  context 'when common class as target' do
9
-
10
10
  let(:target) { DummyClass }
11
11
  let(:target_instance) { target.new }
12
12
 
13
13
  context 'when public method' do
14
-
15
14
  context 'when through module singleton method' do
16
-
17
- before { expect(target_instance).to receive(:puts).with("a_decorated_public_instance_method_with: #{[dummy_arg]}").ordered }
18
- before { expect(target_instance).to receive(:puts).with("a_public_instance_method_arg: #{dummy_arg}").ordered }
15
+ before do
16
+ expect(target_instance).to receive(:puts)
17
+ .with("a_decorated_public_instance_method_with: #{[dummy_arg]}").ordered
18
+ end
19
+ before do
20
+ expect(target_instance).to receive(:puts)
21
+ .with("a_public_instance_method_arg: #{dummy_arg}").ordered
22
+ end
19
23
 
20
24
  it { target_instance.a_public_instance_method dummy_arg }
21
-
22
25
  end
23
26
 
24
27
  context 'when through module inclusion' do
25
-
26
- before { expect(target_instance).to receive(:puts).with("another_decorated_public_instance_method_with: #{[dummy_arg]}").ordered }
27
- before { expect(target_instance).to receive(:puts).with("another_public_instance_method_arg: #{dummy_arg}").ordered }
28
+ before do
29
+ expect(target_instance).to receive(:puts)
30
+ .with("another_decorated_public_instance_method_with: #{[dummy_arg]}").ordered
31
+ end
32
+ before do
33
+ expect(target_instance).to receive(:puts)
34
+ .with("another_public_instance_method_arg: #{dummy_arg}").ordered
35
+ end
28
36
 
29
37
  it { target_instance.another_public_instance_method dummy_arg }
30
-
31
38
  end
32
-
33
39
  end
34
40
 
35
41
  context 'when protected method' do
36
-
37
42
  context 'when calling original method through `call_original`' do
38
-
39
43
  context 'when calling protected method deliberately' do
40
-
41
- it { expect{target_instance.a_protected_instance_method dummy_arg}.to raise_error NoMethodError }
42
-
44
+ it do
45
+ expect { target_instance.a_protected_instance_method dummy_arg }
46
+ .to raise_error(NoMethodError)
47
+ end
43
48
  end
44
49
 
45
50
  context 'when calling protected method through `send`' do
46
-
47
- before { expect(target_instance).to receive(:puts).with("a_decorated_protected_instance_method_with: #{[dummy_arg]}").ordered }
48
- before { expect(target_instance).to receive(:puts).with("a_protected_instance_method_arg: #{dummy_arg}").ordered }
51
+ before do
52
+ expect(target_instance).to receive(:puts)
53
+ .with("a_decorated_protected_instance_method_with: #{[dummy_arg]}").ordered
54
+ end
55
+ before do
56
+ expect(target_instance).to receive(:puts)
57
+ .with("a_protected_instance_method_arg: #{dummy_arg}").ordered
58
+ end
49
59
 
50
60
  it { target_instance.send :a_protected_instance_method, dummy_arg }
51
-
52
61
  end
53
-
54
62
  end
55
63
 
56
64
  context 'when calling original method through `call_original_with`' do
57
-
58
65
  context 'when calling protected method through `send`' do
59
-
60
- before { expect(target_instance).to receive(:puts).with("another_decorated_protected_instance_method_with: #{[dummy_arg]}").ordered }
61
- before { expect(target_instance).to receive(:puts).with("another_protected_instance_method_arg: hue").ordered }
66
+ before do
67
+ expect(target_instance).to receive(:puts)
68
+ .with("another_decorated_protected_instance_method_with: #{[dummy_arg]}").ordered
69
+ end
70
+ before do
71
+ expect(target_instance).to receive(:puts)
72
+ .with('another_protected_instance_method_arg: hue').ordered
73
+ end
62
74
 
63
75
  it { target_instance.send :another_protected_instance_method, dummy_arg }
64
-
65
76
  end
66
-
67
77
  end
68
-
69
78
  end
70
79
 
71
80
  context 'when private method' do
72
-
73
81
  context 'when calling private method deliberately' do
74
-
75
- it { expect{target_instance.a_private_instance_method dummy_arg}.to raise_error NoMethodError }
76
-
82
+ it do
83
+ expect { target_instance.a_private_instance_method dummy_arg }
84
+ .to raise_error(NoMethodError)
85
+ end
77
86
  end
78
87
 
79
88
  context 'when calling private method through `send`' do
80
-
81
- before { expect(target_instance).to receive(:puts).with("a_decorated_private_instance_method_with: #{[dummy_arg]}").ordered }
82
- before { expect(target_instance).to receive(:puts).with("a_private_instance_method_arg: #{dummy_arg}").ordered }
89
+ before do
90
+ expect(target_instance).to receive(:puts)
91
+ .with("a_decorated_private_instance_method_with: #{[dummy_arg]}").ordered
92
+ end
93
+ before do
94
+ expect(target_instance).to receive(:puts)
95
+ .with("a_private_instance_method_arg: #{dummy_arg}").ordered
96
+ end
83
97
 
84
98
  it { target_instance.send :a_private_instance_method, dummy_arg }
85
-
86
99
  end
87
-
88
100
  end
89
-
90
101
  end
91
102
 
92
103
  context 'when singleton class as target' do
93
-
94
104
  let(:target) { DummyClass.singleton_class }
95
105
  let(:target_instance) { DummyClass }
96
106
 
97
107
  context 'when public method' do
98
-
99
108
  context 'when through module singleton method' do
100
-
101
- before { expect(target_instance).to receive(:puts).with("a_decorated_public_singleton_method_with: #{[dummy_arg]}").ordered }
102
- before { expect(target_instance).to receive(:puts).with("a_public_singleton_method_arg: #{dummy_arg}").ordered }
109
+ before do
110
+ expect(target_instance).to receive(:puts)
111
+ .with("a_decorated_public_singleton_method_with: #{[dummy_arg]}").ordered
112
+ end
113
+ before do
114
+ expect(target_instance).to receive(:puts)
115
+ .with("a_public_singleton_method_arg: #{dummy_arg}").ordered
116
+ end
103
117
 
104
118
  it { target_instance.a_public_singleton_method dummy_arg }
105
-
106
119
  end
107
120
 
108
121
  context 'when through module inclusion' do
109
-
110
- before { expect(target_instance).to receive(:puts).with("another_decorated_public_singleton_method_with: #{[dummy_arg]}").ordered }
111
- before { expect(target_instance).to receive(:puts).with("another_public_singleton_method_arg: #{dummy_arg}").ordered }
122
+ before do
123
+ expect(target_instance).to receive(:puts)
124
+ .with("another_decorated_public_singleton_method_with: #{[dummy_arg]}").ordered
125
+ end
126
+ before do
127
+ expect(target_instance).to receive(:puts)
128
+ .with("another_public_singleton_method_arg: #{dummy_arg}").ordered
129
+ end
112
130
 
113
131
  it { target_instance.another_public_singleton_method dummy_arg }
114
-
115
132
  end
116
-
117
133
  end
118
134
 
119
135
  context 'when protected method' do
120
-
121
136
  context 'when calling protected method deliberately' do
122
-
123
- it { expect{target_instance.a_protected_singleton_method dummy_arg}.to raise_error NoMethodError }
124
-
137
+ it do
138
+ expect do
139
+ target_instance.a_protected_singleton_method dummy_arg
140
+ end.to raise_error(NoMethodError)
141
+ end
125
142
  end
126
143
 
127
144
  context 'when calling protected method through `send`' do
128
-
129
- before { expect(target_instance).to receive(:puts).with("a_decorated_protected_singleton_method_with: #{[dummy_arg]}").ordered }
130
- before { expect(target_instance).to receive(:puts).with("a_protected_singleton_method_arg: #{dummy_arg}").ordered }
145
+ before do
146
+ expect(target_instance).to receive(:puts)
147
+ .with("a_decorated_protected_singleton_method_with: #{[dummy_arg]}").ordered
148
+ end
149
+ before do
150
+ expect(target_instance).to receive(:puts)
151
+ .with("a_protected_singleton_method_arg: #{dummy_arg}").ordered
152
+ end
131
153
 
132
154
  it { target_instance.send :a_protected_singleton_method, dummy_arg }
133
-
134
155
  end
135
-
136
156
  end
137
157
 
138
158
  context 'when private method' do
139
-
140
159
  context 'when calling private method deliberately' do
141
-
142
- it { expect{target_instance.a_private_singleton_method dummy_arg}.to raise_error NoMethodError }
143
-
160
+ it do
161
+ expect do
162
+ target_instance.a_private_singleton_method dummy_arg
163
+ end.to raise_error NoMethodError
164
+ end
144
165
  end
145
166
 
146
167
  context 'when calling private method through `send`' do
147
-
148
- before { expect(target_instance).to receive(:puts).with("a_decorated_private_singleton_method_with: #{[dummy_arg]}").ordered }
149
- before { expect(target_instance).to receive(:puts).with("a_private_singleton_method_arg: #{dummy_arg}").ordered }
168
+ before do
169
+ expect(target_instance).to receive(:puts)
170
+ .with("a_decorated_private_singleton_method_with: #{[dummy_arg]}").ordered
171
+ end
172
+ before do
173
+ expect(target_instance).to receive(:puts)
174
+ .with("a_private_singleton_method_arg: #{dummy_arg}").ordered
175
+ end
150
176
 
151
177
  it { target_instance.send :a_private_singleton_method, dummy_arg }
152
-
153
178
  end
154
-
155
179
  end
156
-
157
180
  end
158
-
159
- end
181
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'method_decorator/models/call'
5
+
6
+ describe MethodDecorator::Models::Call do
7
+ describe '#initialize' do
8
+ subject { described_class.new(*args, &block) }
9
+
10
+ context 'when klass is missing' do
11
+ let(:args) { [nil, nil] }
12
+ let(:block) { nil }
13
+ let(:error) { '`klass` attribute is required' }
14
+
15
+ it { expect { subject }.to raise_error(ArgumentError, error) }
16
+ end
17
+
18
+ context 'when klass is not a class' do
19
+ let(:args) { [:klass, nil] }
20
+ let(:block) { nil }
21
+ let(:error) { '`klass` attribute must be a `Class`' }
22
+
23
+ it { expect { subject }.to raise_error(ArgumentError, error) }
24
+ end
25
+
26
+ context 'when method name is missing' do
27
+ let(:args) { [String, nil] }
28
+ let(:block) { nil }
29
+ let(:error) { '`method_name` attribute is required' }
30
+
31
+ it { expect { subject }.to raise_error(ArgumentError, error) }
32
+ end
33
+
34
+ context 'when method name is not a symbol' do
35
+ let(:args) { [String, 'method_name'] }
36
+ let(:block) { nil }
37
+ let(:error) { '`method_name` attribute must be a `Symbol`' }
38
+
39
+ it { expect { subject }.to raise_error(ArgumentError, error) }
40
+ end
41
+
42
+ context 'when everything is ok' do
43
+ let(:args) { [String, :method_name, :a_first_arg, :a_second_arg] }
44
+ let(:block) { proc { :a_block } }
45
+ let(:call) { subject }
46
+
47
+ it { expect(call.klass).to eq String }
48
+ it { expect(call.method_name).to eq :method_name }
49
+ it { expect(call.args).to eq %i[a_first_arg a_second_arg] }
50
+ it { expect(call.block).to eq block }
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'method_decorator/models/decoration'
5
+ require 'method_decorator/models/call'
6
+
7
+ describe MethodDecorator::Models::Decoration do
8
+ let(:method_instance) { String.instance_method(:to_s) }
9
+
10
+ describe '#initialize' do
11
+ subject { described_class.new(*args, &block) }
12
+
13
+ context 'when `klass` attribute is missing' do
14
+ let(:args) { [nil, :method_name, method_instance] }
15
+ let(:block) { proc {} }
16
+ let(:error) { '`klass` attribute is required' }
17
+
18
+ it { expect { subject }.to raise_error(ArgumentError, error) }
19
+ end
20
+
21
+ context 'when `klass` attribute is not a class' do
22
+ let(:args) { [:klass, :method_name, method_instance] }
23
+ let(:block) { proc {} }
24
+ let(:error) { '`klass` attribute must be a `Class`' }
25
+
26
+ it { expect { subject }.to raise_error(ArgumentError, error) }
27
+ end
28
+
29
+ context 'when `method_name` attribute is missing' do
30
+ let(:args) { [String, nil, method_instance] }
31
+ let(:block) { proc {} }
32
+ let(:error) { '`method_name` attribute is required' }
33
+
34
+ it { expect { subject }.to raise_error(ArgumentError, error) }
35
+ end
36
+
37
+ context 'when `method_name` attribute is not a symbol' do
38
+ let(:args) { [String, 'method_name', method_instance] }
39
+ let(:block) { proc {} }
40
+ let(:error) { '`method_name` attribute must be a `Symbol`' }
41
+
42
+ it { expect { subject }.to raise_error(ArgumentError, error) }
43
+ end
44
+
45
+ context 'when `method_instance` attribute is missing' do
46
+ let(:args) { [String, :method_name, nil] }
47
+ let(:block) { proc {} }
48
+ let(:error) { '`method_instance` attribute is required' }
49
+
50
+ it { expect { subject }.to raise_error(ArgumentError, error) }
51
+ end
52
+
53
+ context 'when `method_instance` attribute is not a unbound method' do
54
+ let(:args) { [String, :method_name, :method_instance] }
55
+ let(:block) { proc {} }
56
+ let(:error) { '`method_instance` attribute must be a `UnboundMethod`' }
57
+
58
+ it { expect { subject }.to raise_error(ArgumentError, error) }
59
+ end
60
+
61
+ context 'when `decoration` attribute is missing' do
62
+ let(:args) { [String, :method_name, method_instance] }
63
+ let(:block) { nil }
64
+ let(:error) { '`decoration` attribute is required' }
65
+
66
+ it { expect { subject }.to raise_error(ArgumentError, error) }
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'method_decorator/models/model'
5
+
6
+ describe MethodDecorator::Models::Model do
7
+ describe '#validate!' do
8
+ subject { described_class.new.send :validate! }
9
+ it { expect { subject }.to raise_error(NotImplementedError) }
10
+ end
11
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'method_decorator/repositories/calls_repository'
5
+
6
+ describe MethodDecorator::Repositories::CallsRepository do
7
+ let(:repo) { described_class.singleton }
8
+
9
+ describe '#add' do
10
+ let(:common_args) { [String, :method_name, :a_first_arg, :a_second_arg] }
11
+ let(:block) { proc {} }
12
+
13
+ before { repo.calls = [] }
14
+
15
+ subject { repo.add(*args, &block) }
16
+
17
+ context 'when call not exists' do
18
+ let(:args) { common_args }
19
+
20
+ before { subject }
21
+
22
+ it { expect(repo.calls.count).to eq 1 }
23
+ it { expect(subject[0].klass).to eq String }
24
+ it { expect(subject[0].method_name).to eq :method_name }
25
+ it { expect(subject[0].args).to eq %i[a_first_arg a_second_arg] }
26
+ it { expect(subject[0].block).to eq block }
27
+ end
28
+
29
+ context 'when call already exists' do
30
+ let(:args) { [String, :another_method_name, :another_arg] }
31
+
32
+ before { repo.add(*common_args, &block) }
33
+ before { subject }
34
+
35
+ it { expect(repo.calls.count).to eq 1 }
36
+ it { expect(subject.klass).to eq String }
37
+ it { expect(subject.method_name).to eq :another_method_name }
38
+ it { expect(subject.args).to eq %i[another_arg] }
39
+ it { expect(subject.block).to eq block }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'method_decorator/repositories/decorations_repository'
5
+
6
+ describe MethodDecorator::Repositories::DecorationsRepository do
7
+ let(:repo) { described_class.singleton }
8
+ let(:method_instance) { String.instance_method(:to_s) }
9
+
10
+ before { described_class.singleton.decorations = [] }
11
+
12
+ describe '#add!' do
13
+ let(:args) { [String, :method_name, method_instance] }
14
+ let(:block) { proc {} }
15
+
16
+ subject { repo.add!(*args, &block) }
17
+
18
+ context 'when decoration not exists' do
19
+ it { is_expected.to be_truthy }
20
+ end
21
+
22
+ context 'when decoration already exists' do
23
+ before { repo.add!(*args, &block) }
24
+ it { expect { subject }.to raise_error(MethodDecorator::Errors::AlreadyDecoratedError) }
25
+ end
26
+ end
27
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,7 @@
1
- require 'active_support'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'simplecov'
3
4
 
4
5
  SimpleCov.start
5
6
 
6
- require 'method_decorator'
7
+ require 'method_decorator'
@@ -1,5 +1,6 @@
1
- class DummyClass
1
+ # frozen_string_literal: true
2
2
 
3
+ class DummyClass
3
4
  def a_public_instance_method(arg)
4
5
  puts "a_public_instance_method_arg: #{arg}"
5
6
  end
@@ -25,7 +26,6 @@ class DummyClass
25
26
  end
26
27
 
27
28
  class << self
28
-
29
29
  def a_public_singleton_method(arg)
30
30
  puts "a_public_singleton_method_arg: #{arg}"
31
31
  end
@@ -52,7 +52,6 @@ class DummyClass
52
52
  puts "another_decorated_public_singleton_method_with: #{call_args}"
53
53
  call_original
54
54
  end
55
-
56
55
  end
57
56
 
58
57
  include MethodDecorator
@@ -61,7 +60,6 @@ class DummyClass
61
60
  puts "another_decorated_public_instance_method_with: #{call_args}"
62
61
  call_original
63
62
  end
64
-
65
63
  end
66
64
 
67
65
  MethodDecorator.decorate DummyClass, :a_public_instance_method do
@@ -97,4 +95,4 @@ end
97
95
  MethodDecorator.decorate DummyClass.singleton_class, :a_private_singleton_method do
98
96
  puts "a_decorated_private_singleton_method_with: #{call_args}"
99
97
  call_original
100
- end
98
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: method_decorator
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - r4z3c
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-04 00:00:00.000000000 Z
11
+ date: 2017-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,7 +66,63 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
- description: Provides a way to dynamically overwrite methods without losing original
69
+ - !ruby/object:Gem::Dependency
70
+ name: codeclimate-test-reporter
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: byebug
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Provides a way to dynamically override methods without losing original
70
126
  behavior
71
127
  email:
72
128
  - r4z3c.43@gmail.com
@@ -75,17 +131,30 @@ extensions: []
75
131
  extra_rdoc_files: []
76
132
  files:
77
133
  - ".gitignore"
134
+ - ".rubocop.yml"
78
135
  - ".ruby-version"
136
+ - ".travis.yml"
79
137
  - Gemfile
80
138
  - MIT-LICENSE
81
139
  - README.rdoc
82
140
  - Rakefile
141
+ - bin/console
83
142
  - lib/method_decorator.rb
84
- - lib/method_decorator/decoration.rb
85
- - lib/method_decorator/repository.rb
143
+ - lib/method_decorator/errors/already_decorated_error.rb
144
+ - lib/method_decorator/models/call.rb
145
+ - lib/method_decorator/models/decoration.rb
146
+ - lib/method_decorator/models/model.rb
147
+ - lib/method_decorator/repositories/calls_repository.rb
148
+ - lib/method_decorator/repositories/decorations_repository.rb
149
+ - lib/method_decorator/services/decorations_service.rb
86
150
  - lib/method_decorator/version.rb
87
151
  - method_decorator.gemspec
88
152
  - spec/lib/method_decorator_spec.rb
153
+ - spec/lib/models/call_spec.rb
154
+ - spec/lib/models/decoration_spec.rb
155
+ - spec/lib/models/model_spec.rb
156
+ - spec/lib/repositories/calls_repository_spec.rb
157
+ - spec/lib/repositories/decorations_repository_spec.rb
89
158
  - spec/spec_helper.rb
90
159
  - spec/support/dummy_class.rb
91
160
  homepage: https://github.com/r4z3c/method_decorator.git
@@ -108,11 +177,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
177
  version: '0'
109
178
  requirements: []
110
179
  rubyforge_project:
111
- rubygems_version: 2.5.1
180
+ rubygems_version: 2.6.12
112
181
  signing_key:
113
182
  specification_version: 4
114
- summary: Overwrite methods preserving the original behavior
183
+ summary: Override methods preserving the original behavior
115
184
  test_files:
116
185
  - spec/lib/method_decorator_spec.rb
186
+ - spec/lib/models/call_spec.rb
187
+ - spec/lib/models/decoration_spec.rb
188
+ - spec/lib/models/model_spec.rb
189
+ - spec/lib/repositories/calls_repository_spec.rb
190
+ - spec/lib/repositories/decorations_repository_spec.rb
117
191
  - spec/spec_helper.rb
118
192
  - spec/support/dummy_class.rb
@@ -1,14 +0,0 @@
1
- module MethodDecorator
2
- class Decoration
3
-
4
- attr_accessor :target_class, :target_method_name, :target_method, :decoration
5
-
6
- def initialize(target_class, target_method_name, target_method)
7
- self.target_class = target_class
8
- self.target_method_name = target_method_name
9
- self.target_method = target_method
10
- self.decoration = block_given? ? Proc.new : nil
11
- end
12
-
13
- end
14
- end
@@ -1,30 +0,0 @@
1
- require 'method_decorator/decoration'
2
-
3
- module MethodDecorator
4
- class Repository
5
- class << self
6
-
7
- attr_writer :decorations
8
-
9
- def add(target_class, target_method_name, target_method, &decoration)
10
- exists = original_target_method_of target_class, target_method_name
11
-
12
- self.decorations.push(
13
- Decoration.new target_class, target_method_name, target_method, &decoration
14
- ) unless exists
15
-
16
- not exists
17
- end
18
-
19
- def original_target_method_of(target_class, target_method_name)
20
- decoration = self.decorations.select do
21
- |d| d.target_class.eql?(target_class) and d.target_method_name.eql?(target_method_name)
22
- end.first
23
-
24
- decoration ? decoration.target_method : nil
25
- end
26
-
27
- def decorations; @decorations ||= [] end
28
- end
29
- end
30
- end