context-pattern 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 160ec1333c94272d332b55acccaee5b32435a2d6
4
- data.tar.gz: 96a764e13dec3cad58a5e43639f59946daf63bb2
3
+ metadata.gz: f9fcef9a074a4fcc09344918329dc47c42a766a0
4
+ data.tar.gz: 5f067d37cd45e1f921e24f1e3852e139f85f8011
5
5
  SHA512:
6
- metadata.gz: ae92a3e686f0164548e11d882c4e19bec842ed29198a62b432e0868524e95b719aee3125e152e17e91cf20758aec29d7f448cc9fde994ecf22d56195f123b7a2
7
- data.tar.gz: 7d2f947aaf3a9e3a4ca66de889e0a78e597e2df35824acb1da4cf34acf71ac229aef9b62d3345fb8bc1a443a1013f25009fd5bd911256ac6670a4a08186c17a9
6
+ metadata.gz: 5e0818e724627fccb128a91984d6281314101af23378eb138d40467d6be401fabdf450bc90b86c33c271af0d2c703e7d160968c355ab9526a9807ee4e6416c95
7
+ data.tar.gz: cc29966bdc2088c8d7dd4ec7e22ccc72002b020174c40713f13b26573f8da7837a53e2ebef80fbec3baabdb8ba4db4c4d6f3edf80dfe8d3b8d6960f8e1821595
@@ -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
- # context-pattern
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.
@@ -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', '>= 3.2', '< 5.2')
19
+ s.add_dependency('rails', '>= 4.0')
20
20
  s.add_dependency('memoizer')
21
21
 
22
22
  s.add_development_dependency('rspec', '~> 3.0')
@@ -1,4 +1,7 @@
1
- require 'context/base_context'
2
- require 'context/controller'
1
+ require_relative 'context/base_context'
2
+ require_relative 'context/controller'
3
3
 
4
- require 'context/railtie' if defined?(Rails)
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
@@ -6,11 +6,25 @@ module Context
6
6
 
7
7
  def extend_context(context, **args)
8
8
  context_class = "#{context}Context".constantize
9
- @context = context_class.wrap(@context, **args)
9
+ @__context = context_class.wrap(@__context, **args)
10
10
  end
11
11
 
12
12
  def __set_base_context
13
- @context = Context::BaseContext.new
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
@@ -1,3 +1,3 @@
1
1
  module Context
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.1'
3
3
  end
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.0.0
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-02-08 00:00:00.000000000 Z
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: '3.2'
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: '3.2'
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