detaso-oprah 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1ee902d46e66b0a39b778241c9d18a8202dab24713ffaf4389b9ba3e43da831c
4
+ data.tar.gz: 8a611ae249feb8c39c849201cd463cd269b3f532de9f7fcdac8f5fe8fc296b07
5
+ SHA512:
6
+ metadata.gz: d42a4b5ca87c66396eab35e4f6a53f75078a2e6fd1b4ac48ab91161d63ec727ea3395047ca6b348b9be1d2e6194a635690a92c1df1252284db277251b31b3069
7
+ data.tar.gz: bd2947f1f69df130f0c96101a08ff3ee4a982509ad7c6285f039961a7c1a6e0977ba791552d85ff51279b9b9ac12e873f3f635d78450b2a95dff5f783e0cd72b
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ test/dummy/log/
data/.travis.yml ADDED
@@ -0,0 +1,18 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ script: bundle exec rake test
5
+ rvm:
6
+ - 2.3.1
7
+ matrix:
8
+ global:
9
+ - BUNDLE_JOBS=4
10
+ before_install:
11
+ - bundle install --retry=3
12
+ before_update:
13
+ - bundle update
14
+ notifications:
15
+ email: false
16
+ addons:
17
+ code_climate:
18
+ repo_token: 3d9c9c681c7be79695156ecbf9978ada542e60b726a7ba728866dee6f95f3ccd
data/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ 0.3.0
2
+ -----
3
+
4
+ - Presenter now inherits from SimpleDelegator
5
+ - Support for anonymous ancestors [#9]
6
+
7
+
8
+ 0.2.1
9
+ -----
10
+
11
+ - Support for ActionMailer
12
+
13
+ 0.2.0
14
+ -----
15
+
16
+ - Replace `Oprah::Cache` with `ActiveSupport::Cache::MemoryStore` [#3]
17
+
18
+ 0.1.3
19
+ -----
20
+
21
+ - Presenters can now be specified using the `only:` keyword argument, which
22
+ takes either a Class or an Array of classes
23
+ - Replace repeated method default arguments with splats
24
+ - Add assertions to TestHelper
25
+ - Delegate to most recent view context in presenters created from within Rails
26
+ controllers [#1]
27
+ - Add `#present` and `#present_many` methods to Presenter [#2]
28
+
29
+ 0.1.2
30
+ -----
31
+
32
+ - Explicitly rescue `NameError` in `Cache#presenter_classes_for`
33
+ - Add `Oprah::TestHelpers`
34
+ - Delegate `#to_s` and `#inspect` to the presented object
35
+
36
+ 0.1.1
37
+ -----
38
+
39
+ - Initial public release
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,86 @@
1
+ # Contributing
2
+
3
+ When contributing to this repository, please first discuss the change you wish
4
+ to make via issue, email, or any other method with the owners of this repository
5
+ before making a change.
6
+
7
+ Please note we have a code of conduct, please follow it in all your interactions
8
+ with the project.
9
+
10
+ ## Pull Request Process
11
+
12
+ If you'd like to contribute to Oprah, start by forking the repository on GitHub:
13
+
14
+ http://github.com/endofunky/oprah
15
+
16
+ To get all of the dependencies, install the gem first. The best way to get your
17
+ changes merged back into core is as follows:
18
+
19
+ - If you are about to add a larger feature, open an issue on the GitHub issue
20
+ tracker to discuss your ideas first. If whatever you're about to implement is
21
+ already in the issue tracker, please let us know that you picked it up.
22
+
23
+ - Clone down your fork.
24
+
25
+ - Create a thoughtfully named topic branch to contain your change.
26
+
27
+ - Write some code.
28
+
29
+ - Add tests and make sure everything still passes by running bundle exec rake.
30
+ This is mandatory for pull requests to be accepted.
31
+
32
+ - If you are adding new functionality, document it!
33
+
34
+ - Do not change the version number.
35
+
36
+ - If necessary, rebase your commits into logical chunks, without errors.
37
+
38
+ - Push the branch up to GitHub.
39
+
40
+ - Send a pull request to the endofunky/oprah project.
41
+
42
+ ## Contributor Code of Conduct
43
+
44
+ As contributors and maintainers of this project, and in the interest of
45
+ fostering an open and welcoming community, we pledge to respect all people who
46
+ contribute through reporting issues, posting feature requests, updating
47
+ documentation, submitting pull requests or patches, and other activities.
48
+
49
+ We are committed to making participation in this project a harassment-free
50
+ experience for everyone, regardless of level of experience, gender, gender
51
+ identity and expression, sexual orientation, disability, personal appearance,
52
+ body size, race, ethnicity, age, religion, or nationality.
53
+
54
+ Examples of unacceptable behavior by participants include:
55
+
56
+ - The use of sexualized language or imagery
57
+
58
+ - Personal attacks
59
+
60
+ - Trolling or insulting/derogatory comments
61
+
62
+ - Public or private harassment
63
+
64
+ - Publishing other's private information, such as physical or electronic
65
+ addresses, without explicit permission
66
+
67
+ - Other unethical or unprofessional conduct.
68
+
69
+ Project maintainers have the right and responsibility to remove, edit, or
70
+ reject comments, commits, code, wiki edits, issues, and other contributions that
71
+ are not aligned to this Code of Conduct. By adopting this Code of Conduct,
72
+ project maintainers commit themselves to fairly and consistently applying these
73
+ principles to every aspect of managing this project. Project maintainers who do
74
+ not follow or enforce the Code of Conduct may be permanently removed from the
75
+ project team.
76
+
77
+ This code of conduct applies both within project spaces and in public spaces
78
+ when an individual is representing the project or its community.
79
+
80
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
81
+ reported by opening an issue or contacting one or more of the project
82
+ maintainers.
83
+
84
+ This Code of Conduct is adapted from the
85
+ [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
86
+ available at http://contributor-covenant.org/version/1/2/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sheaf.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2016 Tobias Svensson <tob@tobiassvensson.co.uk>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,314 @@
1
+ # Oprah
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/oprah.svg)](https://badge.fury.io/rb/oprah)
4
+ [![Build Status](https://travis-ci.org/endofunky/oprah.svg)](https://travis-ci.org/endofunky/oprah)
5
+ [![Code Climate](https://codeclimate.com/github/endofunky/oprah.svg)](https://codeclimate.com/github/endofunky/oprah)
6
+ [![Dependency Status](https://gemnasium.com/badges/github.com/endofunky/oprah.svg)](https://gemnasium.com/github.com/endofunky/oprah)
7
+
8
+ Opinionated presenters for Rails 5 - without the cruft.
9
+
10
+ ## Table of Contents
11
+
12
+ * [Overview](#overview)
13
+ * [Installation](#installation)
14
+ * [Getting started](#getting-started)
15
+ + [ActionController integration](#actioncontroller-integration)
16
+ + [ActionMailer integration](#actionmailer-integration)
17
+ * [Collections](#collections)
18
+ * [Associations](#associations)
19
+ * [Composition](#composition)
20
+ + [Performance](#performance)
21
+ + [Ordering](#ordering)
22
+ + [Choosing presenters](#choosing-presenters)
23
+ * [Testing](#testing)
24
+ * [API Documentation](#api-documentation)
25
+ * [Contributing](#contributing)
26
+ * [License](#license)
27
+ * [Author](#author)
28
+
29
+ ## Overview
30
+
31
+ If you've ever worked on a sufficiently large Rails application you've probably
32
+ experienced the Rails helper mess first hand. Helper methods are annoying to
33
+ locate, hard to test and not terribly expressive.
34
+
35
+ So why another presenter/decorator library? Oprah was written with a few simple
36
+ goals in mind only covered partially (or not at all) by other gems:
37
+
38
+ - Thin, lightweight layer over Ruby's `SimpleDelegator`
39
+ - Presenters should be easy to test
40
+ - Avoid monkey patching, where possible :monkey::gun:
41
+ - Embrace convention over configuration
42
+ - First-class support for composition (modules and concerns)
43
+
44
+ ## Installation
45
+
46
+ Add this line to your application's Gemfile:
47
+
48
+ ``` ruby
49
+ gem 'oprah'
50
+ ```
51
+
52
+ And then execute:
53
+
54
+ ```
55
+ $ bundle
56
+ ```
57
+
58
+ ## Getting started
59
+
60
+ Oprah expects a single presenter for each of your classes or modules. If your
61
+ model is called `User` it will look for a class called `UserPresenter`:
62
+
63
+ ``` ruby
64
+ class User
65
+ def first_name
66
+ "John"
67
+ end
68
+
69
+ def last_name
70
+ "Doe"
71
+ end
72
+ end
73
+
74
+ class UserPresenter < Oprah::Presenter
75
+ def name
76
+ "#{first_name} #{last_name}"
77
+ end
78
+ end
79
+ ```
80
+
81
+ Oprah will figure out the presenters by itself so you don't have to instantiate
82
+ your presenter classes directly:
83
+
84
+ ``` ruby
85
+ presenter = Oprah.present(User.new)
86
+
87
+ presenter.name
88
+ # => "John Doe"
89
+
90
+ ```
91
+
92
+ Of course, all the regular methods on your model are still accessible:
93
+
94
+ ``` ruby
95
+ presenter.first_name
96
+ # => "John"
97
+ ```
98
+
99
+ If you *DO* want to use a specific presenter, you can simply instantiate it
100
+ yourself:
101
+
102
+ ``` ruby
103
+ SomeOtherPresenter.new(User.new)
104
+ ```
105
+
106
+ ### ActionController integration
107
+
108
+ Now, where do we put our presenters? Ideally, you'd want to expose them in your
109
+ controller. Oprah avoids monkey patching and generally it's good to be aware of
110
+ what's going on, even if that means to be (at least a little bit) explicit.
111
+
112
+ Here's how you can use Oprah presenters from your controller:
113
+
114
+ ``` ruby
115
+ class UsersController < ApplicationController
116
+ def show
117
+ @user = present User.find(params[:id])
118
+ end
119
+ end
120
+ ```
121
+
122
+ This will also take care of passing the correct view context to the presenter,
123
+ which you can access with the `#view_context` (or shorter, `#h`) instance
124
+ method.
125
+
126
+ ### ActionMailer integration
127
+
128
+ Oprah will make the same helpers you have in ActionController available to
129
+ ActionMailer:
130
+
131
+ ``` ruby
132
+ class UserMailer < ApplicationMailer
133
+ default from: 'notifications@example.com'
134
+
135
+ def welcome_email(user)
136
+ @user = present user
137
+ mail(to: @user.email, subject: 'Welcome to My Awesome Site')
138
+ end
139
+ end
140
+ ```
141
+
142
+ ## Collections
143
+
144
+ Oprah has basic support for collections with `.present_many`. It will simply
145
+ apply it's `.present` behavior to each object in the given collection:
146
+
147
+ ``` ruby
148
+ users = [User.new, User.new]
149
+ presenters = Oprah.present_many(users)
150
+
151
+ presenters.first.kind_of?(UserPresenter)
152
+ # => true
153
+
154
+ presenters.last.kind_of?(UserPresenter)
155
+ # => true
156
+ ```
157
+
158
+ Of course, this works in controllers, too:
159
+
160
+ ``` ruby
161
+ class UserController < ApplicationController
162
+ def index
163
+ @users = present_many User.all
164
+ end
165
+ end
166
+ ```
167
+
168
+ ## Associations
169
+
170
+ You can also automatically use presenters for your associations using the
171
+ `#presents_one` and `#presents_many` macros. Let's say you have the following
172
+ `Project` model:
173
+
174
+ ``` ruby
175
+ class Project
176
+ has_many :users
177
+ has_one :owner, class_name: "User"
178
+ end
179
+ ```
180
+
181
+ Oprah lets you easily wrap the associated objects:
182
+
183
+ ``` ruby
184
+ class ProjectPresenter < Oprah::Presenter
185
+ presents_many :users
186
+ presents_one :owner
187
+ end
188
+ ```
189
+
190
+ Note that you don't need to explicitly state the association class.
191
+
192
+ ## Composition
193
+
194
+ Let's say you extraced some behaviour out of your model into a reusable module (or
195
+ `ActiveSupport::Concern`). Oprah lets you write a single, separate presenter for
196
+ this module and automatically chains it to your "main presenter" by walking up the
197
+ ancestor chain of the given object.
198
+
199
+ Let's say we want to mix a shared `Describable` module into our `User` class from
200
+ above and render the description to HTML:
201
+
202
+
203
+ ``` ruby
204
+ module Describable
205
+ def description
206
+ "*AWESOME*"
207
+ end
208
+ end
209
+
210
+ class User
211
+ include Describable
212
+ end
213
+
214
+ class DescribablePresenter < Oprah::Presenter
215
+ def description
216
+ Kramdown::Document.new(object.description).to_html
217
+ end
218
+ end
219
+ ```
220
+
221
+ You can now access the methods of both, `UserPresenter` *and*
222
+ `DescribablePresenter`:
223
+
224
+ ``` ruby
225
+ presenter = Oprah.present(User.new)
226
+
227
+ presenter.description
228
+ => "<p><em>AWESOME</em></p>\n"
229
+
230
+ presenter.name
231
+ # => John Doe
232
+ ```
233
+
234
+ ### Performance
235
+
236
+ Of course, looking up all the presenters would imply a performance issue. But
237
+ don't worry, Oprah caches all matching presenters for a class (and busts it's
238
+ cache on code reloads for a smooth development experience).
239
+
240
+ ### Ordering
241
+
242
+ Oprah walks your object's ancestor chain in reverse. For example, you'd be
243
+ able to access the methods exposed by the `DescribablePresenter` from your
244
+ `UserPresenter`. You can even use `super`:
245
+
246
+ ``` ruby
247
+ class DescribablePresenter < Oprah::Presenter
248
+ def baz
249
+ "foo"
250
+ end
251
+ end
252
+
253
+ class UserPresenter < Oprah::Presenter
254
+ def baz
255
+ super + "bar"
256
+ end
257
+ end
258
+
259
+ Oprah.present(User.new).baz
260
+ # => "foobar"
261
+ ```
262
+
263
+ ### Choosing presenters
264
+
265
+ When presenting an object you can optionally choose which presenter classes
266
+ to use:
267
+
268
+ ``` ruby
269
+ Oprah.present(User.new, only: DescribablePresenter)
270
+ ```
271
+
272
+ This parameter takes either a single presenter or an `Array` of presenters.
273
+ The presenter(s) given need to match the object's class or one of it's
274
+ ancestors. Non-matching presenters given will be ignored.
275
+
276
+ ## Testing
277
+
278
+ Testing presenters is as simple as testing a regular class. Oprah also
279
+ provides couple of helpers to make it even easier:
280
+
281
+ ``` ruby
282
+ class UserPresenterTest < Minitest::Test
283
+ include Oprah::TestHelpers
284
+
285
+ def setup
286
+ @presenter = present User.new
287
+ end
288
+
289
+ def test_presented
290
+ assert_presented @presenter
291
+ end
292
+
293
+ def test_name
294
+ assert_equal "John Doe", @presenter.name
295
+ end
296
+ end
297
+ ```
298
+
299
+ ## API Documentation
300
+
301
+ Comprehensive API Documentation is available at
302
+ [rubydoc.info](http://www.rubydoc.info/gems/oprah).
303
+
304
+ ## Contributing
305
+
306
+ Please check out our [contributing guidelines](CONTRIBUTING.md).
307
+
308
+ ## License
309
+
310
+ Released under the MIT license. See the LICENSE file for details.
311
+
312
+ ## Author
313
+
314
+ Tobias Svensson, [@endofunky](https://twitter.com/endofunky), [http://github.com/endofunky](http://github.com/endofunky)
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require File.join(Dir.pwd, 'lib', 'oprah', 'version')
2
+
3
+ Dir["tasks/**/*.rb"].each { |task| load task }
4
+
5
+ task default: :test
@@ -0,0 +1,109 @@
1
+ module Oprah
2
+ # Helpers that will be mixed into `ActionController::Base` and
3
+ # `ActionMailer::Base` by the {Oprah::Railtie}.
4
+ module ControllerHelpers
5
+ # A proxy class to delegate method calls to view contexts in presenters
6
+ # to the most recently created view context by
7
+ # {ControllerHelpers#view_context}.
8
+ #
9
+ # `ViewContextProxy` objects are automatically created in
10
+ # {ControllerHelpers#present} and {ControllerHelpers#present_many} and
11
+ # shouldn't have to be created manually.
12
+ #
13
+ # @since 0.1.3
14
+ class ViewContextProxy < ActiveSupport::ProxyObject
15
+ # @param [ActionController::Base] controller
16
+ # The controller to delegate to.
17
+ def initialize(controller)
18
+ @controller = controller
19
+ end
20
+
21
+ # Delegates all method calls to the `ActionView::Base` returned from
22
+ # {ControllerHelpers#oprah_view_context}.
23
+ def method_missing(meth, *args, &block)
24
+ @controller.oprah_view_context.__send__(meth, *args, &block)
25
+ end
26
+ end
27
+
28
+ extend ActiveSupport::Concern
29
+
30
+ included do
31
+ helper_method :present
32
+ helper_method :present_many
33
+ end
34
+
35
+ # Presents a single object.
36
+ #
37
+ # Will pass the view context returned from {#oprah_view_context} to the
38
+ # presenter by default. This can be overridden.
39
+ #
40
+ # @see Presenter.present
41
+ def present(*args, **kwargs, &block)
42
+ kwargs = {
43
+ view_context: oprah_view_context_proxy
44
+ }.merge(kwargs)
45
+
46
+ Presenter.present(*args, **kwargs, &block)
47
+ end
48
+
49
+ # Presents a collection of objects.
50
+ #
51
+ # Will pass the view context returned from {#oprah_view_context} to the
52
+ # presenter by default. This can be overridden.
53
+ #
54
+ # @see Presenter.present_many
55
+ def present_many(*args, **kwargs, &block)
56
+ kwargs = {
57
+ view_context: oprah_view_context_proxy
58
+ }.merge(kwargs)
59
+
60
+ Presenter.present_many(*args, **kwargs, &block)
61
+ end
62
+
63
+ # The view context automatically passed to objects presented from this
64
+ # controller.
65
+ #
66
+ # You can override this method pass a custom view context to all
67
+ # presented objects from the controller scope.
68
+ #
69
+ # @see #oprah_view_context=
70
+ # @return [ActionView::Base]
71
+ def oprah_view_context
72
+ @oprah_view_context || view_context
73
+ end
74
+
75
+ # Assigns the view context returned from {#oprah_view_context}.
76
+ #
77
+ # You can override this method pass a custom view context to all
78
+ # presented objects from the controller scope.
79
+ #
80
+ # @since 0.1.3
81
+ # @see #oprah_view_context
82
+ # @param [ActionView::Base] view_context The view context to assign
83
+ # @return [ActionView::Base]
84
+ def oprah_view_context=(view_context)
85
+ @oprah_view_context = view_context
86
+ end
87
+
88
+ # Returns an instance of a view class and sets the current view context
89
+ # returned by {#oprah_view_context}.
90
+ #
91
+ # If you override this method in your controller ensure you keep Oprah's
92
+ # view context updated using {#oprah_view_context=}.
93
+ #
94
+ # @since 0.1.3
95
+ # @see http://api.rubyonrails.org/classes/ActionView/Rendering.html#method-i-view_context
96
+ # Rails API Documentation
97
+ # @return [ActionView::Base]
98
+ def view_context
99
+ self.oprah_view_context = super
100
+ end
101
+
102
+ private
103
+
104
+ # @since 0.1.3
105
+ def oprah_view_context_proxy
106
+ @oprah_view_context_proxy ||= ViewContextProxy.new(self)
107
+ end
108
+ end
109
+ end