meta_presenter 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +14 -12
- data/lib/meta_presenter/base/delegate_all_to.rb +1 -1
- data/lib/meta_presenter/helpers.rb +0 -9
- data/meta_presenter.gemspec +1 -1
- data/spec/meta_presenter/base/delegate_all_to_spec.rb +116 -0
- data/spec/meta_presenter/base/delegate_to_controller_spec.rb +58 -0
- data/spec/meta_presenter/base_spec.rb +19 -0
- data/spec/meta_presenter/builder_spec.rb +1 -2
- data/spec/support/app/controllers/application_controller.rb +4 -0
- data/spec/support/app/controllers/pages_controller.rb +4 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8304ed69b8fcec17f9f190714e38d3d84e5dfc0747889303120c4d4eb77c8630
|
4
|
+
data.tar.gz: 2005c166ac8139c6e567c5e19c56ab5d38febc3a596e5a0f30edfd09074dcafb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6184fa5b49ecfba29fcc2825bd4dfcf82f83d4dcad33311bf9b9586cadeb2dfb1475796e1ca21db0bbf04a57dd8093454b88c3c91f41186c9fdfa9119226176
|
7
|
+
data.tar.gz: 4ff04576a64dd7c50670b74b5bbd2aa27c8a198000c5b3d2fd325b4806e0026bc2de595884818aa975d002c7537c9b91df9680134585dbdafe38374a29f54b44
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[![logo](https://user-images.githubusercontent.com/28652/50427588-2289cf80-087a-11e9-82e1-ae212adf0d07.png)](https://metapresenter.com)
|
4
4
|
|
5
|
-
MetaPresenter is a Ruby gem
|
5
|
+
MetaPresenter is a Ruby gem for writing highly focused and testable view Rails presenter classes. For each controller/action pair you get a presenter class in `app/presenters` that you can use in your views with with `presenter.method_name`. This helps you decompose your helper logic into tight, easily testable classes. There's even a DSL for method delegation on objects to reduce boilerplate.
|
6
6
|
|
7
7
|
![overlay-shape-clean-sm](https://user-images.githubusercontent.com/28652/50854229-828c7580-1352-11e9-824b-a78c9a2404fb.png)
|
8
8
|
|
@@ -36,7 +36,7 @@ end
|
|
36
36
|
|
37
37
|
## Example
|
38
38
|
|
39
|
-
Say you have a PagesController with
|
39
|
+
Say you have a PagesController with `#home` and `#logs` actions. Underneath app/presenters you can add a presenter class for each action (Pages::HomePresenter and Pages::LogsPresenter). We'll also create an ApplicationPresenter superclass for methods that can be used in any action throughout the app.
|
40
40
|
|
41
41
|
```
|
42
42
|
app/
|
@@ -113,7 +113,7 @@ class ApplicationPresenter < MetaPresenter::BasePresenter
|
|
113
113
|
end
|
114
114
|
```
|
115
115
|
|
116
|
-
app/presenters/pages_presenter.rb
|
116
|
+
app/presenters/pages_presenter.rb
|
117
117
|
|
118
118
|
```ruby
|
119
119
|
class PagesPresenter < ApplicationPresenter
|
@@ -121,8 +121,8 @@ class PagesPresenter < ApplicationPresenter
|
|
121
121
|
# all actions on PagesController
|
122
122
|
def nav_items
|
123
123
|
[
|
124
|
-
|
125
|
-
|
124
|
+
{name: "Home", path: home_path},
|
125
|
+
{name: "Logs", path: logs_path}
|
126
126
|
]
|
127
127
|
end
|
128
128
|
end
|
@@ -135,7 +135,7 @@ class Pages::HomePresenter < PagesPresenter
|
|
135
135
|
# presenter.email, presenter.id or any other
|
136
136
|
# method not already defined will delegate to
|
137
137
|
# the current_user
|
138
|
-
delegate_all_to :current_user
|
138
|
+
delegate_all_to = :current_user
|
139
139
|
|
140
140
|
# presenter.greeting in views
|
141
141
|
def greeting
|
@@ -144,12 +144,12 @@ class Pages::HomePresenter < PagesPresenter
|
|
144
144
|
end
|
145
145
|
```
|
146
146
|
|
147
|
-
app/views/pages/home.html.
|
147
|
+
app/views/pages/home.html.erb
|
148
148
|
|
149
|
-
```
|
150
|
-
|
151
|
-
|
152
|
-
|
149
|
+
```Erb
|
150
|
+
<h1>Home</h1>
|
151
|
+
<p><%= presenter.greeting %></p>
|
152
|
+
<p>Last login <%= distance_of_time_in_words_to_now(presenter.last_login_at) %></p>
|
153
153
|
```
|
154
154
|
|
155
155
|
app/presenters/pages/logs_presenter.rb
|
@@ -193,7 +193,7 @@ end
|
|
193
193
|
|
194
194
|
## Requirements
|
195
195
|
|
196
|
-
MetaPresenter supports Ruby >= 2.1 and ActionPack >= 4.0. If you'd like to help adding support for older versions please submit a pull request with passing specs.
|
196
|
+
MetaPresenter supports Ruby >= 2.1 and ActionPack/ActionMailer >= 4.0. If you'd like to help adding support for older versions please submit a pull request with passing specs.
|
197
197
|
|
198
198
|
## Links
|
199
199
|
|
@@ -207,6 +207,8 @@ To run the specs for the currently running Ruby version, run `bundle install` an
|
|
207
207
|
Make sure the specs pass, bump the version number in meta_presenter.gemspec, build the gem with `gem build meta_presenter.gemspec`. Then commit changes and tag the commit with the current release number with `git tag -a "vVersionNumberHere" -m "vVersionNumberHere"`. Push the commit with `git push`, then push the tags with `git push origin --tags`. Finally, push the gem with `gem push meta_presenter-version-number-here.gem`.
|
208
208
|
|
209
209
|
## TODO
|
210
|
+
* create an example app and link to the repo for it in this README
|
211
|
+
* proofread the README instructions to make sure everything is correct
|
210
212
|
* optional `rake meta_presenter:install` that generates the scaffolding for you. Or, you can manually create the files you want.
|
211
213
|
* add support for layout-level presenters
|
212
214
|
* add Rails 6 support once it comes out (hopefully just have to add a gemfiles/actionpack6.gemfile and it will run with the Appraisal suite)
|
@@ -12,7 +12,7 @@ module MetaPresenter
|
|
12
12
|
# all incoming methods send to the presenter that
|
13
13
|
# we not already handled by the presenter otherwise
|
14
14
|
# (such as explicitly defining the method)
|
15
|
-
class_attribute :delegate_all_to
|
15
|
+
class_attribute :delegate_all_to, instance_reader: false, instance_writer: false
|
16
16
|
|
17
17
|
include InstanceMethods
|
18
18
|
end
|
@@ -23,21 +23,12 @@ module MetaPresenter
|
|
23
23
|
helper_method :presenter
|
24
24
|
end
|
25
25
|
|
26
|
-
class PresenterClassNotFoundError < NameError
|
27
|
-
def initialize(controller, action_name)
|
28
|
-
super("No presenter class and method found for #{controller.class.name}##{action_name} (Are you rendering another action's template? If so then move your presenter method to a base presenter class for the controller that both action presenters inherit from)")
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
26
|
private
|
33
27
|
# Initialize presenter with the current controller
|
34
28
|
def presenter
|
35
29
|
@presenter ||= begin
|
36
30
|
controller = self
|
37
31
|
klass = MetaPresenter::Builder.new(controller, action_name).presenter_class
|
38
|
-
if klass.nil?
|
39
|
-
raise PresenterClassNotFoundError.new(controller, action_name)
|
40
|
-
end
|
41
32
|
klass.new(controller)
|
42
33
|
end
|
43
34
|
end
|
data/meta_presenter.gemspec
CHANGED
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MetaPresenter::Base::DelegateAllTo do
|
4
|
+
|
5
|
+
describe '.delegate_all_to' do
|
6
|
+
subject { presenter.send(method_name) }
|
7
|
+
|
8
|
+
let(:controller_class) { PagesController }
|
9
|
+
let(:controller) { controller_class.new }
|
10
|
+
let(:action_name) { 'logs' }
|
11
|
+
let(:presenter) { controller.view_context.presenter }
|
12
|
+
|
13
|
+
let(:controller_all_instance_methods) { controller.class.instance_methods(true) }
|
14
|
+
let(:presenter_all_instance_methods) { presenter.class.instance_methods(true) }
|
15
|
+
|
16
|
+
let(:delegate_object) { controller.send(delegate_object_method_name) }
|
17
|
+
let(:delegate_object_method_name) { :character }
|
18
|
+
|
19
|
+
before do
|
20
|
+
# stub action name on the controller
|
21
|
+
allow(controller).to receive(:action_name).and_return(action_name)
|
22
|
+
|
23
|
+
controller.class.redefine_method(delegate_object_method_name) do
|
24
|
+
OpenStruct.new(name: "Mario")
|
25
|
+
end
|
26
|
+
|
27
|
+
# set up the controller to delegate all to an object
|
28
|
+
presenter.class.delegate_all_to = delegate_object_method_name
|
29
|
+
end
|
30
|
+
|
31
|
+
def define_method_on_presenter
|
32
|
+
presenter.class.redefine_method(method_name) do
|
33
|
+
"presenter retval"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def define_method_on_controller
|
38
|
+
controller.class.redefine_method(method_name) do
|
39
|
+
"controller retval"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "delegating a method the object responds to" do
|
44
|
+
let(:method_name) { :name }
|
45
|
+
|
46
|
+
before do
|
47
|
+
expect(delegate_object).to respond_to(method_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
context "and presenter doesn't define the method" do
|
51
|
+
before do
|
52
|
+
expect(presenter_all_instance_methods).to_not include(method_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
context "and controller doesn't define the method" do
|
56
|
+
before do
|
57
|
+
expect(controller_all_instance_methods).to_not include(method_name)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "calls the method on the delegated object" do
|
61
|
+
expect(subject).to eql(delegate_object.send(method_name))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "but controller defines the method" do
|
66
|
+
before do
|
67
|
+
define_method_on_controller
|
68
|
+
expect(controller_all_instance_methods).to include(method_name)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "still calls the method on the delegated object" do
|
72
|
+
expect(subject).to eql(delegate_object.send(method_name))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "but presenter also defines the method" do
|
78
|
+
before do
|
79
|
+
define_method_on_presenter
|
80
|
+
expect(presenter_all_instance_methods).to include(method_name)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "calls the presenter defined method" do
|
84
|
+
expect(subject).to eql("presenter retval")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "delegating a method the object doesn't respond to" do
|
90
|
+
let(:method_name) { :age }
|
91
|
+
|
92
|
+
before do
|
93
|
+
expect(delegate_object).to_not respond_to(method_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
context "and presenter doesn't define the method" do
|
97
|
+
before do
|
98
|
+
expect(presenter_all_instance_methods).to_not include(method_name)
|
99
|
+
end
|
100
|
+
|
101
|
+
it { expect { subject }.to raise_error(NoMethodError, "undefined method `#{method_name}' for #<Pages::LogsPresenter>") }
|
102
|
+
end
|
103
|
+
|
104
|
+
context "but presenter defines the method" do
|
105
|
+
before do
|
106
|
+
define_method_on_presenter
|
107
|
+
expect(presenter_all_instance_methods).to include(method_name)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "calls the presenter defined method" do
|
111
|
+
expect(subject).to eql("presenter retval")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MetaPresenter::Base::DelegateToController do
|
4
|
+
|
5
|
+
describe 'delegating a method to controller' do
|
6
|
+
subject { presenter.send(method_name) }
|
7
|
+
|
8
|
+
let(:controller_class) { PagesController }
|
9
|
+
let(:controller) { controller_class.new }
|
10
|
+
let(:action_name) { 'logs' }
|
11
|
+
let(:presenter) { controller.view_context.presenter }
|
12
|
+
|
13
|
+
before do
|
14
|
+
allow(controller).to receive(:action_name).and_return(action_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:directly_defined_instance_methods) { controller.class.instance_methods(false) }
|
18
|
+
let(:inherited_instance_methods) { controller.class.instance_methods(true) }
|
19
|
+
|
20
|
+
context "method exists on the controller" do
|
21
|
+
before do
|
22
|
+
expect(controller).to respond_to(method_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
context "on the same class" do
|
26
|
+
let(:method_name) { :a_method_defined_on_pages_controller }
|
27
|
+
|
28
|
+
before do
|
29
|
+
expect(directly_defined_instance_methods).to include(method_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
it { is_expected.to eql("pages controller method return value") }
|
33
|
+
end
|
34
|
+
|
35
|
+
context "on a superclass" do
|
36
|
+
let(:method_name) { :a_method_defined_on_application_controller }
|
37
|
+
|
38
|
+
before do
|
39
|
+
expect(directly_defined_instance_methods).to_not include(method_name)
|
40
|
+
expect(inherited_instance_methods).to include(method_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
it { is_expected.to eql("application controller method return value") }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "method doesn't exist on the controller" do
|
48
|
+
let(:method_name) { :a_method_not_defined_on_controller }
|
49
|
+
|
50
|
+
before do
|
51
|
+
expect(directly_defined_instance_methods).to_not include(method_name)
|
52
|
+
expect(inherited_instance_methods).to_not include(method_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
it { expect { subject }.to raise_error(NoMethodError) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MetaPresenter::Base do
|
4
|
+
|
5
|
+
describe '#inspect' do
|
6
|
+
subject { presenter.inspect }
|
7
|
+
|
8
|
+
let(:controller_class) { ApplicationController }
|
9
|
+
let(:controller) { controller_class.new }
|
10
|
+
let(:action_name) { 'test' }
|
11
|
+
let(:presenter) { controller.view_context.presenter }
|
12
|
+
|
13
|
+
before do
|
14
|
+
allow(controller).to receive(:action_name).and_return(action_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
it { is_expected.to eql('#<ApplicationPresenter>') }
|
18
|
+
end
|
19
|
+
end
|
@@ -1,10 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
# require 'ostruct'
|
3
2
|
|
4
3
|
describe MetaPresenter::Builder do
|
5
4
|
let(:controller_class) { ApplicationController }
|
6
5
|
let(:controller) { controller_class.send(:new) }
|
7
|
-
let(:action_name) { '
|
6
|
+
let(:action_name) { 'test' }
|
8
7
|
let(:object) { described_class.new(controller, action_name) }
|
9
8
|
|
10
9
|
def controller_ancestors
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: meta_presenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- szTheory
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-01-
|
11
|
+
date: 2019-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -163,6 +163,9 @@ files:
|
|
163
163
|
- lib/meta_presenter/helpers.rb
|
164
164
|
- logo.png
|
165
165
|
- meta_presenter.gemspec
|
166
|
+
- spec/meta_presenter/base/delegate_all_to_spec.rb
|
167
|
+
- spec/meta_presenter/base/delegate_to_controller_spec.rb
|
168
|
+
- spec/meta_presenter/base_spec.rb
|
166
169
|
- spec/meta_presenter/builder_spec.rb
|
167
170
|
- spec/meta_presenter/helpers_spec.rb
|
168
171
|
- spec/spec_helper.rb
|
@@ -209,6 +212,9 @@ summary: MetaPresenter is a Ruby gem that gives you access to the powerful prese
|
|
209
212
|
This helps you decompose your helper logic into small, tight, classes that are easily
|
210
213
|
testable. There's even a DSL for method delegation on objects to reduce boilerplate.
|
211
214
|
test_files:
|
215
|
+
- spec/meta_presenter/base/delegate_all_to_spec.rb
|
216
|
+
- spec/meta_presenter/base/delegate_to_controller_spec.rb
|
217
|
+
- spec/meta_presenter/base_spec.rb
|
212
218
|
- spec/meta_presenter/builder_spec.rb
|
213
219
|
- spec/meta_presenter/helpers_spec.rb
|
214
220
|
- spec/spec_helper.rb
|