context-pattern 1.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 +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: []
|