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.
Files changed (132) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -2
  3. data/.rspec +2 -0
  4. data/.travis.yml +18 -0
  5. data/.yardopts +1 -0
  6. data/CHANGELOG.md +31 -0
  7. data/Gemfile +22 -2
  8. data/Guardfile +26 -0
  9. data/README.md +210 -52
  10. data/Rakefile +77 -1
  11. data/gemfiles/3.0.gemfile +2 -0
  12. data/gemfiles/3.1.gemfile +2 -0
  13. data/gemfiles/3.2.gemfile +2 -0
  14. data/gemfiles/4.0.gemfile +2 -0
  15. data/gemfiles/4.1.gemfile +2 -0
  16. data/lib/generators/controller_override.rb +15 -0
  17. data/lib/generators/mini_test/presenter_generator.rb +20 -0
  18. data/lib/generators/mini_test/templates/presenter_spec.rb +4 -0
  19. data/lib/generators/mini_test/templates/presenter_test.rb +4 -0
  20. data/lib/generators/rails/presenter_generator.rb +36 -0
  21. data/lib/generators/rails/templates/presenter.rb +19 -0
  22. data/lib/generators/rspec/presenter_generator.rb +9 -0
  23. data/lib/generators/rspec/templates/presenter_spec.rb +4 -0
  24. data/lib/generators/test_unit/presenter_generator.rb +9 -0
  25. data/lib/generators/test_unit/templates/presenter_test.rb +4 -0
  26. data/lib/strong_presenter/associable.rb +78 -0
  27. data/lib/strong_presenter/collection_presenter.rb +90 -0
  28. data/lib/strong_presenter/controller_additions.rb +50 -0
  29. data/lib/strong_presenter/delegation.rb +18 -0
  30. data/lib/strong_presenter/factory.rb +74 -0
  31. data/lib/strong_presenter/helper_proxy.rb +29 -11
  32. data/lib/strong_presenter/inferrer.rb +54 -0
  33. data/lib/strong_presenter/permissible.rb +73 -0
  34. data/lib/strong_presenter/permissions.rb +138 -0
  35. data/lib/strong_presenter/presenter.rb +191 -0
  36. data/lib/strong_presenter/presenter_association.rb +29 -0
  37. data/lib/strong_presenter/presenter_helper_constructor.rb +60 -0
  38. data/lib/strong_presenter/railtie.rb +27 -3
  39. data/lib/strong_presenter/tasks/test.rake +22 -0
  40. data/lib/strong_presenter/test/devise_helper.rb +30 -0
  41. data/lib/strong_presenter/test/minitest_integration.rb +6 -0
  42. data/lib/strong_presenter/test/rspec_integration.rb +16 -0
  43. data/lib/strong_presenter/test_case.rb +53 -0
  44. data/lib/strong_presenter/version.rb +1 -1
  45. data/lib/strong_presenter/view_context/build_strategy.rb +48 -0
  46. data/lib/strong_presenter/view_context.rb +84 -0
  47. data/lib/strong_presenter/view_helpers.rb +39 -0
  48. data/lib/strong_presenter.rb +64 -2
  49. data/spec/dummy/.rspec +2 -0
  50. data/spec/dummy/Rakefile +7 -0
  51. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  52. data/spec/dummy/app/controllers/localized_urls.rb +5 -0
  53. data/spec/dummy/app/controllers/posts_controller.rb +25 -0
  54. data/spec/dummy/app/helpers/application_helper.rb +5 -0
  55. data/spec/dummy/app/mailers/application_mailer.rb +3 -0
  56. data/spec/dummy/app/mailers/post_mailer.rb +19 -0
  57. data/spec/dummy/app/models/admin.rb +5 -0
  58. data/spec/dummy/app/models/post.rb +3 -0
  59. data/spec/dummy/app/models/user.rb +5 -0
  60. data/spec/dummy/app/presenters/post_presenter.rb +69 -0
  61. data/spec/dummy/app/presenters/special_post_presenter.rb +5 -0
  62. data/spec/dummy/app/presenters/special_posts_presenter.rb +5 -0
  63. data/spec/dummy/app/views/layouts/application.html.erb +11 -0
  64. data/spec/dummy/app/views/post_mailer/presented_email.html.erb +1 -0
  65. data/spec/dummy/app/views/posts/_post.html.erb +50 -0
  66. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  67. data/spec/dummy/bin/rails +4 -0
  68. data/spec/dummy/config/application.rb +70 -0
  69. data/spec/dummy/config/boot.rb +5 -0
  70. data/spec/dummy/config/database.yml +25 -0
  71. data/spec/dummy/config/environment.rb +5 -0
  72. data/spec/dummy/config/environments/development.rb +33 -0
  73. data/spec/dummy/config/environments/production.rb +57 -0
  74. data/spec/dummy/config/environments/test.rb +31 -0
  75. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  76. data/spec/dummy/config/initializers/inflections.rb +15 -0
  77. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  78. data/spec/dummy/config/initializers/secret_token.rb +8 -0
  79. data/spec/dummy/config/initializers/session_store.rb +8 -0
  80. data/spec/dummy/config/locales/en.yml +5 -0
  81. data/spec/dummy/config/routes.rb +9 -0
  82. data/spec/dummy/config.ru +4 -0
  83. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
  84. data/spec/dummy/db/schema.rb +21 -0
  85. data/spec/dummy/db/seeds.rb +2 -0
  86. data/spec/dummy/fast_spec/post_presenter_spec.rb +37 -0
  87. data/spec/dummy/lib/tasks/test.rake +16 -0
  88. data/spec/dummy/log/.gitkeep +0 -0
  89. data/spec/dummy/public/404.html +26 -0
  90. data/spec/dummy/public/422.html +26 -0
  91. data/spec/dummy/public/500.html +25 -0
  92. data/spec/dummy/public/favicon.ico +0 -0
  93. data/spec/dummy/script/rails +6 -0
  94. data/spec/dummy/spec/fast_spec_helper.rb +13 -0
  95. data/spec/dummy/spec/mailers/post_mailer_spec.rb +33 -0
  96. data/spec/dummy/spec/models/post_spec.rb +4 -0
  97. data/spec/dummy/spec/presenters/active_model_serializers_spec.rb +11 -0
  98. data/spec/dummy/spec/presenters/devise_spec.rb +64 -0
  99. data/spec/dummy/spec/presenters/helpers_spec.rb +21 -0
  100. data/spec/dummy/spec/presenters/post_presenter_spec.rb +66 -0
  101. data/spec/dummy/spec/presenters/spec_type_spec.rb +7 -0
  102. data/spec/dummy/spec/presenters/special_post_presenter_spec.rb +11 -0
  103. data/spec/dummy/spec/presenters/view_context_spec.rb +22 -0
  104. data/spec/dummy/spec/spec_helper.rb +19 -0
  105. data/spec/dummy/test/minitest_helper.rb +2 -0
  106. data/spec/dummy/test/presenters/minitest/devise_test.rb +64 -0
  107. data/spec/dummy/test/presenters/minitest/helpers_test.rb +21 -0
  108. data/spec/dummy/test/presenters/minitest/spec_type_test.rb +52 -0
  109. data/spec/dummy/test/presenters/minitest/view_context_test.rb +24 -0
  110. data/spec/dummy/test/presenters/test_unit/devise_test.rb +64 -0
  111. data/spec/dummy/test/presenters/test_unit/helpers_test.rb +21 -0
  112. data/spec/dummy/test/presenters/test_unit/view_context_test.rb +24 -0
  113. data/spec/dummy/test/test_helper.rb +13 -0
  114. data/spec/generators/presenters/presenter_generator_spec.rb +131 -0
  115. data/spec/generators/simplecov_spec.rb +5 -0
  116. data/spec/integration/integration_spec.rb +81 -0
  117. data/spec/integration/simplecov_spec.rb +4 -0
  118. data/spec/spec_helper.rb +47 -0
  119. data/spec/strong_presenter/associable_spec.rb +122 -0
  120. data/spec/strong_presenter/collection_presenter_spec.rb +34 -0
  121. data/spec/strong_presenter/delegation_spec.rb +20 -0
  122. data/spec/strong_presenter/permissible_spec.rb +24 -0
  123. data/spec/strong_presenter/permissions_spec.rb +188 -0
  124. data/spec/strong_presenter/presenter_spec.rb +43 -0
  125. data/spec/strong_presenter/simplecov_spec.rb +4 -0
  126. data/spec/support/dummy_app.rb +85 -0
  127. data/spec/support/matchers/have_text.rb +50 -0
  128. data/spec/support/models.rb +14 -0
  129. data/spec/support/schema.rb +12 -0
  130. data/strong_presenter.gemspec +15 -0
  131. metadata +392 -13
  132. 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
