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 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