context-pattern 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +22 -0
- data/README.md +1 -0
- data/Rakefile +6 -0
- data/context-pattern.gemspec +24 -0
- data/lib/context-pattern.rb +4 -0
- data/lib/context/base_context.rb +121 -0
- data/lib/context/controller.rb +16 -0
- data/lib/context/railtie.rb +11 -0
- data/lib/context/version.rb +3 -0
- data/spec/base_context_spec.rb +391 -0
- metadata +119 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 160ec1333c94272d332b55acccaee5b32435a2d6
|
4
|
+
data.tar.gz: 96a764e13dec3cad58a5e43639f59946daf63bb2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ae92a3e686f0164548e11d882c4e19bec842ed29198a62b432e0868524e95b719aee3125e152e17e91cf20758aec29d7f448cc9fde994ecf22d56195f123b7a2
|
7
|
+
data.tar.gz: 7d2f947aaf3a9e3a4ca66de889e0a78e597e2df35824acb1da4cf34acf71ac229aef9b62d3345fb8bc1a443a1013f25009fd5bd911256ac6670a4a08186c17a9
|
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
gemspec
|
3
|
+
|
4
|
+
# The AR environment variable lets you test against different
|
5
|
+
# versions of Rails. For example:
|
6
|
+
#
|
7
|
+
# AR=3.2.13 rm Gemfile.lock && bundle install && bundle exec rspec
|
8
|
+
# AR=4.0.0 rm Gemfile.lock && bundle install && bundle exec rspec
|
9
|
+
#
|
10
|
+
if ENV['AR']
|
11
|
+
gem 'activerecord', ENV['AR']
|
12
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT LICENSE
|
2
|
+
|
3
|
+
Copyright (c) 2019 Appfolio Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# context-pattern
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'rubygems' unless defined? Gem
|
3
|
+
require File.dirname(__FILE__) + "/lib/context/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "context-pattern"
|
7
|
+
s.version = Context::VERSION
|
8
|
+
s.authors = ["Barun Singh"]
|
9
|
+
s.email = "bsingh@wegowise.com"
|
10
|
+
s.homepage = "http://github.com/barunio/context-pattern"
|
11
|
+
s.summary = "Start using the Context Pattern in your Rails app"
|
12
|
+
s.description = "Start using the Context Pattern in your Rails app"
|
13
|
+
s.required_rubygems_version = ">= 1.3.6"
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.require_paths = ['lib']
|
16
|
+
s.extra_rdoc_files = ["README.md", "LICENSE.txt"]
|
17
|
+
s.license = 'MIT'
|
18
|
+
|
19
|
+
s.add_dependency('rails', '>= 3.2', '< 5.2')
|
20
|
+
s.add_dependency('memoizer')
|
21
|
+
|
22
|
+
s.add_development_dependency('rspec', '~> 3.0')
|
23
|
+
s.add_development_dependency('rake', '>= 10.4')
|
24
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'memoizer'
|
3
|
+
|
4
|
+
module Context
|
5
|
+
class MethodOverrideError < ::StandardError
|
6
|
+
def initialize(context_class, method_names)
|
7
|
+
@context_class = context_class
|
8
|
+
@method_names = method_names
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"#{@context_class.name} can not overwrite methods already defined in "\
|
13
|
+
"the context chain: #{@method_names}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class BaseContext
|
18
|
+
include Memoizer
|
19
|
+
delegate :link_to, to: 'ActionController::Base.helpers'
|
20
|
+
|
21
|
+
class << self
|
22
|
+
include Memoizer
|
23
|
+
|
24
|
+
def decorate(ancestor_context_method, decorator:, args: [], memoize: false)
|
25
|
+
define_method(ancestor_context_method) do
|
26
|
+
decorator.new(
|
27
|
+
@parent_context.public_send(ancestor_context_method),
|
28
|
+
*(args.map { |arg| instance_eval(arg.to_s) })
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
@decorated_methods ||= []
|
33
|
+
@decorated_methods << ancestor_context_method.to_sym
|
34
|
+
|
35
|
+
if memoize
|
36
|
+
public_send(:memoize, ancestor_context_method)
|
37
|
+
@decorated_methods << "_unmemoized_#{ancestor_context_method}".to_sym
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def is_decorated?(method_name)
|
42
|
+
@decorated_methods.is_a?(Array) &&
|
43
|
+
@decorated_methods.include?(method_name.to_sym)
|
44
|
+
end
|
45
|
+
|
46
|
+
def has_view_helper?(method_name)
|
47
|
+
@view_helpers.is_a?(Array) && @view_helpers.include?(method_name.to_sym)
|
48
|
+
end
|
49
|
+
|
50
|
+
def view_helpers(*method_names)
|
51
|
+
@view_helpers ||= []
|
52
|
+
@view_helpers += method_names.map(&:to_sym)
|
53
|
+
end
|
54
|
+
|
55
|
+
def wrap(parent_context, **args)
|
56
|
+
existing_public_methods = parent_context.context_method_mapping.keys
|
57
|
+
new_public_methods = public_instance_methods(false)
|
58
|
+
redefined_methods = existing_public_methods & new_public_methods
|
59
|
+
redefined_methods.reject! { |method| is_decorated?(method) }
|
60
|
+
|
61
|
+
unless redefined_methods.empty?
|
62
|
+
raise Context::MethodOverrideError.new(self, redefined_methods)
|
63
|
+
end
|
64
|
+
|
65
|
+
new(parent_context: parent_context, **args)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
attr_accessor :parent_context
|
70
|
+
|
71
|
+
def initialize(attributes = {})
|
72
|
+
attributes.each do |k, v|
|
73
|
+
if respond_to?(:"#{k}=")
|
74
|
+
then public_send(:"#{k}=", v)
|
75
|
+
else raise ArgumentError, "unknown attribute: #{k}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def context_class_chain
|
81
|
+
@context_class_chain ||=
|
82
|
+
((@parent_context.try(:context_class_chain) || []) + [self.class.name])
|
83
|
+
end
|
84
|
+
|
85
|
+
def has_view_helper?(method_name)
|
86
|
+
self.class.has_view_helper?(method_name) ||
|
87
|
+
(@parent_context.present? &&
|
88
|
+
@parent_context.has_view_helper?(method_name))
|
89
|
+
end
|
90
|
+
|
91
|
+
def context_method_mapping
|
92
|
+
@context_method_mapping ||=
|
93
|
+
(@parent_context.try(:context_method_mapping) || {})
|
94
|
+
.merge(get_context_method_mapping)
|
95
|
+
end
|
96
|
+
|
97
|
+
def method_missing(method_name, *args, &block)
|
98
|
+
if @parent_context
|
99
|
+
@parent_context.public_send(method_name, *args, &block)
|
100
|
+
else
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def respond_to_missing?(method_name, include_private = false)
|
106
|
+
@parent_context&.respond_to?(method_name, include_private)
|
107
|
+
end
|
108
|
+
|
109
|
+
def whereis(method_name)
|
110
|
+
context_method_mapping[method_name.to_sym]
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def get_context_method_mapping
|
116
|
+
self.class.public_instance_methods(false).reduce({}) do |hash, method_name|
|
117
|
+
hash.merge!(method_name => self.class.name)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Context
|
2
|
+
module Controller
|
3
|
+
def self.included(base)
|
4
|
+
base.send(:prepend_before_action, :__set_base_context)
|
5
|
+
end
|
6
|
+
|
7
|
+
def extend_context(context, **args)
|
8
|
+
context_class = "#{context}Context".constantize
|
9
|
+
@context = context_class.wrap(@context, **args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def __set_base_context
|
13
|
+
@context = Context::BaseContext.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Context
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
|
4
|
+
initializer 'context-pattern.include_url_helpers_in_base_context' do
|
5
|
+
ActiveSupport.on_load :active_record do
|
6
|
+
Context::BaseContext.send :include, Rails.application.routes.url_helpers
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,391 @@
|
|
1
|
+
require 'context-pattern'
|
2
|
+
|
3
|
+
class TestDecorator
|
4
|
+
attr_accessor :decorator_val
|
5
|
+
|
6
|
+
def initialize(str, other_str = '')
|
7
|
+
self.decorator_val = "#{str}..#{other_str}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class TestContext < Context::BaseContext
|
12
|
+
view_helpers :method1,
|
13
|
+
:method2
|
14
|
+
|
15
|
+
attr_accessor :foo
|
16
|
+
|
17
|
+
def method1
|
18
|
+
'method1'
|
19
|
+
end
|
20
|
+
|
21
|
+
def method2
|
22
|
+
'method2'
|
23
|
+
end
|
24
|
+
|
25
|
+
def method3
|
26
|
+
'method3'
|
27
|
+
end
|
28
|
+
|
29
|
+
def bowie
|
30
|
+
"ziggy#{Random.rand}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def random
|
34
|
+
Random.rand
|
35
|
+
end
|
36
|
+
|
37
|
+
def rolling
|
38
|
+
'rolling'
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
attr_accessor :bar1
|
44
|
+
|
45
|
+
def protected_method
|
46
|
+
'protected_method'
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def private_method
|
52
|
+
'private_method'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class TestContext2 < Context::BaseContext
|
57
|
+
decorate :bowie, decorator: TestDecorator
|
58
|
+
decorate :random, decorator: TestDecorator, memoize: true
|
59
|
+
decorate :rolling, decorator: TestDecorator, args: [:stones]
|
60
|
+
|
61
|
+
attr_accessor :blah
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def stones
|
66
|
+
'stones'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class TestContext3 < Context::BaseContext
|
71
|
+
def alpha
|
72
|
+
'alpha'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe Context::BaseContext do
|
77
|
+
describe '.new' do
|
78
|
+
it 'accepts attributes when there are associated public setter methods' do
|
79
|
+
context = TestContext.new(foo: 1)
|
80
|
+
expect(context.foo).to eq(1)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'raises an ArgumentError if the setter is protected' do
|
84
|
+
method = :bar1
|
85
|
+
expect(TestContext.instance_methods.include?(method)).to eq(true)
|
86
|
+
expect { TestContext.new(method => 1) }
|
87
|
+
.to raise_error(ArgumentError, "unknown attribute: #{method}")
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'raises an ArgumentError if the setter is private' do
|
91
|
+
method = :bar2
|
92
|
+
expect(TestContext.new.instance_eval("#{method} = 1")).to eq(1)
|
93
|
+
expect { TestContext.new(method => 1) }
|
94
|
+
.to raise_error(ArgumentError, "unknown attribute: #{method}")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '.has_view_helper?' do
|
99
|
+
it 'is true for declared view helpers' do
|
100
|
+
expect(TestContext.has_view_helper?(:method1)).to eq(true)
|
101
|
+
expect(TestContext.has_view_helper?('method2')).to eq(true)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'is false for public methods that are not declared as view helpers' do
|
105
|
+
method = :method3
|
106
|
+
expect(TestContext.public_instance_methods.include?(method)).to eq(true)
|
107
|
+
expect(TestContext.has_view_helper?(method)).to eq(false)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe '.wrap' do
|
112
|
+
it 'returns a new instance with the `parent_context` attribute set to '\
|
113
|
+
'the supplied context' do
|
114
|
+
contextA = TestContext.new
|
115
|
+
contextB = TestContext2.wrap(contextA)
|
116
|
+
|
117
|
+
expect(contextB.class).to eq(TestContext2)
|
118
|
+
expect(contextB.class.superclass).to eq(Context::BaseContext)
|
119
|
+
expect(contextB.parent_context).to eq(contextA)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'allows setting attributes when wrapping a context' do
|
123
|
+
contextA = TestContext.new
|
124
|
+
contextB = TestContext2.wrap(contextA, blah: 1)
|
125
|
+
|
126
|
+
expect(contextB.blah).to eq(1)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'allows accessing attributes of parent contexts after wrapping' do
|
130
|
+
contextA = TestContext.new(foo: 1)
|
131
|
+
contextB = TestContext2.wrap(contextA)
|
132
|
+
|
133
|
+
expect(contextB.foo).to eq(1)
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'allows setting attributes of parent contexts when wrapping a context' do
|
137
|
+
contextA = TestContext.new
|
138
|
+
contextB = TestContext2.wrap(contextA, foo: 1)
|
139
|
+
|
140
|
+
expect(contextA.foo).to eq(1)
|
141
|
+
expect(contextB.foo).to eq(1)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'raises a MethodOverrideError if a context attempts to overwrite a '\
|
145
|
+
'public instance method defined in a parent context' do
|
146
|
+
contextA = TestContext.new
|
147
|
+
other_context_class = Class.new(Context::BaseContext) do
|
148
|
+
def self.name
|
149
|
+
'BadContext'
|
150
|
+
end
|
151
|
+
|
152
|
+
def method1
|
153
|
+
'overwrite'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
expect { other_context_class.wrap(contextA) }.to raise_error(
|
158
|
+
Context::MethodOverrideError,
|
159
|
+
'BadContext can not overwrite methods already defined in the '\
|
160
|
+
'context chain: [:method1]'
|
161
|
+
)
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'allows overwriting non-public methods' do
|
165
|
+
contextA = TestContext.new
|
166
|
+
other_context_class = Class.new(Context::BaseContext) do
|
167
|
+
def private_method
|
168
|
+
'overwrite'
|
169
|
+
end
|
170
|
+
|
171
|
+
def protected_method
|
172
|
+
'overwrite'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
expect { other_context_class.wrap(contextA) }.not_to raise_error
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe '.decorate is used in conjunction with `wrap` to provide an '\
|
181
|
+
'explicit interface for decorating method values retrieved from earlier in '\
|
182
|
+
'the context chain' do
|
183
|
+
let(:instance) { TestContext.new }
|
184
|
+
let(:instance2) { TestContext2.wrap(instance) }
|
185
|
+
let(:instance3) { TestContext3.wrap(instance2) }
|
186
|
+
|
187
|
+
it 'works when there are no arguments' do
|
188
|
+
allow(Random).to receive(:rand).and_return(1)
|
189
|
+
|
190
|
+
expect(instance.bowie).to eq('ziggy1')
|
191
|
+
|
192
|
+
decorated = instance2.bowie
|
193
|
+
expect(decorated).to be_a(TestDecorator)
|
194
|
+
expect(decorated.decorator_val).to eq('ziggy1..')
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'works when there are arguments supplied to the decorator, with the '\
|
198
|
+
'arguments coming from method evaluations in the context' do
|
199
|
+
expect(instance.rolling).to eq('rolling')
|
200
|
+
|
201
|
+
decorated = instance2.rolling
|
202
|
+
expect(decorated).to be_a(TestDecorator)
|
203
|
+
expect(decorated.decorator_val).to eq('rolling..stones')
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'does not memoize by default' do
|
207
|
+
allow(Random).to receive(:rand).and_return(1, 2, 3)
|
208
|
+
|
209
|
+
expect(instance2.bowie.decorator_val).to eq('ziggy1..')
|
210
|
+
expect(instance2.bowie.decorator_val).to eq('ziggy2..')
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'memoizes when the relevant option is set to true' do
|
214
|
+
allow(Random).to receive(:rand).and_return(1, 2, 3)
|
215
|
+
|
216
|
+
expect(instance2.random.decorator_val).to eq('1..')
|
217
|
+
expect(instance2.random.decorator_val).to eq('1..')
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'cascades decorated values to contexts down the chain' do
|
221
|
+
cascaded_decorated = instance3.rolling
|
222
|
+
expect(cascaded_decorated).to be_a(TestDecorator)
|
223
|
+
expect(cascaded_decorated.decorator_val).to eq('rolling..stones')
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe '#context_class_chain' do
|
228
|
+
let(:instance) { TestContext.new(foo: 1) }
|
229
|
+
let(:instance2) { TestContext2.wrap(instance) }
|
230
|
+
let(:instance3) { TestContext3.wrap(instance2) }
|
231
|
+
|
232
|
+
it 'returns an array of all chained context class names in the order '\
|
233
|
+
'they were wrapped' do
|
234
|
+
expect(instance.context_class_chain).to eq(['TestContext'])
|
235
|
+
expect(instance2.context_class_chain)
|
236
|
+
.to eq(['TestContext', 'TestContext2'])
|
237
|
+
expect(instance3.context_class_chain)
|
238
|
+
.to eq(['TestContext', 'TestContext2', 'TestContext3'])
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
describe '#has_view_helper?' do
|
243
|
+
let(:instance) { TestContext.new(foo: 1) }
|
244
|
+
let(:instance2) { TestContext2.wrap(instance) }
|
245
|
+
let(:instance3) { TestContext3.wrap(instance2) }
|
246
|
+
|
247
|
+
it 'is true for a declared view helper' do
|
248
|
+
expect(instance.has_view_helper?(:method1)).to eq(true)
|
249
|
+
expect(instance.has_view_helper?('method2')).to eq(true)
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'is false for public methods not declared as view helpers' do
|
253
|
+
expect(instance.method3).to eq('method3')
|
254
|
+
expect(instance.has_view_helper?(:method3)).to eq(false)
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'is true if a wrapped context has a certain declared view helper' do
|
258
|
+
expect(instance2.has_view_helper?(:method1)).to eq(true)
|
259
|
+
expect(instance2.has_view_helper?('method2')).to eq(true)
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'is true if a grand-wrapped context has a declared view helper' do
|
263
|
+
expect(instance3.has_view_helper?(:method1)).to eq(true)
|
264
|
+
expect(instance3.has_view_helper?('method2')).to eq(true)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
describe '#context_method_mapping' do
|
269
|
+
let(:instance) { TestContext.new }
|
270
|
+
let(:instance2) { TestContext2.wrap(instance) }
|
271
|
+
let(:instance3) { TestContext3.wrap(instance2) }
|
272
|
+
|
273
|
+
it 'returns a hash of public instance method names as keys and the '\
|
274
|
+
'context name as values' do
|
275
|
+
expect(instance.context_method_mapping).to eq(
|
276
|
+
{
|
277
|
+
:bowie => 'TestContext',
|
278
|
+
:foo => 'TestContext',
|
279
|
+
:foo= => 'TestContext',
|
280
|
+
:method1 => 'TestContext',
|
281
|
+
:method2 => 'TestContext',
|
282
|
+
:method3 => 'TestContext',
|
283
|
+
:random => 'TestContext',
|
284
|
+
:rolling => 'TestContext',
|
285
|
+
}
|
286
|
+
)
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'includes public instance methods from a wrapped context, with that '\
|
290
|
+
'wrapped context class name as the associated value for those methods' do
|
291
|
+
expect(instance2.context_method_mapping).to eq(
|
292
|
+
{
|
293
|
+
:_unmemoized_random => 'TestContext2',
|
294
|
+
:blah => 'TestContext2',
|
295
|
+
:blah= => 'TestContext2',
|
296
|
+
:bowie => 'TestContext2',
|
297
|
+
:foo => 'TestContext',
|
298
|
+
:foo= => 'TestContext',
|
299
|
+
:method1 => 'TestContext',
|
300
|
+
:method2 => 'TestContext',
|
301
|
+
:method3 => 'TestContext',
|
302
|
+
:random => 'TestContext2',
|
303
|
+
:rolling => 'TestContext2'
|
304
|
+
}
|
305
|
+
)
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'returns the expected values for multiple-wrapped contexts' do
|
309
|
+
expect(instance3.context_method_mapping).to eq(
|
310
|
+
{
|
311
|
+
:_unmemoized_random => 'TestContext2',
|
312
|
+
:alpha => 'TestContext3',
|
313
|
+
:blah => 'TestContext2',
|
314
|
+
:blah= => 'TestContext2',
|
315
|
+
:bowie => 'TestContext2',
|
316
|
+
:foo => 'TestContext',
|
317
|
+
:foo= => 'TestContext',
|
318
|
+
:method1 => 'TestContext',
|
319
|
+
:method2 => 'TestContext',
|
320
|
+
:method3 => 'TestContext',
|
321
|
+
:random => 'TestContext2',
|
322
|
+
:rolling => 'TestContext2'
|
323
|
+
}
|
324
|
+
)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
describe '#whereis' do
|
329
|
+
let(:instance) { TestContext.new }
|
330
|
+
let(:instance2) { TestContext2.wrap(instance) }
|
331
|
+
let(:instance3) { TestContext3.wrap(instance2) }
|
332
|
+
|
333
|
+
it 'returns the name of the context class in the chain where a method is '\
|
334
|
+
'defined' do
|
335
|
+
expect(instance3.whereis('foo=')).to eq('TestContext')
|
336
|
+
expect(instance3.whereis(:method2)).to eq('TestContext')
|
337
|
+
expect(instance3.whereis('blah')).to eq('TestContext2')
|
338
|
+
expect(instance3.whereis('alpha')).to eq('TestContext3')
|
339
|
+
end
|
340
|
+
|
341
|
+
it 'handles decorated methods, showing the class that most recently '\
|
342
|
+
'did the decoration' do
|
343
|
+
expect(instance3.whereis(:bowie)).to eq('TestContext2')
|
344
|
+
end
|
345
|
+
|
346
|
+
it 'returns nil if the method is not a public method' do
|
347
|
+
expect(instance.instance_eval('protected_method'))
|
348
|
+
.to eq('protected_method')
|
349
|
+
expect(instance.whereis(:protected_method)).to eq(nil)
|
350
|
+
|
351
|
+
expect(instance.instance_eval('private_method')).to eq('private_method')
|
352
|
+
expect(instance.whereis(:private_method)).to eq(nil)
|
353
|
+
end
|
354
|
+
|
355
|
+
it 'returns nil if the method does not exist' do
|
356
|
+
expect(instance.whereis(:obladiblahda)).to eq(nil)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
describe 'method_missing' do
|
361
|
+
let(:instance) { TestContext.new(foo: 1) }
|
362
|
+
let(:instance2) { TestContext2.wrap(instance) }
|
363
|
+
let(:instance3) { TestContext3.wrap(instance2) }
|
364
|
+
|
365
|
+
it 'responds to public methods from a wrapped context' do
|
366
|
+
expect(instance2.method3).to eq('method3')
|
367
|
+
end
|
368
|
+
|
369
|
+
it 'response to public methods from a grand-wrapped context' do
|
370
|
+
expect(instance3.method3).to eq('method3')
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'does not respond to protected methods from a wrapped context' do
|
374
|
+
expect(instance.instance_eval('protected_method'))
|
375
|
+
.to eq('protected_method')
|
376
|
+
|
377
|
+
expect { instance2.instance_eval('protected_method') }
|
378
|
+
.to raise_error(NoMethodError)
|
379
|
+
expect { instance2.protected_method }.to raise_error(NoMethodError)
|
380
|
+
end
|
381
|
+
|
382
|
+
it 'does not respond to private methods from a wrapped context' do
|
383
|
+
expect(instance.instance_eval('private_method'))
|
384
|
+
.to eq('private_method')
|
385
|
+
|
386
|
+
expect { instance2.instance_eval('private_method') }
|
387
|
+
.to raise_error(NoMethodError)
|
388
|
+
expect { instance2.private_method }.to raise_error(NoMethodError)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: context-pattern
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Barun Singh
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-02-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '5.2'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.2'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '5.2'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: memoizer
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rake
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '10.4'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '10.4'
|
75
|
+
description: Start using the Context Pattern in your Rails app
|
76
|
+
email: bsingh@wegowise.com
|
77
|
+
executables: []
|
78
|
+
extensions: []
|
79
|
+
extra_rdoc_files:
|
80
|
+
- README.md
|
81
|
+
- LICENSE.txt
|
82
|
+
files:
|
83
|
+
- ".gitignore"
|
84
|
+
- Gemfile
|
85
|
+
- LICENSE.txt
|
86
|
+
- README.md
|
87
|
+
- Rakefile
|
88
|
+
- context-pattern.gemspec
|
89
|
+
- lib/context-pattern.rb
|
90
|
+
- lib/context/base_context.rb
|
91
|
+
- lib/context/controller.rb
|
92
|
+
- lib/context/railtie.rb
|
93
|
+
- lib/context/version.rb
|
94
|
+
- spec/base_context_spec.rb
|
95
|
+
homepage: http://github.com/barunio/context-pattern
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 1.3.6
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 2.6.14.1
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Start using the Context Pattern in your Rails app
|
119
|
+
test_files: []
|