strong_presenter 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -2
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +31 -0
- data/Gemfile +22 -2
- data/Guardfile +26 -0
- data/README.md +210 -52
- data/Rakefile +77 -1
- data/gemfiles/3.0.gemfile +2 -0
- data/gemfiles/3.1.gemfile +2 -0
- data/gemfiles/3.2.gemfile +2 -0
- data/gemfiles/4.0.gemfile +2 -0
- data/gemfiles/4.1.gemfile +2 -0
- data/lib/generators/controller_override.rb +15 -0
- data/lib/generators/mini_test/presenter_generator.rb +20 -0
- data/lib/generators/mini_test/templates/presenter_spec.rb +4 -0
- data/lib/generators/mini_test/templates/presenter_test.rb +4 -0
- data/lib/generators/rails/presenter_generator.rb +36 -0
- data/lib/generators/rails/templates/presenter.rb +19 -0
- data/lib/generators/rspec/presenter_generator.rb +9 -0
- data/lib/generators/rspec/templates/presenter_spec.rb +4 -0
- data/lib/generators/test_unit/presenter_generator.rb +9 -0
- data/lib/generators/test_unit/templates/presenter_test.rb +4 -0
- data/lib/strong_presenter/associable.rb +78 -0
- data/lib/strong_presenter/collection_presenter.rb +90 -0
- data/lib/strong_presenter/controller_additions.rb +50 -0
- data/lib/strong_presenter/delegation.rb +18 -0
- data/lib/strong_presenter/factory.rb +74 -0
- data/lib/strong_presenter/helper_proxy.rb +29 -11
- data/lib/strong_presenter/inferrer.rb +54 -0
- data/lib/strong_presenter/permissible.rb +73 -0
- data/lib/strong_presenter/permissions.rb +138 -0
- data/lib/strong_presenter/presenter.rb +191 -0
- data/lib/strong_presenter/presenter_association.rb +29 -0
- data/lib/strong_presenter/presenter_helper_constructor.rb +60 -0
- data/lib/strong_presenter/railtie.rb +27 -3
- data/lib/strong_presenter/tasks/test.rake +22 -0
- data/lib/strong_presenter/test/devise_helper.rb +30 -0
- data/lib/strong_presenter/test/minitest_integration.rb +6 -0
- data/lib/strong_presenter/test/rspec_integration.rb +16 -0
- data/lib/strong_presenter/test_case.rb +53 -0
- data/lib/strong_presenter/version.rb +1 -1
- data/lib/strong_presenter/view_context/build_strategy.rb +48 -0
- data/lib/strong_presenter/view_context.rb +84 -0
- data/lib/strong_presenter/view_helpers.rb +39 -0
- data/lib/strong_presenter.rb +64 -2
- data/spec/dummy/.rspec +2 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/dummy/app/controllers/localized_urls.rb +5 -0
- data/spec/dummy/app/controllers/posts_controller.rb +25 -0
- data/spec/dummy/app/helpers/application_helper.rb +5 -0
- data/spec/dummy/app/mailers/application_mailer.rb +3 -0
- data/spec/dummy/app/mailers/post_mailer.rb +19 -0
- data/spec/dummy/app/models/admin.rb +5 -0
- data/spec/dummy/app/models/post.rb +3 -0
- data/spec/dummy/app/models/user.rb +5 -0
- data/spec/dummy/app/presenters/post_presenter.rb +69 -0
- data/spec/dummy/app/presenters/special_post_presenter.rb +5 -0
- data/spec/dummy/app/presenters/special_posts_presenter.rb +5 -0
- data/spec/dummy/app/views/layouts/application.html.erb +11 -0
- data/spec/dummy/app/views/post_mailer/presented_email.html.erb +1 -0
- data/spec/dummy/app/views/posts/_post.html.erb +50 -0
- data/spec/dummy/app/views/posts/show.html.erb +1 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/config/application.rb +70 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +33 -0
- data/spec/dummy/config/environments/production.rb +57 -0
- data/spec/dummy/config/environments/test.rb +31 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +8 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +9 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
- data/spec/dummy/db/schema.rb +21 -0
- data/spec/dummy/db/seeds.rb +2 -0
- data/spec/dummy/fast_spec/post_presenter_spec.rb +37 -0
- data/spec/dummy/lib/tasks/test.rake +16 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/spec/fast_spec_helper.rb +13 -0
- data/spec/dummy/spec/mailers/post_mailer_spec.rb +33 -0
- data/spec/dummy/spec/models/post_spec.rb +4 -0
- data/spec/dummy/spec/presenters/active_model_serializers_spec.rb +11 -0
- data/spec/dummy/spec/presenters/devise_spec.rb +64 -0
- data/spec/dummy/spec/presenters/helpers_spec.rb +21 -0
- data/spec/dummy/spec/presenters/post_presenter_spec.rb +66 -0
- data/spec/dummy/spec/presenters/spec_type_spec.rb +7 -0
- data/spec/dummy/spec/presenters/special_post_presenter_spec.rb +11 -0
- data/spec/dummy/spec/presenters/view_context_spec.rb +22 -0
- data/spec/dummy/spec/spec_helper.rb +19 -0
- data/spec/dummy/test/minitest_helper.rb +2 -0
- data/spec/dummy/test/presenters/minitest/devise_test.rb +64 -0
- data/spec/dummy/test/presenters/minitest/helpers_test.rb +21 -0
- data/spec/dummy/test/presenters/minitest/spec_type_test.rb +52 -0
- data/spec/dummy/test/presenters/minitest/view_context_test.rb +24 -0
- data/spec/dummy/test/presenters/test_unit/devise_test.rb +64 -0
- data/spec/dummy/test/presenters/test_unit/helpers_test.rb +21 -0
- data/spec/dummy/test/presenters/test_unit/view_context_test.rb +24 -0
- data/spec/dummy/test/test_helper.rb +13 -0
- data/spec/generators/presenters/presenter_generator_spec.rb +131 -0
- data/spec/generators/simplecov_spec.rb +5 -0
- data/spec/integration/integration_spec.rb +81 -0
- data/spec/integration/simplecov_spec.rb +4 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/strong_presenter/associable_spec.rb +122 -0
- data/spec/strong_presenter/collection_presenter_spec.rb +34 -0
- data/spec/strong_presenter/delegation_spec.rb +20 -0
- data/spec/strong_presenter/permissible_spec.rb +24 -0
- data/spec/strong_presenter/permissions_spec.rb +188 -0
- data/spec/strong_presenter/presenter_spec.rb +43 -0
- data/spec/strong_presenter/simplecov_spec.rb +4 -0
- data/spec/support/dummy_app.rb +85 -0
- data/spec/support/matchers/have_text.rb +50 -0
- data/spec/support/models.rb +14 -0
- data/spec/support/schema.rb +12 -0
- data/strong_presenter.gemspec +15 -0
- metadata +392 -13
- data/lib/strong_presenter/base.rb +0 -217
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1a4c71461c442ca200ed884ad386a15e9ff45ef5
|
4
|
+
data.tar.gz: 33fdccd699136c74dcaf5d05d26ae389b068820f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 18500c434c142b6308f5dbcc54917cec4cbbc0abfba72fd5afcb0f630c6c1503ea5ee3fae333581b734c24244b217b6c1ba6c297fd1ff13a14abc0c55c58b1cb
|
7
|
+
data.tar.gz: 193487ffad3116820af4096105461b882ffc506b179fd60bc99f262f41aac2419e95f07c8b72712dc5091144791663ba6f7f87b083ba4cdde1e181d4aab0df22
|
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
rvm:
|
4
|
+
- 1.9.3
|
5
|
+
- 2.0.0
|
6
|
+
- rbx-19mode
|
7
|
+
- jruby-19mode
|
8
|
+
|
9
|
+
env:
|
10
|
+
- "RAILS_VERSION=4.0"
|
11
|
+
- "RAILS_VERSION=3.2"
|
12
|
+
- "RAILS_VERSION=3.1"
|
13
|
+
- "RAILS_VERSION=3.0"
|
14
|
+
- "RAILS_VERSION=4.1"
|
15
|
+
|
16
|
+
matrix:
|
17
|
+
allow_failures:
|
18
|
+
- env: "RAILS_VERSION=4.1"
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
yardoc 'lib/strong_presenter/**/*.rb' -m markdown --no-private
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# StrongPresenter Changelog
|
2
|
+
|
3
|
+
## 0.2.0 In development
|
4
|
+
|
5
|
+
Changes to be made:
|
6
|
+
|
7
|
+
- Remove permissions grouping - convert to copy on write
|
8
|
+
- Remove permit_all! class method
|
9
|
+
- Require explicit wildcard endings to attribute paths to permit everything `[:association, :nested_association, :*]` to permit all methods in the association
|
10
|
+
|
11
|
+
## 0.1.0 - Stable
|
12
|
+
|
13
|
+
- Copied features from Draper gem (thanks):
|
14
|
+
- spec/spec_helper.rb, spec/integration, spec/dummy - much easier for me, since I have difficulties trying to get a dummy app working.
|
15
|
+
- ViewContext, HelperProxy - compared to my HelperProxy trying to include various rails modules, this exposes methods like `current_user` as well.
|
16
|
+
- test integration - ties in with ViewContext.
|
17
|
+
- Gemfile, .travis.yml, .yardopts, Rakefile, Guardfile, .rspec, tasks
|
18
|
+
- Generators
|
19
|
+
- Features derived from the Draper gem with substantial modifications
|
20
|
+
- factory - remove many options, some simplifications.
|
21
|
+
- DecoratesAssigned -> ControllerAdditions - :only, :except options, execute block instead of passing :context-like options.
|
22
|
+
- CollectionDecorator -> CollectionPresenter
|
23
|
+
- Associations -> Associable
|
24
|
+
- Other features added
|
25
|
+
- *Presenter::Collection - constant automatically added which points to the corresponding collection presenter. If this does not exist, a subclass of StrongPresenter::CollectionPresenter is dynamically created.
|
26
|
+
- Permissible, Permissions - `permit`, `permit!`, `filter` interface separated into a separate Permissible module. The Permissions class just stores attributes that have been permitted
|
27
|
+
|
28
|
+
## 0.0.1
|
29
|
+
|
30
|
+
- Allows presenters in `app/presenters` to wrap models and expose a read-only interface.
|
31
|
+
- `permit` interface to mark attributes with a `presents` method on each presenter, which yields the value of permitted attributes to a block.
|
data/Gemfile
CHANGED
@@ -1,4 +1,24 @@
|
|
1
|
-
source
|
1
|
+
source "https://rubygems.org"
|
2
2
|
|
3
|
-
# Specify your gem's dependencies in strong_presenter.gemspec
|
4
3
|
gemspec
|
4
|
+
|
5
|
+
gem 'debugger', platforms: :mri_19
|
6
|
+
gem 'byebug', platforms: :mri_20
|
7
|
+
|
8
|
+
platforms :mri_19, :mri_20 do
|
9
|
+
gem 'simplecov', require: false
|
10
|
+
gem 'coveralls', require: false
|
11
|
+
end
|
12
|
+
|
13
|
+
platforms :ruby do
|
14
|
+
gem "sqlite3"
|
15
|
+
end
|
16
|
+
|
17
|
+
platforms :jruby do
|
18
|
+
gem "minitest", ">= 3.0"
|
19
|
+
gem "activerecord-jdbcsqlite3-adapter", ">= 1.3.0.beta2"
|
20
|
+
end
|
21
|
+
|
22
|
+
version = ENV["RAILS_VERSION"] || "4.0"
|
23
|
+
|
24
|
+
eval_gemfile File.expand_path("../gemfiles/#{version}.gemfile", __FILE__)
|
data/Guardfile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
def rspec_guard(options = {}, &block)
|
2
|
+
options = {
|
3
|
+
:version => 2,
|
4
|
+
:notification => false
|
5
|
+
}.merge(options)
|
6
|
+
|
7
|
+
guard 'rspec', options, &block
|
8
|
+
end
|
9
|
+
|
10
|
+
rspec_guard :spec_paths => %w{spec/strong_presenter spec/generators} do
|
11
|
+
watch(%r{^spec/.+_spec\.rb$})
|
12
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
13
|
+
watch('spec/spec_helper.rb') { "spec" }
|
14
|
+
end
|
15
|
+
|
16
|
+
rspec_guard :spec_paths => 'spec/integration', :env => {'RAILS_ENV' => 'development'} do
|
17
|
+
watch(%r{^spec/.+_spec\.rb$})
|
18
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
19
|
+
watch('spec/spec_helper.rb') { "spec" }
|
20
|
+
end
|
21
|
+
|
22
|
+
rspec_guard :spec_paths => 'spec/integration', :env => {'RAILS_ENV' => 'production'} do
|
23
|
+
watch(%r{^spec/.+_spec\.rb$})
|
24
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
25
|
+
watch('spec/spec_helper.rb') { "spec" }
|
26
|
+
end
|
data/README.md
CHANGED
@@ -1,20 +1,31 @@
|
|
1
1
|
# StrongPresenter
|
2
2
|
|
3
|
-
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/strong_presenter.png)](http://badge.fury.io/rb/strong_presenter)
|
4
|
+
[![Build Status](https://travis-ci.org/ronalchn/strong_presenter.png?branch=master)](https://travis-ci.org/ronalchn/strong_presenter)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/ronalchn/strong_presenter/badge.png)](https://coveralls.io/r/ronalchn/strong_presenter)
|
6
|
+
[![Code Climate](https://codeclimate.com/github/ronalchn/strong_presenter.png)](https://codeclimate.com/github/ronalchn/strong_presenter)
|
4
7
|
|
5
|
-
|
8
|
+
StrongPresenter adds a layer of presentation logic to your application. The presenters also give you a strong_parameters-like syntax to specify what attributes can be read, helping you push authorization logic from the view layer back to the controller layer. The view layer can be dumber, and concentrate on page layout rather than authorization logic flow.
|
6
9
|
|
7
|
-
|
10
|
+
A number of features have been copied from Draper and refined.
|
8
11
|
|
9
|
-
|
12
|
+
## Why use Presenters?
|
10
13
|
|
11
|
-
|
14
|
+
Presenters deal with presentation, so your models can concentrate on domain logic. Instead of using a helper methods, you can implement the method in a presenter instead.
|
15
|
+
|
16
|
+
Others have used decorators for this purpose - while they can be used for presentation logic, it may cause some confusion in your application. Presenters are designed to help you to present - a market-oriented solution. Decorators are a coding pattern which can be used for presentation - a product-oriented solution.
|
17
|
+
|
18
|
+
The decorator coding pattern involves using delegation to wrap the model, adding new behaviour to the base object, possibly overriding some methods to present data in a visually appealing way. The decorator pattern not only wraps an object, but also involves keeping the decorated object acting like an instance of the base object (allowing multiple redecorations) - but this is not really what you want for presentation.
|
19
|
+
|
20
|
+
When we consider presentation, we are interested in reading the information, not further mutating the object, so we want to hide attribute setters, or other domain logic. We are also not interested in wrapping multiple layers around the object (we can use multiple still use presenters of the same object). If we want to share presentation logic between different models, we should instead use one presenter per model, including various behaviour using mixins.
|
21
|
+
|
22
|
+
While there exist other gems for presentation, we hope to provide a more natural interface to create your presenters.
|
12
23
|
|
13
24
|
## Installation
|
14
25
|
|
15
26
|
Requires Rails. Rails 3.2+ is supported (probably works on 3.0, 3.1 as well).
|
16
27
|
|
17
|
-
Add this line to your application's Gemfile:
|
28
|
+
Add this line to your application''s Gemfile:
|
18
29
|
|
19
30
|
gem 'strong_presenter'
|
20
31
|
|
@@ -26,106 +37,184 @@ Or install it yourself as:
|
|
26
37
|
|
27
38
|
$ gem install strong_presenter
|
28
39
|
|
40
|
+
Or to use the edge version, add this to your Gemfile:
|
41
|
+
|
42
|
+
gem 'strong_presenter', :github => 'ronalchn/strong_presenter'
|
43
|
+
|
29
44
|
## Usage
|
30
45
|
|
31
|
-
|
46
|
+
### Writing Presenters
|
47
|
+
|
48
|
+
Presenters are stored in `app/presenters`, they inherit from `StrongPresenter::BasePresenter` and are named based on the model they decorate. We also recommend you create an `ApplicationPresenter`.
|
32
49
|
|
33
|
-
```ruby
|
34
|
-
|
50
|
+
```ruby
|
51
|
+
# app/presenters/user_presenter.rb
|
52
|
+
class UserPresenter < ApplicationPresenter
|
53
|
+
# ...
|
35
54
|
end
|
55
|
+
```
|
56
|
+
|
57
|
+
### Accessing the Model and Helpers
|
36
58
|
|
59
|
+
As shown below, rails helpers can be accessed through `h`. You can access the model using the `object` method, or the model name (in this case `user`). For example:
|
60
|
+
|
61
|
+
```ruby
|
37
62
|
class UserPresenter < ApplicationPresenter
|
38
63
|
presents :user
|
39
|
-
delegate :username, :name, :email, to: :user
|
40
|
-
|
41
64
|
def avatar
|
42
65
|
h.tag :img, :src => user.avatar_url
|
43
66
|
end
|
44
67
|
end
|
45
68
|
```
|
46
69
|
|
47
|
-
|
70
|
+
The model name is either inferred from the class name - taking `UserPresenter`, and converting the part before "Presenter" to lower case with underscores between each word, or it can be set using the `presents` method as shown above.
|
71
|
+
|
72
|
+
### Delegating Methods
|
73
|
+
|
74
|
+
If no changes are necessary, a method can be delegated to the model. The `:to` option defaults to `object`.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
class UserPresenter < ApplicationPresenter
|
78
|
+
delegate :username, :email
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
### Wrapping Models with Presenters
|
83
|
+
|
84
|
+
#### Single Objects
|
85
|
+
|
86
|
+
Just pass the model to a new presenter instance:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
@user_presenter = UserPresenter.new(@user)
|
90
|
+
```
|
91
|
+
|
92
|
+
#### Collections
|
93
|
+
|
94
|
+
Pass the model to a corresponding collection presenter:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
@users_presenter = UserPresenter::Collection.new(@users)
|
98
|
+
```
|
99
|
+
|
100
|
+
To add methods to your collection presenter, inherit from `StrongPresenter::CollectionPresenter`:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
# app/presenters/users_presenter.rb
|
104
|
+
class UsersPresenter < StrongPresenter::CollectionPresenter
|
105
|
+
def pages
|
106
|
+
(collection.size / 20).ceil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
It wraps each item in the collection with the corresponding singular presenter inferred from the class name, but it can be set using the `:with` option in the constructor, or by calling `presents_with :user` (for example), in the class definition.
|
112
|
+
|
113
|
+
### Model Associations
|
114
|
+
|
115
|
+
To automatically wrap associations with a presenter, use `presents_association` or `presents_associations`:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
# app/presenters/users_presenter.rb
|
119
|
+
class UsersPresenter < StrongPresenter::CollectionPresenter
|
120
|
+
presents_association :comments
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
A specific presenter can be specified using the `:with` option, otherwise it is inferred from the association. A scope can be specified using the `:scope` option. It can also yield the new presenter to a block.
|
125
|
+
|
126
|
+
### When to Wrap the Model with the Presenter
|
127
|
+
|
128
|
+
You will normally want to wrap the model with the presenter at the end of your controller action:
|
48
129
|
|
49
130
|
```ruby
|
50
131
|
class UserController < ApplicationController
|
51
132
|
def show
|
52
133
|
@user = User.find(params[:id])
|
53
|
-
@user_presenter = UserPresenter.
|
134
|
+
@user_presenter = UserPresenter.new(@user)
|
54
135
|
end
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
But you can use the `presents` method to add a helper method that returns the presenter lazily:
|
55
140
|
|
56
|
-
|
57
|
-
|
58
|
-
|
141
|
+
```ruby
|
142
|
+
class UserController < ApplicationController
|
143
|
+
presents :user, with: UserPresenter, only: :show
|
144
|
+
def show
|
145
|
+
@user = User.find(params[:id])
|
59
146
|
end
|
60
147
|
end
|
61
148
|
```
|
62
149
|
|
63
|
-
|
150
|
+
If the `:with` option is not passed, it will be inferred by using the model class name. `:only` and `:except` sets the controller actions it applies to.
|
151
|
+
|
152
|
+
### Permit!
|
153
|
+
|
154
|
+
You can simply use the presenter in the view:
|
64
155
|
|
65
156
|
```ruby
|
66
157
|
Avatar: <%= @user_presenter.avatar %>
|
67
158
|
```
|
68
159
|
|
69
|
-
|
70
|
-
does not include name or email, they will not show. The advantage is that authorization logic
|
71
|
-
remains in the controller where it arguably belongs.
|
160
|
+
However, sometimes you will want to display information only if it is permitted. To do this, simply pass the attribute symbols to the `presents` method on the presenter, and it will only display it if it is permitted. This is especially powerful because it controls the display of not just the attribute value, but everything that is passed in the block.
|
72
161
|
|
73
162
|
```erb
|
74
|
-
<%
|
75
|
-
|
163
|
+
<% fields = { :username => "Username", :name => "Name", :email => "E-mail" } %>
|
164
|
+
<% user.presents *fields.keys do |key, value| # user = @user_presenter because of the call to `presents` in the controller class %>
|
165
|
+
<b><%= fields[key] %>:</b> <%= value %><br>
|
76
166
|
<% end %>
|
77
167
|
```
|
78
168
|
|
79
|
-
The `
|
80
|
-
such as `avatar` on the presenter will not check if the attribute is permitted. To check this, the `presents`
|
81
|
-
method can be used. For example:
|
169
|
+
The `present` method is also available to display a single attribute:
|
82
170
|
|
83
171
|
```erb
|
84
|
-
|
85
|
-
|
172
|
+
<b>Hello <%= user.present :name %></b><br>
|
173
|
+
<% user.present :username do |value| %>
|
174
|
+
<b>Username:</b> <%= value %><br>
|
86
175
|
<% end %>
|
87
176
|
```
|
88
177
|
|
89
|
-
|
90
|
-
from mass presentations, simply call `permit!` on an instance of a presenter, or on the class (to disable for all instances).
|
91
|
-
|
92
|
-
Fields or attributes can be assigned a label, by calling `label` with a hash.
|
178
|
+
To permit the display of the attributes, call `permit` on the presenter with the attribute symbols in the controller.
|
93
179
|
|
94
180
|
```ruby
|
95
|
-
class
|
96
|
-
|
97
|
-
|
181
|
+
class UserController < ApplicationController
|
182
|
+
def show
|
183
|
+
@user = User.find(params[:id])
|
184
|
+
@user_presenter = UserPresenter.new(@user).permit :username, :name # but not :email
|
185
|
+
end
|
98
186
|
end
|
99
187
|
```
|
100
188
|
|
101
|
-
|
189
|
+
You can also call it when using the `presents` method in the controller:
|
102
190
|
|
103
191
|
```ruby
|
104
|
-
|
192
|
+
class UserController < ApplicationController
|
193
|
+
presents :user do |presenter|
|
194
|
+
presenter.permit :username, :name
|
195
|
+
presenter.permit :email if current_user.admin?
|
196
|
+
end
|
197
|
+
def show
|
198
|
+
@user = User.find(params[:id])
|
199
|
+
end
|
200
|
+
end
|
105
201
|
```
|
106
202
|
|
107
|
-
|
203
|
+
To remove authorization checks, simply call `permit!` on an instance of a presenter.
|
108
204
|
|
109
|
-
|
110
|
-
a collection using `wrap_each`:
|
111
|
-
|
112
|
-
```ruby
|
113
|
-
@users_presenter = UserPresenter.wrap_each(@users).permit( *visible_attributes )
|
114
|
-
```
|
115
|
-
|
116
|
-
Then the view can use each presenter, to display only the columns a user is permitted to view.
|
205
|
+
There is also a `filter` method to help you with tables:
|
117
206
|
|
118
207
|
```erb
|
119
|
-
<% fields =
|
208
|
+
<% fields = { :username => "Username", :name => "Name", :email => "E-mail" } %>
|
120
209
|
<table>
|
121
210
|
<tr>
|
122
|
-
<% @users_presenter.filter( *fields )
|
123
|
-
<th><%=
|
211
|
+
<% @users_presenter.filter( *fields.keys ) do |key| %>
|
212
|
+
<th><%= fields[key] %></th>
|
124
213
|
<% end %>
|
125
214
|
</tr>
|
126
215
|
<% @users_presenter.each do |user_presenter| %>
|
127
216
|
<tr>
|
128
|
-
<% user_presenter.presents( *fields ).each do |value| %>
|
217
|
+
<% user_presenter.presents( *fields.keys ).each do |value| %>
|
129
218
|
<%= content_tag :td, value %>
|
130
219
|
<% end %>
|
131
220
|
</tr>
|
@@ -134,16 +223,85 @@ Then the view can use each presenter, to display only the columns a user is perm
|
|
134
223
|
```
|
135
224
|
|
136
225
|
Here, we use filter to check which of the columns are visible, just like `presents` does. It returns an
|
137
|
-
array of only the visible columns, and
|
226
|
+
array of only the visible columns, and we use our `fields` hash to label it.
|
138
227
|
|
139
|
-
|
228
|
+
We can decide what attributes to present based on a GET parameter input, for example:
|
140
229
|
|
141
230
|
```erb
|
142
|
-
<% user_presenter.presents( params[:columns].split(',') ).each do |value|
|
231
|
+
<% @user_presenter.presents( params[:columns].split(',') ).each do |value| %>
|
232
|
+
<%= content_tag :td, value %>
|
233
|
+
<% end %>
|
143
234
|
```
|
144
235
|
|
145
236
|
Because of the `permit` checks, there is no danger that private information will be revealed.
|
146
237
|
|
238
|
+
#### Permissions Paths
|
239
|
+
|
240
|
+
Association methods can be permitted by passing an array.
|
241
|
+
|
242
|
+
```rb
|
243
|
+
@article_presenter.permit :body, [:author, :name]
|
244
|
+
```
|
245
|
+
|
246
|
+
Then, presenting it:
|
247
|
+
|
248
|
+
```erb
|
249
|
+
<%= @article_presenter.present [:author, :name] %>
|
250
|
+
```
|
251
|
+
|
252
|
+
is equivalent to `@article_presenter.author.name`, except it includes the permission check.
|
253
|
+
|
254
|
+
#### Permissions Groups
|
255
|
+
|
256
|
+
Currently, each group of presenters shares a single permissions object. Therefore, each element in a collection references the same permissions object. The presenter for each association also shares the same permissions object. This means that permitting a method will permit it for all presenters in the group, and it is not possible for two presenters in the same collection to have different methods permitted.
|
257
|
+
|
258
|
+
Everytime you get a presenter through a collection or association, it will be added to the permissions group. To start a new group, you will need to initialize it yourself.
|
259
|
+
|
260
|
+
It is the intention that in version 0.2.0, permissions groups (for efficiency in a simple implementation), will be removed, and each new presenter will implement copy on write with the permissions object. This will retain efficiency where `permit` is called early before forking new presenters, but allow different permissions for each presenter. This will change the behaviour of what is considered permitted, but if `permit` is called before using the presenter, the behaviour will not change.
|
261
|
+
|
262
|
+
### Testing
|
263
|
+
|
264
|
+
#### RSpec
|
265
|
+
|
266
|
+
The specs are placed in `spec/presenters`. Add `type: :presenter` if they are placed elsewhere.
|
267
|
+
|
268
|
+
#### Isolated Tests
|
269
|
+
|
270
|
+
In tests, a view context is built to access helper methods. By default, it will create an ApplicationController and then use its view context. If you are speeding up your test suite by testing each component in isolation, you can eliminate this dependency by putting the following in your spec_helper or similar:
|
271
|
+
|
272
|
+
```ruby
|
273
|
+
StrongPresenter::ViewContext.test_strategy :fast
|
274
|
+
```
|
275
|
+
|
276
|
+
In doing so, your presenters will no longer have access to your application''s helpers. If you need to selectively include such helpers, you can pass a block:
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
StrongPresenter::ViewContext.test_strategy :fast do
|
280
|
+
include ApplicationHelper
|
281
|
+
end
|
282
|
+
```
|
283
|
+
|
284
|
+
### Generating Presenters
|
285
|
+
|
286
|
+
With StrongPresenter installed:
|
287
|
+
|
288
|
+
```sh
|
289
|
+
rails generate resource Article
|
290
|
+
```
|
291
|
+
|
292
|
+
will include a presenter.
|
293
|
+
|
294
|
+
To generate a presenter by itself:
|
295
|
+
|
296
|
+
```sh
|
297
|
+
rails generate presenter Article
|
298
|
+
```
|
299
|
+
|
300
|
+
## Acknowledgements
|
301
|
+
|
302
|
+
- [Draper](https://github.com/drapergem/draper) - a number of features from this gem have been copied and refined. Thanks!
|
303
|
+
- https://github.com/railscasts/287-presenters-from-scratch/
|
304
|
+
|
147
305
|
## License
|
148
306
|
|
149
307
|
Mozilla Public License Version 2.0
|
data/Rakefile
CHANGED
@@ -1 +1,77 @@
|
|
1
|
-
require
|
1
|
+
require 'rake'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
def run_in_dummy_app(command)
|
6
|
+
success = system("cd spec/dummy && #{command}")
|
7
|
+
raise "#{command} failed" unless success
|
8
|
+
end
|
9
|
+
|
10
|
+
task "default" => "ci"
|
11
|
+
|
12
|
+
desc "Run all tests for CI"
|
13
|
+
citask = ["spec"]
|
14
|
+
begin
|
15
|
+
require 'coveralls/rake/task'
|
16
|
+
Coveralls::RakeTask.new
|
17
|
+
citask << "coveralls:push" if defined? 'Coveralls'
|
18
|
+
rescue LoadError
|
19
|
+
end
|
20
|
+
task "ci" => citask
|
21
|
+
|
22
|
+
desc "Run all specs"
|
23
|
+
task "spec" => "spec:all"
|
24
|
+
|
25
|
+
namespace "spec" do
|
26
|
+
task "all" => ["strong_presenter", "generators", "integration"]
|
27
|
+
|
28
|
+
def spec_task(name)
|
29
|
+
desc "Run #{name} specs"
|
30
|
+
RSpec::Core::RakeTask.new(name) do |t|
|
31
|
+
t.pattern = "spec/#{name}/**/*_spec.rb"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
spec_task "strong_presenter"
|
36
|
+
spec_task "generators"
|
37
|
+
|
38
|
+
desc "Run integration specs"
|
39
|
+
task "integration" => ["db:setup", "integration:all"]
|
40
|
+
|
41
|
+
namespace "integration" do
|
42
|
+
task "all" => ["development", "production", "test"]
|
43
|
+
|
44
|
+
["development", "production"].each do |environment|
|
45
|
+
task environment do
|
46
|
+
Rake::Task["spec:integration:run"].execute environment
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
task "run" do |t, environment|
|
51
|
+
puts "Running integration specs in #{environment}"
|
52
|
+
|
53
|
+
ENV["RAILS_ENV"] = environment
|
54
|
+
success = system("rspec spec/integration")
|
55
|
+
|
56
|
+
raise "Integration specs failed in #{environment}" unless success
|
57
|
+
end
|
58
|
+
|
59
|
+
task "test" do
|
60
|
+
puts "Running rake in dummy app"
|
61
|
+
ENV["RAILS_ENV"] = "test"
|
62
|
+
run_in_dummy_app "rake"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
namespace "db" do
|
68
|
+
desc "Set up databases for integration testing"
|
69
|
+
task "setup" do
|
70
|
+
puts "Setting up databases"
|
71
|
+
run_in_dummy_app "rm -f db/*.sqlite3"
|
72
|
+
run_in_dummy_app "RAILS_ENV=development rake db:schema:load db:seed"
|
73
|
+
run_in_dummy_app "RAILS_ENV=production rake db:schema:load db:seed"
|
74
|
+
run_in_dummy_app "RAILS_ENV=test rake db:schema:load"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
require "rails/generators/rails/controller/controller_generator"
|
3
|
+
require "rails/generators/rails/scaffold_controller/scaffold_controller_generator"
|
4
|
+
|
5
|
+
module Rails
|
6
|
+
module Generators
|
7
|
+
class ControllerGenerator
|
8
|
+
hook_for :presenter, default: true
|
9
|
+
end
|
10
|
+
|
11
|
+
class ScaffoldControllerGenerator
|
12
|
+
hook_for :presenter, default: true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'generators/mini_test'
|
2
|
+
|
3
|
+
module MiniTest
|
4
|
+
module Generators
|
5
|
+
class PresenterGenerator < Base
|
6
|
+
def self.source_root
|
7
|
+
File.expand_path('../templates', __FILE__)
|
8
|
+
end
|
9
|
+
|
10
|
+
class_option :spec, :type => :boolean, :default => false, :desc => "Use MiniTest::Spec DSL"
|
11
|
+
|
12
|
+
check_class_collision suffix: "PresenterTest"
|
13
|
+
|
14
|
+
def create_test_file
|
15
|
+
template_type = options[:spec] ? "spec" : "test"
|
16
|
+
template "presenter_#{template_type}.rb", File.join("test/presenters", class_path, "#{singular_name}_presenter_test.rb")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|