oprah 0.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 +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +15 -0
- data/CHANGELOG.md +4 -0
- data/CONTRIBUTING.md +86 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +243 -0
- data/Rakefile +5 -0
- data/lib/oprah/cache.rb +58 -0
- data/lib/oprah/controller_helpers.rb +46 -0
- data/lib/oprah/presenter.rb +138 -0
- data/lib/oprah/railtie.rb +17 -0
- data/lib/oprah/version.rb +4 -0
- data/lib/oprah.rb +43 -0
- data/oprah.gemspec +31 -0
- data/tasks/bundler.rb +5 -0
- data/tasks/console.rb +35 -0
- data/tasks/minitest.rb +8 -0
- data/tasks/yard.rb +16 -0
- data/test/helper.rb +63 -0
- data/test/oprah/controller_helpers_test.rb +65 -0
- data/test/oprah/oprah_test.rb +17 -0
- data/test/oprah/presenter_test.rb +107 -0
- metadata +169 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9338c79bdb8b1d23b379817b46c5c2ed1dbf07d1
|
4
|
+
data.tar.gz: 5af6034580ada342dfe5cd69e02f94dbb6deb622
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2810722883f201f4cea966612128a01f5173ea14c64fb5fd1a30982f944377af6494e5de264198c63d22b7d4d91d9c900d67f329d2bc7e7596c754082da31a3b
|
7
|
+
data.tar.gz: c7d1053a0023d28d258d9b397aac887b1f1718002824c7b8d4d6c742d6148317c2434fc758b54e602455cbf4438ab8aa29343d807ae501c9c256de00a3ab2f12
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
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
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,243 @@
|
|
1
|
+
# Oprah
|
2
|
+
|
3
|
+
Opinionated presenters for Rails 5 - without the cruft.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
* [Overview](#overview)
|
8
|
+
* [Installation](#installation)
|
9
|
+
* [Getting started](#getting-started)
|
10
|
+
+ [ActionController integration](#actioncontroller-integration)
|
11
|
+
* [Collections](#collections)
|
12
|
+
* [Associations](#associations)
|
13
|
+
* [Composition](#composition)
|
14
|
+
+ [Performance](#performance)
|
15
|
+
+ [Ordering](#ordering)
|
16
|
+
* [License](#license)
|
17
|
+
* [Author](#author)
|
18
|
+
|
19
|
+
## Overview
|
20
|
+
|
21
|
+
If you've ever worked on a sufficiently large Rails application you've probably
|
22
|
+
experienced the Rails helper mess first hand. Helper methods are annoying to
|
23
|
+
locate, hard to test and not terribly expressive.
|
24
|
+
|
25
|
+
So why another presenter/decorator library? Oprah was written with a few simple
|
26
|
+
goals in mind only covered partially (or not at all) by other gems:
|
27
|
+
|
28
|
+
- Lightweight
|
29
|
+
- Presenters should be easy to test
|
30
|
+
- No monkey patching :monkey::gun:
|
31
|
+
- Embrace convention over configuration
|
32
|
+
- First-class support for composition (modules and concerns)
|
33
|
+
|
34
|
+
## Installation
|
35
|
+
|
36
|
+
Add this line to your application's Gemfile:
|
37
|
+
|
38
|
+
``` ruby
|
39
|
+
gem 'oprah'
|
40
|
+
```
|
41
|
+
|
42
|
+
And then execute:
|
43
|
+
|
44
|
+
```
|
45
|
+
$ bundle
|
46
|
+
```
|
47
|
+
|
48
|
+
## Getting started
|
49
|
+
|
50
|
+
Oprah expects a single presenter for each of your classes or modules. If your
|
51
|
+
model is called `User` it will look for a class called `UserPresenter`:
|
52
|
+
|
53
|
+
``` ruby
|
54
|
+
class User
|
55
|
+
def first_name
|
56
|
+
"John"
|
57
|
+
end
|
58
|
+
|
59
|
+
def last_name
|
60
|
+
"Doe"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class UserPresenter < Oprah::Presenter
|
65
|
+
def name
|
66
|
+
"#{first_name} #{last_name}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
Oprah will figure out the presenters by itself so you don't have to instantiate
|
72
|
+
your presenter classes directly:
|
73
|
+
|
74
|
+
``` ruby
|
75
|
+
presenter = Oprah.present(User.new)
|
76
|
+
|
77
|
+
presenter.name
|
78
|
+
# => "John Doe"
|
79
|
+
|
80
|
+
```
|
81
|
+
|
82
|
+
Of course, all the regular methods on your model are still accessible:
|
83
|
+
|
84
|
+
``` ruby
|
85
|
+
presenter.first_name
|
86
|
+
# => "John"
|
87
|
+
```
|
88
|
+
|
89
|
+
If you *DO* want to use a specific presenter, you can simply instantiate it
|
90
|
+
yourself:
|
91
|
+
|
92
|
+
``` ruby
|
93
|
+
SomeOtherPresenter.new(User.new)
|
94
|
+
```
|
95
|
+
|
96
|
+
### ActionController integration
|
97
|
+
|
98
|
+
Now, where do we put our presenters? Ideally, you'd want to expose them in your
|
99
|
+
controller. Oprah avoids monkey patching and generally it's good to be aware of
|
100
|
+
what's going on, even if that means to be (at least a little bit) explicit.
|
101
|
+
|
102
|
+
Here's how you can use Oprah presenters from your controller:
|
103
|
+
|
104
|
+
``` ruby
|
105
|
+
class UserController < ApplicationController
|
106
|
+
def show
|
107
|
+
@user = present User.find(params[:id])
|
108
|
+
end
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
This will also take care of passing the correct view context to the presenter,
|
113
|
+
which you can access with the `#view_context` (or shorter, `#h`) instance
|
114
|
+
method.
|
115
|
+
|
116
|
+
## Collections
|
117
|
+
|
118
|
+
Oprah has basic support for collections with `.present_many`. It will simply
|
119
|
+
apply it's `.present` behavior to each object in the given collection:
|
120
|
+
|
121
|
+
``` ruby
|
122
|
+
users = [User.new, User.new]
|
123
|
+
presenters = Oprah.present_many(users)
|
124
|
+
|
125
|
+
presenters.first.kind_of?(UserPresenter)
|
126
|
+
# => true
|
127
|
+
|
128
|
+
presenters.last.kind_of?(UserPresenter)
|
129
|
+
# => true
|
130
|
+
```
|
131
|
+
|
132
|
+
Of course, this works in controllers, too:
|
133
|
+
|
134
|
+
``` ruby
|
135
|
+
class UserController < ApplicationController
|
136
|
+
def index
|
137
|
+
@users = present_many User.all
|
138
|
+
end
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
## Associations
|
143
|
+
|
144
|
+
You can also automatically use presenters for your associations using the
|
145
|
+
`#presents_one` and `#presents_many` macros. Let's say you have the following
|
146
|
+
`Project` model:
|
147
|
+
|
148
|
+
``` ruby
|
149
|
+
class Project
|
150
|
+
has_many :users
|
151
|
+
has_one :owner, class_name: "User"
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
Oprah lets you easily wrap the associated objects:
|
156
|
+
|
157
|
+
``` ruby
|
158
|
+
class ProjectPresenter < Oprah::Presenter
|
159
|
+
presents_many :users
|
160
|
+
presents_one :owner
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
164
|
+
Note that you don't need to explicitly state the association class.
|
165
|
+
|
166
|
+
## Composition
|
167
|
+
|
168
|
+
Let's say you extraced some behaviour out of your model into a reusable module (or
|
169
|
+
`ActiveSupport::Concern`). Oprah lets you write a single, separate presenter for
|
170
|
+
this module and automatically chains it to your "main presenter" by walking up the
|
171
|
+
ancestor chain of the given object.
|
172
|
+
|
173
|
+
Let's say we want to mix a shared `Describable` module into our `User` class from
|
174
|
+
above and render the description to HTML:
|
175
|
+
|
176
|
+
|
177
|
+
``` ruby
|
178
|
+
module Describable
|
179
|
+
def description
|
180
|
+
"*AWESOME*"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class User
|
185
|
+
include Describable
|
186
|
+
end
|
187
|
+
|
188
|
+
class DescribablePresenter < Oprah::Presenter
|
189
|
+
def description
|
190
|
+
Kramdown::Document.new(object.description).to_html
|
191
|
+
end
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
You can now access the methods of both, `UserPresenter` *and*
|
196
|
+
`DescribablePresenter`:
|
197
|
+
|
198
|
+
``` ruby
|
199
|
+
presenter = Oprah.present(User.new)
|
200
|
+
|
201
|
+
presenter.description
|
202
|
+
=> "<p><em>AWESOME</em></p>\n"
|
203
|
+
|
204
|
+
presenter.name
|
205
|
+
# => John Doe
|
206
|
+
```
|
207
|
+
|
208
|
+
### Performance
|
209
|
+
|
210
|
+
Of course, looking up all the presenters would imply a performance issue. But
|
211
|
+
don't worry, Oprah caches all matching presenters for a class (and busts it's
|
212
|
+
cache on code reloads for a smooth development experience).
|
213
|
+
|
214
|
+
### Ordering
|
215
|
+
|
216
|
+
Oprah walks your object's ancestor chain in reverse. For example, you'd be
|
217
|
+
able to access the methods exposed by the `DescribablePresenter` from your
|
218
|
+
`UserPresenter`. You can even use `super`:
|
219
|
+
|
220
|
+
``` ruby
|
221
|
+
class DescribablePresenter < Oprah::Presenter
|
222
|
+
def baz
|
223
|
+
"foo"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class UserPresenter < Oprah::Presenter
|
228
|
+
def baz
|
229
|
+
super + "bar"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
Oprah.present(User.new).baz
|
234
|
+
# => "foobar"
|
235
|
+
```
|
236
|
+
|
237
|
+
## License
|
238
|
+
|
239
|
+
Released under the MIT license. See the LICENSE file for details.
|
240
|
+
|
241
|
+
## Author
|
242
|
+
|
243
|
+
Tobias Svensson, [@endofunky](https://twitter.com/endofunky), [http://github.com/endofunky](http://github.com/endofunky)
|
data/Rakefile
ADDED
data/lib/oprah/cache.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Oprah
|
2
|
+
# A cache store to keep Object-to-Presenter mappings. This class is
|
3
|
+
# thread-safe.
|
4
|
+
class Cache
|
5
|
+
def initialize
|
6
|
+
@mutex = Mutex.new
|
7
|
+
@mapping = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
# Looks up presenters matching to `object` and stores them in the cache.
|
11
|
+
#
|
12
|
+
# @param object [Object] The presentable object
|
13
|
+
# @return [Array] An array of Presenter classes
|
14
|
+
def lookup(object)
|
15
|
+
@mutex.synchronize do
|
16
|
+
key = class_name_for(object)
|
17
|
+
|
18
|
+
if found = @mapping[key]
|
19
|
+
return found
|
20
|
+
end
|
21
|
+
|
22
|
+
@mapping[key] = presenter_classes_for(object)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Clears the presenter cache.
|
27
|
+
#
|
28
|
+
# @return [Boolean]
|
29
|
+
def clear!
|
30
|
+
@mutex.synchronize do
|
31
|
+
@mapping = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
Rails.logger.debug "Oprah cache cleared." if Oprah.debug?
|
35
|
+
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def presenter_classes_for(object)
|
42
|
+
class_for(object).ancestors.map do |klass|
|
43
|
+
begin
|
44
|
+
(klass.name + "Presenter").constantize
|
45
|
+
rescue
|
46
|
+
end
|
47
|
+
end.compact.reverse
|
48
|
+
end
|
49
|
+
|
50
|
+
def class_name_for(object)
|
51
|
+
class_for(object).name
|
52
|
+
end
|
53
|
+
|
54
|
+
def class_for(object)
|
55
|
+
object.kind_of?(Class) ? object : object.class
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Oprah
|
2
|
+
# Helpers that will be mixed into `ActionController::Base` by
|
3
|
+
# the {Oprah::Railtie}.
|
4
|
+
module ControllerHelpers
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
helper_method :present
|
9
|
+
helper_method :present_many
|
10
|
+
end
|
11
|
+
|
12
|
+
# Presents the given `object` using {Presenter.present}.
|
13
|
+
#
|
14
|
+
# Will pass the view context returned from {#oprah_view_context} to the
|
15
|
+
# presenter.
|
16
|
+
#
|
17
|
+
# @param object [Object] The object to present
|
18
|
+
# @param view_context [ActionView::Context] View context to assign
|
19
|
+
# @return [Presenter] Presented object
|
20
|
+
def present(object, view_context: oprah_view_context)
|
21
|
+
Oprah.present(object, view_context: view_context)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Presents the given `objects` using {Presenter.present}.
|
25
|
+
#
|
26
|
+
# Will pass the view context returned from {#oprah_view_context} to the
|
27
|
+
# presenter.
|
28
|
+
#
|
29
|
+
# @param objects [Enumerable] The objects to present
|
30
|
+
# @param view_context [ActionView::Context] View context to assign
|
31
|
+
# @return [Presenter] Presented object
|
32
|
+
def present_many(objects, view_context: oprah_view_context)
|
33
|
+
Oprah.present_many(objects, view_context: view_context)
|
34
|
+
end
|
35
|
+
|
36
|
+
# The view context automatically passed to presented objects.
|
37
|
+
#
|
38
|
+
# You can override this method pass a custom view context to all
|
39
|
+
# presented objects from the controller scope.
|
40
|
+
#
|
41
|
+
# @return [ActionView::Context]
|
42
|
+
def oprah_view_context
|
43
|
+
view_context
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Oprah
|
2
|
+
class Presenter
|
3
|
+
# @return [Object] The presented object
|
4
|
+
attr_reader :object
|
5
|
+
|
6
|
+
# @return [ActionView::Base] The view context
|
7
|
+
attr_reader :view_context
|
8
|
+
|
9
|
+
alias :h :view_context
|
10
|
+
|
11
|
+
# @!visibility private
|
12
|
+
@@cache = Oprah::Cache.new
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Returns the shared presenter cache object.
|
16
|
+
#
|
17
|
+
# @return [Cache]
|
18
|
+
def cache
|
19
|
+
@@cache
|
20
|
+
end
|
21
|
+
|
22
|
+
# Presents the given `object` with all it's matching presenters,
|
23
|
+
# following it's ancestors in reverse.
|
24
|
+
#
|
25
|
+
# @param object [Object] The object to present
|
26
|
+
# @param view_context [ActionView::Context] View context to assign
|
27
|
+
# @return [Presenter] Presented object
|
28
|
+
def present(object, view_context: default_view_context)
|
29
|
+
@@cache.lookup(object).inject(object) do |memo, presenter|
|
30
|
+
presenter.new(memo, view_context: view_context)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Presents the given `objects` with all their matching presenters.
|
35
|
+
# The individual behavior is the same as `.present`'s.
|
36
|
+
#
|
37
|
+
# @param objects [Enumerable] The objects to present
|
38
|
+
# @param view_context [ActionView::Context] View context to assign
|
39
|
+
# @return [Enumerable] Presented collection
|
40
|
+
def present_many(objects, view_context: default_view_context)
|
41
|
+
objects.map do |object|
|
42
|
+
present(object, view_context: view_context)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Automatically wrap the objects returned by the given one-to-one
|
47
|
+
# `association` method in presenters.
|
48
|
+
#
|
49
|
+
# @param association [Symbol] Name of the association
|
50
|
+
# @return [Boolean]
|
51
|
+
def presents_one(association)
|
52
|
+
define_method association do
|
53
|
+
self.class.present(
|
54
|
+
object.__send__(association), view_context: view_context)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Automatically wrap the objects returned by the given one-to-many
|
59
|
+
# or many-to-many `association` method in presenters.
|
60
|
+
#
|
61
|
+
# @param association [Symbol] Name of the association
|
62
|
+
# @return [Boolean]
|
63
|
+
def presents_many(association)
|
64
|
+
define_method association do
|
65
|
+
self.class.present_many(
|
66
|
+
object.__send__(association), view_context: view_context)
|
67
|
+
end
|
68
|
+
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the default view context to use if no view context is explicitly
|
73
|
+
# passed to the presenter.
|
74
|
+
#
|
75
|
+
# @return [ActionView::Context]
|
76
|
+
def default_view_context
|
77
|
+
ActionController::Base.new.view_context
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Initializes a new Presenter.
|
82
|
+
#
|
83
|
+
# @param object [Object] The object to present
|
84
|
+
# @param view_context [ActionView::Context] View context to assign
|
85
|
+
def initialize(object, view_context: self.class.default_view_context)
|
86
|
+
@object = object
|
87
|
+
@view_context = view_context
|
88
|
+
end
|
89
|
+
|
90
|
+
# Delegates all method calls not handled by the presenter to `object`.
|
91
|
+
def method_missing(meth, *args, &block)
|
92
|
+
if respond_to?(meth)
|
93
|
+
object.__send__(meth, *args, &block)
|
94
|
+
else
|
95
|
+
super
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns true if either `object` or `self` responds to the given method
|
100
|
+
# name.
|
101
|
+
#
|
102
|
+
# @param method [Symbol] Name of the method
|
103
|
+
# @param include_private [Boolean] Whether to include private methods
|
104
|
+
# @return [Boolean] result
|
105
|
+
def respond_to?(method, include_private = false)
|
106
|
+
super || object.respond_to?(method, include_private)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns true if `klass` is the class of `object` or the presenter, or
|
110
|
+
# if `#class` is one of the superclasses of `object`, the presenter or
|
111
|
+
# modules included in `object` or the presenter.
|
112
|
+
#
|
113
|
+
# @param other [Module]
|
114
|
+
# @return [Boolean] result
|
115
|
+
def kind_of?(other)
|
116
|
+
super || object.kind_of?(other)
|
117
|
+
end
|
118
|
+
|
119
|
+
alias :is_a? :kind_of?
|
120
|
+
|
121
|
+
# Returns `true` if `object` or the presenter is an instance of the given
|
122
|
+
# `class`.
|
123
|
+
#
|
124
|
+
# @param klass [Class]
|
125
|
+
# @return [Boolean] result
|
126
|
+
def instance_of?(klass)
|
127
|
+
super || object.instance_of?(klass)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns `true` if `object` or the presenter tests positive for equality.
|
131
|
+
#
|
132
|
+
# @param other [Object]
|
133
|
+
# @return [Boolean] result
|
134
|
+
def ==(other)
|
135
|
+
super || object == other
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rails/railtie'
|
2
|
+
|
3
|
+
module Oprah
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer "oprah.configure_cache_clear_on_code_reload" do
|
6
|
+
ActiveSupport::Reloader.to_run do
|
7
|
+
Oprah::Presenter.cache.clear!
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
initializer "oprah.configure_action_controller_helpers" do
|
12
|
+
ActiveSupport.on_load :action_controller do
|
13
|
+
ActionController::Base.include(Oprah::ControllerHelpers)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/oprah.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# stdlib
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
# gems
|
5
|
+
require 'active_support/concern'
|
6
|
+
require 'active_support/inflector'
|
7
|
+
require 'action_controller'
|
8
|
+
|
9
|
+
# internal
|
10
|
+
require 'oprah/cache'
|
11
|
+
require 'oprah/controller_helpers'
|
12
|
+
require 'oprah/presenter'
|
13
|
+
require 'oprah/version'
|
14
|
+
|
15
|
+
require 'oprah/railtie' if defined?(Rails)
|
16
|
+
|
17
|
+
# The Oprah namespace.
|
18
|
+
module Oprah
|
19
|
+
# @!visibility private
|
20
|
+
def debug?
|
21
|
+
!!ENV["OPRAH_DEBUG"]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Shortcut to {Oprah::Presenter#present}.
|
25
|
+
#
|
26
|
+
# @param object [Object] The object to present
|
27
|
+
# @param view_context [ActionView::Context] View context to assign
|
28
|
+
# @return [Presenter] Presented object
|
29
|
+
def present(object, view_context: Presenter.default_view_context)
|
30
|
+
Presenter.present(object, view_context: view_context)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Shortcut to {Presenter#present_many}.
|
34
|
+
#
|
35
|
+
# @param objects [Enumerable] The objects to present
|
36
|
+
# @param view_context [ActionView::Context] View context to assign
|
37
|
+
# @return [Enumerable] Presented collection
|
38
|
+
def present_many(objects, view_context: Presenter.default_view_context)
|
39
|
+
Presenter.present_many(objects, view_context: view_context)
|
40
|
+
end
|
41
|
+
|
42
|
+
extend self
|
43
|
+
end
|
data/oprah.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'oprah/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "oprah"
|
8
|
+
gem.version = Oprah::VERSION
|
9
|
+
gem.authors = ["Tobias Svensson"]
|
10
|
+
gem.email = ["tob@tobiassvensson.co.uk"]
|
11
|
+
gem.summary = "Opinionated presenters for Rails 5 - without the cruft"
|
12
|
+
gem.description = gem.summary
|
13
|
+
gem.homepage = "https://github.com/endofunky/oprah"
|
14
|
+
gem.license = "Apache License, Version 2.0"
|
15
|
+
|
16
|
+
gem.files = `git ls-files -z`.split("\x0")
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.has_rdoc = 'yard'
|
22
|
+
|
23
|
+
gem.add_dependency "activesupport", ">= 5.0.0"
|
24
|
+
gem.add_dependency "actionpack", ">= 5.0.0"
|
25
|
+
|
26
|
+
gem.add_development_dependency "yard", "~> 0.9.5"
|
27
|
+
gem.add_development_dependency "redcarpet", "~> 3.3.4"
|
28
|
+
gem.add_development_dependency "bundler", "~> 1.7"
|
29
|
+
gem.add_development_dependency "rake", "~> 10.0"
|
30
|
+
gem.add_development_dependency "minitest", "~> 5.5.1"
|
31
|
+
end
|
data/tasks/bundler.rb
ADDED
data/tasks/console.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
class RakeConsole
|
2
|
+
GEM = Dir["*.gemspec"].first.sub('.gemspec', '')
|
3
|
+
REQUIRE_PATH = File.join(Dir.pwd, 'lib', GEM)
|
4
|
+
|
5
|
+
module Helpers
|
6
|
+
def reload!
|
7
|
+
puts "Reloading..."
|
8
|
+
$LOADED_FEATURES.select do |feat|
|
9
|
+
feat =~ /\/#{GEM}\//
|
10
|
+
end.each { |file| load file }
|
11
|
+
true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def start
|
16
|
+
require REQUIRE_PATH
|
17
|
+
ARGV.clear
|
18
|
+
Object.include(Helpers)
|
19
|
+
|
20
|
+
begin
|
21
|
+
require 'pry'
|
22
|
+
TOPLEVEL_BINDING.pry
|
23
|
+
rescue LoadError
|
24
|
+
require 'irb'
|
25
|
+
require 'irb/completion'
|
26
|
+
IRB.start
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "Start development console"
|
32
|
+
task :console do
|
33
|
+
RakeConsole.new.start
|
34
|
+
end
|
35
|
+
|
data/tasks/minitest.rb
ADDED
data/tasks/yard.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'yard'
|
2
|
+
require 'yard/rake/yardoc_task'
|
3
|
+
|
4
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
5
|
+
t.files = ['lib/**/*.rb']
|
6
|
+
t.options = %w{
|
7
|
+
--verbose
|
8
|
+
--markup markdown
|
9
|
+
--readme README.md
|
10
|
+
--tag comment
|
11
|
+
--hide-tag comment
|
12
|
+
--hide-void-return
|
13
|
+
-M
|
14
|
+
redcarpet
|
15
|
+
}
|
16
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/pride'
|
3
|
+
require 'oprah'
|
4
|
+
|
5
|
+
module Fixtures
|
6
|
+
module Entity
|
7
|
+
end
|
8
|
+
|
9
|
+
class EntityPresenter < Oprah::Presenter
|
10
|
+
def foo
|
11
|
+
"foo"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class User
|
16
|
+
include Entity
|
17
|
+
|
18
|
+
def first_name
|
19
|
+
"Foo"
|
20
|
+
end
|
21
|
+
|
22
|
+
def last_name
|
23
|
+
"Bar"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def password
|
29
|
+
"baz"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class UserPresenter < Oprah::Presenter
|
34
|
+
def name
|
35
|
+
[first_name, last_name].join(' ')
|
36
|
+
end
|
37
|
+
|
38
|
+
def foo
|
39
|
+
super + "bar"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Comment
|
44
|
+
end
|
45
|
+
|
46
|
+
class CommentPresenter < Oprah::Presenter
|
47
|
+
end
|
48
|
+
|
49
|
+
class Project
|
50
|
+
def comments
|
51
|
+
Array.new(3) { Comment.new }
|
52
|
+
end
|
53
|
+
|
54
|
+
def owner
|
55
|
+
User.new
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class ProjectPresenter < Oprah::Presenter
|
60
|
+
presents_many :comments
|
61
|
+
presents_one :owner
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Oprah
|
4
|
+
class ControllerHelpersTest < Minitest::Test
|
5
|
+
class Controller
|
6
|
+
@@helper_methods = []
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def helper_methods
|
10
|
+
@@helper_methods
|
11
|
+
end
|
12
|
+
|
13
|
+
def helper_method(method)
|
14
|
+
@@helper_methods << method
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def view_context
|
19
|
+
:ok
|
20
|
+
end
|
21
|
+
|
22
|
+
include Oprah::ControllerHelpers
|
23
|
+
end
|
24
|
+
|
25
|
+
include Fixtures
|
26
|
+
|
27
|
+
def setup
|
28
|
+
super
|
29
|
+
@controller = Controller.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_present
|
33
|
+
presenter = @controller.present(User.new)
|
34
|
+
|
35
|
+
assert_kind_of UserPresenter, presenter
|
36
|
+
assert_kind_of EntityPresenter, presenter
|
37
|
+
|
38
|
+
assert_equal :ok, presenter.view_context
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_present
|
42
|
+
presenters = @controller.present_many([User.new, User.new])
|
43
|
+
|
44
|
+
assert_equal 2, presenters.length
|
45
|
+
|
46
|
+
presenters.each do |presenter|
|
47
|
+
assert_equal "Foo Bar", presenter.name
|
48
|
+
assert_equal :ok, presenter.view_context
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_present_custom_view_context
|
53
|
+
presenter = @controller.present(User.new, view_context: :foobar)
|
54
|
+
assert_equal :foobar, presenter.view_context
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_helper_method
|
58
|
+
assert_equal [:present, :present_many], Controller.helper_methods
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_oprah_view_context
|
62
|
+
assert_equal @controller.oprah_view_context, @controller.view_context
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Oprah
|
4
|
+
class Test < Minitest::Test
|
5
|
+
def test_debug
|
6
|
+
old = ENV["OPRAH_DEBUG"]
|
7
|
+
|
8
|
+
ENV["OPRAH_DEBUG"] = nil
|
9
|
+
refute Oprah.debug?
|
10
|
+
|
11
|
+
ENV["OPRAH_DEBUG"] = "1"
|
12
|
+
assert Oprah.debug?
|
13
|
+
|
14
|
+
ENV["OPRAH_DEBUG"] = old
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Oprah
|
4
|
+
class PresenterTest < Minitest::Test
|
5
|
+
include Fixtures
|
6
|
+
|
7
|
+
def test_cache
|
8
|
+
assert_kind_of Oprah::Cache, Presenter.cache
|
9
|
+
assert_equal Presenter.cache, Presenter.cache
|
10
|
+
assert_equal UserPresenter.cache, Presenter.cache
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_present_many
|
14
|
+
present_many([User.new, User.new]).each do |presenter|
|
15
|
+
assert_equal "Foo Bar", presenter.name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_presents_many
|
20
|
+
project = present(Project.new)
|
21
|
+
|
22
|
+
assert_equal 3, project.comments.length
|
23
|
+
|
24
|
+
project.comments.each do |comment|
|
25
|
+
assert_kind_of Comment, comment
|
26
|
+
assert_kind_of CommentPresenter, comment
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_presents_one
|
31
|
+
project = present(Project.new)
|
32
|
+
owner = project.owner
|
33
|
+
|
34
|
+
assert_kind_of UserPresenter, owner
|
35
|
+
assert_kind_of User, owner
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_presenter_stack_ordering
|
39
|
+
assert_equal "foobar", present(User.new).foo
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_default_view_context_using_present
|
43
|
+
presenter = Presenter.present(User.new)
|
44
|
+
assert_kind_of ActionView::Context, presenter.view_context
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_default_view_context_using_initialize
|
48
|
+
presenter = UserPresenter.new(User.new)
|
49
|
+
assert_kind_of ActionView::Context, presenter.view_context
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_method_missing_delegation
|
53
|
+
assert_equal "Foo Bar", present(User.new).name
|
54
|
+
assert_equal "Foo", present(User.new).first_name
|
55
|
+
assert_equal "Bar", present(User.new).last_name
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_method_missing_wont_delegate_to_private_methods
|
59
|
+
assert_raises NoMethodError do
|
60
|
+
present(User.new).password
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_kind_of?
|
65
|
+
presenter = present(User.new)
|
66
|
+
|
67
|
+
assert_kind_of User, presenter
|
68
|
+
assert_kind_of UserPresenter, presenter
|
69
|
+
assert_kind_of Entity, presenter
|
70
|
+
assert_kind_of EntityPresenter, presenter
|
71
|
+
refute_kind_of self.class, presenter
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_instance_of?
|
75
|
+
presenter = present(User.new)
|
76
|
+
|
77
|
+
assert_instance_of User, presenter
|
78
|
+
assert_instance_of UserPresenter, presenter
|
79
|
+
refute_instance_of Entity, presenter
|
80
|
+
assert_instance_of EntityPresenter, presenter
|
81
|
+
refute_instance_of self.class, presenter
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_equality
|
85
|
+
project = Project.new
|
86
|
+
project_presenter = Project.new
|
87
|
+
|
88
|
+
user = User.new
|
89
|
+
user_presenter = present(user)
|
90
|
+
|
91
|
+
assert_equal user_presenter, user
|
92
|
+
assert_equal user_presenter, user_presenter
|
93
|
+
refute_equal user_presenter, project
|
94
|
+
refute_equal user_presenter, project_presenter
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def present_many(*args, &block)
|
100
|
+
Oprah.present_many(*args, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
def present(*args, &block)
|
104
|
+
Oprah.present(*args, &block)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
metadata
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: oprah
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tobias Svensson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: actionpack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: yard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.9.5
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.9.5
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redcarpet
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.3.4
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.3.4
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.7'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.7'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: minitest
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 5.5.1
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 5.5.1
|
111
|
+
description: Opinionated presenters for Rails 5 - without the cruft
|
112
|
+
email:
|
113
|
+
- tob@tobiassvensson.co.uk
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- ".travis.yml"
|
120
|
+
- CHANGELOG.md
|
121
|
+
- CONTRIBUTING.md
|
122
|
+
- Gemfile
|
123
|
+
- LICENSE
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- lib/oprah.rb
|
127
|
+
- lib/oprah/cache.rb
|
128
|
+
- lib/oprah/controller_helpers.rb
|
129
|
+
- lib/oprah/presenter.rb
|
130
|
+
- lib/oprah/railtie.rb
|
131
|
+
- lib/oprah/version.rb
|
132
|
+
- oprah.gemspec
|
133
|
+
- tasks/bundler.rb
|
134
|
+
- tasks/console.rb
|
135
|
+
- tasks/minitest.rb
|
136
|
+
- tasks/yard.rb
|
137
|
+
- test/helper.rb
|
138
|
+
- test/oprah/controller_helpers_test.rb
|
139
|
+
- test/oprah/oprah_test.rb
|
140
|
+
- test/oprah/presenter_test.rb
|
141
|
+
homepage: https://github.com/endofunky/oprah
|
142
|
+
licenses:
|
143
|
+
- Apache License, Version 2.0
|
144
|
+
metadata: {}
|
145
|
+
post_install_message:
|
146
|
+
rdoc_options: []
|
147
|
+
require_paths:
|
148
|
+
- lib
|
149
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
requirements: []
|
160
|
+
rubyforge_project:
|
161
|
+
rubygems_version: 2.5.1
|
162
|
+
signing_key:
|
163
|
+
specification_version: 4
|
164
|
+
summary: Opinionated presenters for Rails 5 - without the cruft
|
165
|
+
test_files:
|
166
|
+
- test/helper.rb
|
167
|
+
- test/oprah/controller_helpers_test.rb
|
168
|
+
- test/oprah/oprah_test.rb
|
169
|
+
- test/oprah/presenter_test.rb
|