@@ -6,13 +6,16 @@
6
6
  Gemfile.lock
7
7
  InstalledFiles
8
8
  _yardoc
9
- coverage
9
+ /coverage
10
10
  doc/
11
11
  lib/bundler/man
12
- pkg
12
+ /pkg
13
13
  rdoc
14
14
  spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  *.swp
19
+ *.log
20
+ *.sqlite3
21
+
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --order rand
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 'https://rubygems.org'
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
- StrongPresenter lets you add presenters to your application, along with strong_parameters-inspired permit logic to handle mass presentations, where each user may have permision to view different fields.
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
- Adding an explicit `permit` interface allows authorization logic to be pushed back from the view to the controller where it probably belongs. This allows the view to concentrate on laying out the webpage, rather than deciding whether each of the components should be displayed.
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
- When displaying a datatable, there is no need to call `can? :read_email_column, @user` in both the table headings, and as each row is displayed. The permission check can be performed once in the controller.
10
+ A number of features have been copied from Draper and refined.
8
11
 
9
- This gem puts presenters in `app/presenters`, not `app/decorators`, because not all decorators are presenters. It better separates the different class types in your application. Presenters should be providing a read-only interface to a model. Variable setters should not be delegated through the presenter. On the other hand, decorators add features to a model, and can very well delegate setters methods to the model. However, they may be used for other reasons, such as in domain-specific logic. We don't want to mix presenter classes with decorator classes that may be used for domain-specific logic.
12
+ ## Why use Presenters?
10
13
 
