context-pattern 1.0.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/Gemfile +0 -10
- data/README.md +193 -1
- data/context-pattern.gemspec +1 -1
- data/lib/context-pattern.rb +6 -3
- data/lib/context/base_context_helper.rb +15 -0
- data/lib/context/controller.rb +16 -2
- data/lib/context/version.rb +1 -1
- metadata +6 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9fcef9a074a4fcc09344918329dc47c42a766a0
|
4
|
+
data.tar.gz: 5f067d37cd45e1f921e24f1e3852e139f85f8011
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e0818e724627fccb128a91984d6281314101af23378eb138d40467d6be401fabdf450bc90b86c33c271af0d2c703e7d160968c355ab9526a9807ee4e6416c95
|
7
|
+
data.tar.gz: cc29966bdc2088c8d7dd4ec7e22ccc72002b020174c40713f13b26573f8da7837a53e2ebef80fbec3baabdb8ba4db4c4d6f3edf80dfe8d3b8d6960f8e1821595
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [1.1.1]
|
4
|
+
* Fix naming bug in BaseContextHelper
|
5
|
+
|
6
|
+
## [1.1.0]
|
7
|
+
* Add method_missing logic to Context::Controller, so that controllers can
|
8
|
+
easily access public methods in the context chain
|
9
|
+
* Add BaseContextHelper module, which is used to provide views access to
|
10
|
+
view_helpers defined in the context chain
|
11
|
+
* Fix gem dependencies in this gemfile
|
12
|
+
* Add README file
|
13
|
+
* Add this changelog
|
14
|
+
|
15
|
+
## [1.0.0]
|
16
|
+
* Prevent contexts from overriding public methods already available in the
|
17
|
+
context chain
|
18
|
+
|
19
|
+
## [0.2.0]
|
20
|
+
* Add specs for BaseContext
|
21
|
+
* Add the introspection methods to BaseContext:
|
22
|
+
#context_chain_class, #context_method_mapping, and #whereis
|
23
|
+
* Add BaseContext.decorate interface
|
24
|
+
|
25
|
+
## [0.1.0]
|
26
|
+
* Initial code ported from the WegoWise app
|
data/Gemfile
CHANGED
@@ -1,12 +1,2 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
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/README.md
CHANGED
@@ -1 +1,193 @@
|
|
1
|
-
#
|
1
|
+
# Context pattern
|
2
|
+
|
3
|
+
This gem gives you the scaffolding needed to easily use the Context Pattern in
|
4
|
+
your Ruby on Rails application
|
5
|
+
|
6
|
+
## What is the context pattern?
|
7
|
+
|
8
|
+
The context pattern provides a way of thinking about and writing Rails
|
9
|
+
applications that results in better code that is easier to maintain. This is
|
10
|
+
done through the introduction of a new category of object known as a Context
|
11
|
+
Object.
|
12
|
+
|
13
|
+
A Context Object is responsible for interpreting the current state of the
|
14
|
+
request, providing the context for a controller to do its work, and defining an
|
15
|
+
interface that may be referenced by views. Every request has exactly one
|
16
|
+
context object associated with it. This context is built up throughout the life
|
17
|
+
cycle of a request.
|
18
|
+
|
19
|
+
If you have never encountered the context pattern before, you should read
|
20
|
+
[the explanatory blog post](http://barunsingh.com/2018/03/04/context_pattern.html)
|
21
|
+
to get a thorough understanding of the motivations behind and benefits of this
|
22
|
+
code pattern, examples of before and after code, and a thorough explanation of
|
23
|
+
how everything works.
|
24
|
+
|
25
|
+
This README is intended to provide a reference for those who are already
|
26
|
+
somewhat familiar with the context pattern.
|
27
|
+
|
28
|
+
## Setting up the gem
|
29
|
+
|
30
|
+
To use this gem, you need to do two things:
|
31
|
+
|
32
|
+
1. Require it in your Gemfile:
|
33
|
+
```ruby
|
34
|
+
gem 'context-pattern'
|
35
|
+
```
|
36
|
+
|
37
|
+
2. Add the following two lines to your `ApplicationController`:
|
38
|
+
```ruby
|
39
|
+
include Context::Controller
|
40
|
+
helper Context::BaseContextHelper
|
41
|
+
```
|
42
|
+
|
43
|
+
## Simple example
|
44
|
+
|
45
|
+
The example below is a simple one that is used to demonstrate various facets
|
46
|
+
of how this gem and the context pattern work. Suppose we have an online
|
47
|
+
bookstore and are looking at a `BooksController#show` action. We want to
|
48
|
+
retrieve the logged in user from the session and the book being viewed from the
|
49
|
+
params. We use a decorator to provide some functionality around showing the
|
50
|
+
user's name (this is contrived, but demonstrative).
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
class ApplicationController < ActionController::Base
|
54
|
+
include Context::Controller
|
55
|
+
helper Context::BaseContextHelper
|
56
|
+
|
57
|
+
before_action :set_application_context
|
58
|
+
|
59
|
+
def set_application_context
|
60
|
+
extend_context :Application, params: params, session: session
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class BooksController < ApplicationController
|
65
|
+
def show
|
66
|
+
extend_context :BookShow
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class ApplicationContext < Context::BaseContext
|
71
|
+
view_helpers :current_user
|
72
|
+
|
73
|
+
attr_accessor :session, :params
|
74
|
+
|
75
|
+
def current_user
|
76
|
+
User.find_by(id: session[:user_id])
|
77
|
+
end
|
78
|
+
memoize :current_user
|
79
|
+
end
|
80
|
+
|
81
|
+
class BookShowContext < Context::BaseContext
|
82
|
+
view_helpers :book
|
83
|
+
|
84
|
+
decorate :current_user, decorator: UserPresenter, memoize: true
|
85
|
+
|
86
|
+
def book
|
87
|
+
Book.find(params[:id])
|
88
|
+
end
|
89
|
+
memoize :book
|
90
|
+
end
|
91
|
+
|
92
|
+
class UserPresenter < SimpleDelegator
|
93
|
+
def abbreviated_name
|
94
|
+
"#{first_name} #{last_name[0]}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
View file:
|
100
|
+
```erb
|
101
|
+
Hi, <%= current_user.abbreviated_name %>.
|
102
|
+
Here is information about <%= book.title %>
|
103
|
+
```
|
104
|
+
|
105
|
+
## Basic components of using the gem
|
106
|
+
|
107
|
+
* All context classes must inherit from `Context::BaseContext`
|
108
|
+
* To add a context to the context stack, use `extend_context`. Usage example:
|
109
|
+
```ruby
|
110
|
+
extend_context :Foo, arg1: 1, arg2: 2
|
111
|
+
# The above is equivalent to adding the following object to the context stack:
|
112
|
+
# FooContext.new(arg1: 1, arg2: 2)
|
113
|
+
```
|
114
|
+
* If you want to be able to provide arguments when initializing a context as
|
115
|
+
in the example above, your context class needs to use `attr_accessor` to
|
116
|
+
declare those attribute names.
|
117
|
+
* A context object has access to all public methods already defined in the
|
118
|
+
context stack. It does not have access to any non-public methods used by
|
119
|
+
other objects in the context stack.
|
120
|
+
* The order in which you add to the context stack is important. While a context
|
121
|
+
object can reference public methods from earlier in the context stack, it can
|
122
|
+
not make reference to public methods from objects added later to the context
|
123
|
+
stack.
|
124
|
+
* Controllers have access to all public methods defined anywhere in the context
|
125
|
+
stack.
|
126
|
+
* Views have access to all public methods in the context stack that are declared
|
127
|
+
to be `view_helpers`.
|
128
|
+
* Methods do not necessarily need to be defined in the same context in which
|
129
|
+
they are declarated to be `view_helpers`. But a method must be available to
|
130
|
+
the context in which it is declared to be a view helper. This means the method
|
131
|
+
must either be defined in that context or in a context that is already part
|
132
|
+
of the context stack at the time.
|
133
|
+
* A context can not overwrite a public method that is already defined in the
|
134
|
+
context stack. Trying to do so will cause a `Context::MethodOverrideError`
|
135
|
+
exception to be raised.
|
136
|
+
* The `decorate` declaration provides a way to get around the above restriction
|
137
|
+
in situations where we reasonably wish to decorate or present an object
|
138
|
+
already available in the context stack. This declaration may be used as
|
139
|
+
follows:
|
140
|
+
```ruby
|
141
|
+
class BlahContext < Context::BaseContext
|
142
|
+
# Suppose `foo` is a public method already available in the context stack
|
143
|
+
decorate :foo, decorator: FooDecorator, args: [:bar, :baz], memoize: true
|
144
|
+
|
145
|
+
# The above is functionaly equivalent to the code below:
|
146
|
+
# def foo
|
147
|
+
# FooDecorator.new(@parent_context.foo, bar: bar, baz: baz)
|
148
|
+
# end
|
149
|
+
# memoize :foo
|
150
|
+
|
151
|
+
def bar; end
|
152
|
+
def baz; end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
* You can reference application routes in your context objects.
|
156
|
+
`Context::BaseContext` includes `Rails.application.routes.url_helpers`. You
|
157
|
+
can also use `link_to` within your contexts.
|
158
|
+
|
159
|
+
|
160
|
+
## Best practices for usage
|
161
|
+
|
162
|
+
The following suggestions are not requirements for using this gem, but bits of
|
163
|
+
advice that have been pulled together from using the context pattern across
|
164
|
+
the WegoWise codebase over a period of five years.
|
165
|
+
|
166
|
+
* You should have something like an `ApplicationContext` that takes params,
|
167
|
+
session, etc. as arguments on initialization. The example in this README
|
168
|
+
shows a simple version of this. If you do this, all later contexts will have
|
169
|
+
access to the params, which will greatly simplify things.
|
170
|
+
* Aside from `ApplicationContext`, you should almost never need to provide any
|
171
|
+
arguments to a context when initializing it via `extend_context`. This means
|
172
|
+
those contexts shouldn't make use of `attr_accessor`. There may be some
|
173
|
+
exceptions, but generally speaking a context should be able to figure out
|
174
|
+
everything it needs from `params` and methods already available via the
|
175
|
+
context stack.
|
176
|
+
* If you find yourself wanting to override methods from earlier contexts in
|
177
|
+
ways that do not follow the decorator pattern, this is a sign you are not
|
178
|
+
thinking about your code properly. Sometimes this may simply be a matter of
|
179
|
+
having different method names for different concepts, Other times it may mean
|
180
|
+
that your contexts are conceptually ambiguous.
|
181
|
+
* It is a best practice to add a comment at the top of each context file stating
|
182
|
+
in plain language what the context is for the usage of that object. This
|
183
|
+
should not need to be more than a couple short sentences. If you are having
|
184
|
+
difficulty doing this, it may be a sign that you are trying to do too much
|
185
|
+
within a single context object.
|
186
|
+
* `memoize` is made available via the `memoizer` gem, which is a dependency of
|
187
|
+
this gem. It is a best practice to memoize all view helpers that do any sort
|
188
|
+
of work, and to memoize objects that use the `decorate` declaration.
|
189
|
+
|
190
|
+
|
191
|
+
## How to test context objects
|
192
|
+
|
193
|
+
To be filled in soon.
|
data/context-pattern.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.extra_rdoc_files = ["README.md", "LICENSE.txt"]
|
17
17
|
s.license = 'MIT'
|
18
18
|
|
19
|
-
s.add_dependency('rails', '>=
|
19
|
+
s.add_dependency('rails', '>= 4.0')
|
20
20
|
s.add_dependency('memoizer')
|
21
21
|
|
22
22
|
s.add_development_dependency('rspec', '~> 3.0')
|
data/lib/context-pattern.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require_relative 'context/base_context'
|
2
|
+
require_relative 'context/controller'
|
3
3
|
|
4
|
-
|
4
|
+
if defined?(Rails)
|
5
|
+
require_relative 'context/railtie'
|
6
|
+
require_relative 'context/base_context_helper'
|
7
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Context
|
2
|
+
module BaseContextHelper
|
3
|
+
def method_missing(method_name, *args, &block)
|
4
|
+
if @__context && @__context.has_view_helper?(method_name)
|
5
|
+
@__context.public_send(method_name, *args, &block)
|
6
|
+
else
|
7
|
+
super
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def respond_to_missing?(method_name, _include_private = false)
|
12
|
+
@__context.has_view_helper?(method_name)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/context/controller.rb
CHANGED
@@ -6,11 +6,25 @@ module Context
|
|
6
6
|
|
7
7
|
def extend_context(context, **args)
|
8
8
|
context_class = "#{context}Context".constantize
|
9
|
-
@
|
9
|
+
@__context = context_class.wrap(@__context, **args)
|
10
10
|
end
|
11
11
|
|
12
12
|
def __set_base_context
|
13
|
-
@
|
13
|
+
@__context = Context::BaseContext.new
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def method_missing(method_name, *args, &block)
|
19
|
+
if @__context.respond_to?(method_name)
|
20
|
+
@__context.public_send(method_name, *args, &block)
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_to_missing?(method_name, include_private = false)
|
27
|
+
@__context.respond_to?(method_name, include_private)
|
14
28
|
end
|
15
29
|
end
|
16
30
|
end
|
data/lib/context/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: context-pattern
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Barun Singh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-03-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -16,20 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
-
- - "<"
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: '5.2'
|
19
|
+
version: '4.0'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
24
|
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
30
|
-
- - "<"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '5.2'
|
26
|
+
version: '4.0'
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: memoizer
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,6 +75,7 @@ extra_rdoc_files:
|
|
81
75
|
- LICENSE.txt
|
82
76
|
files:
|
83
77
|
- ".gitignore"
|
78
|
+
- CHANGELOG.md
|
84
79
|
- Gemfile
|
85
80
|
- LICENSE.txt
|
86
81
|
- README.md
|
@@ -88,6 +83,7 @@ files:
|
|
88
83
|
- context-pattern.gemspec
|
89
84
|
- lib/context-pattern.rb
|
90
85
|
- lib/context/base_context.rb
|
86
|
+
- lib/context/base_context_helper.rb
|
91
87
|
- lib/context/controller.rb
|
92
88
|
- lib/context/railtie.rb
|
93
89
|
- lib/context/version.rb
|