11
- While there exist other presenter gems, we hope to provide a more natural interface to create your presenters. This gem is opinionated - presenters should be in `app/presenters`, read-only interfaces to the underlying models, and not mixed with general-purpose decorators.
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
- Create a new presenter:
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
- class ApplicationPresenter < StrongPresenter::Base
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
- Use the presenter on your object in your controller
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.wrap(@user).permit *visible_attributes
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
- private
57
- def visible_attributes
58
- [:username] + (can?(:read_private_details, @user) ? [:name, :email] : [])
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
- Use the presenter in the view:
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
- Or use mass presentation to present multiple fields/attributes at once. Notice that if `visible_attributes`
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
- <% @user_presenter.presents :username, :name, :email do |key, value| %>
75
- <b><%= UserPresenter.label(key) %>:</b> <%= value %><br>
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 `presents` method will only present those attributes which have been permitted. In contrast, a method
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
- <% @user_presenter.presents(:avatar).each do |presented_avatar| %>
85
- <%= presented_avatar %>
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
- In this case, since `permit` was not passed `:avatar`, it will not be presented. To remove authorization checks
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 UserPresenter < ApplicationPresenter
96
- ...
97
- label :email => "E-mail", :name => "Real Name"
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
- Labels can be retrieved by using the field/attribute by calling `label` with the symbol(s):
189
+ You can also call it when using the `presents` method in the controller:
102
190
 
103
191
  ```ruby
104
- UserPresenter.label [:email, :username] # returns ["E-mail", "Username"]
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
- If no label was explicitly assigned, it is simply converted to a string and humanized.
203
+ To remove authorization checks, simply call `permit!` on an instance of a presenter.
108
204
 
109
- Labels can be useful when used in a datatable to display collections. The controller can wrap
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 = [:username, :name, :email] %>
208
+ <% fields = { :username => "Username", :name => "Name", :email => "E-mail" } %>
120
209
  <table>
121
210
  <tr>
122
- <% @users_presenter.filter( *fields ).to_labels.each do |label| %>
123
- <th><%= label %></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 `to_labels` is a method added to the array to allow conversion to label form.
226
+ array of only the visible columns, and we use our `fields` hash to label it.
138
227
 
139
- This also allows mass presentation based on GET parameter input, for example:
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| %><%= content_tag :td, value %><% end %>
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 "bundler/gem_tasks"
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,2 @@
1
+ gem "rails", "~> 3.0.0"
2
+ gem "active_model_serializers", "~> 0.7.0"
@@ -0,0 +1,2 @@
1
+ gem "rails", "~> 3.1.0"
2
+ gem "devise", "~> 2.2"
@@ -0,0 +1,2 @@
1
+ gem "rails", "~> 3.2.0"
2
+ gem "devise", "~> 2.2"
@@ -0,0 +1,2 @@
1
+ gem "rails", "~> 4.0.0"
2
+ gem "devise", "~> 3.0.0"
@@ -0,0 +1,2 @@
1
+ gem "rails", github: "rails/rails"
2
+ gem "devise", github: "plataformatec/devise"
@@ -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
@@ -0,0 +1,4 @@
1
+ require 'minitest_helper'
2
+
3
+ describe <%= class_name %>Presenter do
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'minitest_helper'
2
+
3
+ class <%= class_name %>PresenterTest < StrongPresenter::TestCase
4
+ end