presenter_rails 1.0.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +32 -26
- data/lib/presenter_rails.rb +22 -3
- data/lib/presenter_rails/controller.rb +26 -0
- data/lib/presenter_rails/version.rb +3 -0
- data/spec/features/birds_controller_spec.rb +22 -0
- data/spec/features/birds_mailer_spec.rb +22 -0
- data/spec/presenter_rails/controller_spec.rb +68 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/rails_app.rb +41 -0
- metadata +62 -11
- data/lib/presenter_rails/presenter.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad881d965883dd21b21b96fca07691befd7aaa50
|
4
|
+
data.tar.gz: bbd8f56f09035195e9b469733f98188b281e8c37
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0540e2d1c5e94a3aacb7ca87db48ec09f67c8c06bca8685e3f6bb501c49aec9e7c08b4599fa1a8882fe2c97f27b598f05a90ab25b29e4f46430c89c28d4a853
|
7
|
+
data.tar.gz: cb5fd596bc8178cc99362e555d0c3039b3448786279a702791d332fbf43be36f6517b4bcf1b5a7d3c6fa5b6719073904961b08ebbb60432de43286ce483df6fb
|
data/README.md
CHANGED
@@ -1,41 +1,47 @@
|
|
1
|
-
Presenter
|
1
|
+
Presenter [![Gem Version](https://badge.fury.io/rb/presenter_rails.svg)](http://badge.fury.io/rb/presenter_rails) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ElMassimo/presenter_rails/blob/master/LICENSE.txt)
|
2
2
|
=====================
|
3
3
|
|
4
4
|
Presenter helps you expose view models to your views in a convenient way, while
|
5
5
|
still allowing you to define methods with the same name inside your controllers.
|
6
6
|
|
7
7
|
```ruby
|
8
|
-
|
9
|
-
|
8
|
+
# app/controllers/people_controller.rb
|
9
|
+
class PeopleController < ApplicationController
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
present(:person) {
|
12
|
+
PersonDecorator.decorate(person)
|
13
|
+
}
|
14
|
+
|
15
|
+
...
|
16
|
+
|
17
|
+
def person
|
18
|
+
People.find(params[:id])
|
19
|
+
end
|
20
|
+
end
|
15
21
|
```
|
16
22
|
|
17
23
|
```haml
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
24
|
+
/ app/views/people/show.html.haml
|
25
|
+
.person
|
26
|
+
.person-name= person.name
|
27
|
+
.person-info= person.biography
|
22
28
|
```
|
23
|
-
|
29
|
+
|
30
|
+
The method is also available in the controller, with a `_presenter` suffix:
|
24
31
|
```ruby
|
25
|
-
|
26
|
-
|
27
|
-
|
32
|
+
# app/controllers/people_controller.rb
|
33
|
+
class PeopleController < ApplicationController
|
34
|
+
|
35
|
+
...
|
28
36
|
|
29
|
-
|
37
|
+
def update
|
38
|
+
person.update(attrs)
|
39
|
+
redirect_to person_presenter.path, notice: "Successfully updated."
|
40
|
+
end
|
30
41
|
|
31
|
-
|
32
|
-
PersonPresenter.decorate(...)
|
33
|
-
end
|
42
|
+
...
|
34
43
|
|
35
|
-
|
36
|
-
People.all
|
37
|
-
end
|
38
|
-
end
|
44
|
+
end
|
39
45
|
```
|
40
46
|
|
41
47
|
## Background
|
@@ -45,12 +51,12 @@ about what you are exposing, although it's specially useful to implement [two-st
|
|
45
51
|
|
46
52
|
### How it works
|
47
53
|
|
48
|
-
When you provide a block, it defines a `"#{name}_presenter"` private method in your controller
|
54
|
+
When you provide a block, it defines a `"#{name}_presenter"` private method in your controller.
|
49
55
|
|
50
|
-
After that, it creates helper
|
56
|
+
After that, it creates a helper method for your views, which calls the `"#{name}_presenter"` counterpart in the controller.
|
51
57
|
|
52
58
|
#### Memoization
|
53
|
-
Each presenter method is memoized, so the method is called only once and your views get the same instance every time.
|
59
|
+
Each presenter method is memoized, so the method is called only once and your views get the same instance every time. The block is evaluated only if the method is called.
|
54
60
|
|
55
61
|
#### Corolary
|
56
62
|
Since the helper methods defined are only available for the view, you can define methods with the same name in your controller :smiley:.
|
data/lib/presenter_rails.rb
CHANGED
@@ -1,5 +1,24 @@
|
|
1
|
-
require
|
1
|
+
require "presenter_rails/version"
|
2
|
+
require "active_support/all"
|
2
3
|
|
3
|
-
|
4
|
-
|
4
|
+
module PresenterRails
|
5
|
+
autoload :Controller, "presenter_rails/controller"
|
6
|
+
|
7
|
+
# Internal: Name of the presenter method as defined in the controller.
|
8
|
+
def self.method_name_for(name)
|
9
|
+
"#{ name }_presenter"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Internal: Name of the instance variable used to memoize a presenter method.
|
13
|
+
def self.ivar_for(name)
|
14
|
+
"@_presenter_#{ name.to_s.gsub("?", "_query").gsub("!", "_bang") }"
|
15
|
+
end
|
16
|
+
|
17
|
+
ActiveSupport.on_load :action_controller do
|
18
|
+
extend Controller
|
19
|
+
end
|
20
|
+
|
21
|
+
ActiveSupport.on_load :action_mailer do
|
22
|
+
extend Controller
|
23
|
+
end
|
5
24
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module PresenterRails
|
2
|
+
module Controller
|
3
|
+
# Public: Defines a method and makes it available to the view context
|
4
|
+
# under the specified name as a memoized variable.
|
5
|
+
#
|
6
|
+
# name - The name of the method as called from the view context.
|
7
|
+
# block - Executed once if (and only if) the method is called.
|
8
|
+
#
|
9
|
+
# Returns nothing.
|
10
|
+
def present(name, &block)
|
11
|
+
presenter_method = PresenterRails.method_name_for(name)
|
12
|
+
ivar = PresenterRails.ivar_for(name)
|
13
|
+
|
14
|
+
private define_method(presenter_method) {
|
15
|
+
unless instance_variable_defined?(ivar)
|
16
|
+
instance_variable_set(ivar, instance_exec(&block))
|
17
|
+
end
|
18
|
+
instance_variable_get(ivar)
|
19
|
+
}
|
20
|
+
|
21
|
+
helper Module.new {
|
22
|
+
define_method(name) { controller.send(presenter_method) }
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "support/rails_app"
|
3
|
+
require "rspec/rails"
|
4
|
+
|
5
|
+
RSpec.describe BirdsController, type: :controller do
|
6
|
+
Given(:bird) { double(:bird) }
|
7
|
+
|
8
|
+
context "present works well with the view context" do
|
9
|
+
Given {
|
10
|
+
class BirdsController
|
11
|
+
present(:bird) { bird }
|
12
|
+
present(:bird?) { bird_presenter.present? }
|
13
|
+
end
|
14
|
+
|
15
|
+
expect_any_instance_of(BirdsController).to receive(:bird).once.and_return(bird)
|
16
|
+
}
|
17
|
+
When { get :index }
|
18
|
+
Then { controller.view_context.bird == bird }
|
19
|
+
And { controller.view_context.bird == controller.send(:bird_presenter) }
|
20
|
+
And { controller.view_context.bird? == true }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "support/rails_app"
|
3
|
+
require "rspec/rails"
|
4
|
+
require "action_mailer"
|
5
|
+
|
6
|
+
class BirdsMailer < ActionMailer::Base
|
7
|
+
present(:bird) { OpenStruct.new(name: @name) }
|
8
|
+
|
9
|
+
def hello_bird(name:)
|
10
|
+
@name = name
|
11
|
+
mail { |format| format.text { render plain: bird_presenter.name } }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec.describe BirdsMailer, type: :mailer do
|
16
|
+
Given(:name) { "pidgeon" }
|
17
|
+
|
18
|
+
context "with keyword arguments" do
|
19
|
+
When(:mail) { BirdsMailer.hello_bird(name: name) }
|
20
|
+
Then { mail.body.to_s == name }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "rspec/rails"
|
3
|
+
|
4
|
+
RSpec.describe PresenterRails::Controller do
|
5
|
+
class Thing; end
|
6
|
+
class DifferentThing; end
|
7
|
+
|
8
|
+
class ViewContext
|
9
|
+
attr_reader :controller
|
10
|
+
|
11
|
+
def initialize(controller)
|
12
|
+
@controller = controller
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class BaseController
|
17
|
+
extend PresenterRails::Controller
|
18
|
+
|
19
|
+
def self.helper(helper_module)
|
20
|
+
@helper_modules ||= []
|
21
|
+
@helper_modules << helper_module
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.helper_modules
|
25
|
+
@helper_modules
|
26
|
+
end
|
27
|
+
|
28
|
+
def view_context
|
29
|
+
self.class.helper_modules.inject(ViewContext.new(self)) { |view_context, helper_module|
|
30
|
+
view_context.extend(helper_module)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Given(:controller_class) { Class.new(BaseController) }
|
36
|
+
Given(:controller) { controller_class.new }
|
37
|
+
Given(:view_context) { controller.view_context }
|
38
|
+
Given(:helper_modules) { controller_class.helper_modules }
|
39
|
+
|
40
|
+
delegate :present, to: :controller_class
|
41
|
+
|
42
|
+
context "present" do
|
43
|
+
Given {
|
44
|
+
expect(controller).to receive(:thing).once.and_return(Thing.new)
|
45
|
+
expect(controller).to receive(:different_thing).once.and_return(DifferentThing.new)
|
46
|
+
expect(controller).to receive(:empty).once.and_return(nil)
|
47
|
+
}
|
48
|
+
|
49
|
+
Given {
|
50
|
+
present(:thing) { thing }
|
51
|
+
present(:another_thing) { different_thing }
|
52
|
+
present(:empty?) { empty }
|
53
|
+
}
|
54
|
+
|
55
|
+
Then { controller.send(:thing_presenter).is_a?(Thing) }
|
56
|
+
And { controller.send(:another_thing_presenter).is_a?(DifferentThing) }
|
57
|
+
And { controller.send(:'empty?_presenter').nil? }
|
58
|
+
|
59
|
+
And { helper_modules.size == 3 }
|
60
|
+
And { helper_modules[0].instance_methods == [:thing] }
|
61
|
+
And { helper_modules[1].instance_methods == [:another_thing] }
|
62
|
+
And { helper_modules[2].instance_methods == [:empty?] }
|
63
|
+
|
64
|
+
And { view_context.thing.is_a?(Thing) }
|
65
|
+
And { view_context.another_thing.is_a?(DifferentThing) }
|
66
|
+
And { view_context.empty?.nil? }
|
67
|
+
end
|
68
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require "action_controller"
|
2
|
+
require "rails"
|
3
|
+
|
4
|
+
def request_params(params)
|
5
|
+
return params if Rails::VERSION::MAJOR < 5
|
6
|
+
{ params: params }
|
7
|
+
end
|
8
|
+
|
9
|
+
module Rails
|
10
|
+
class App
|
11
|
+
def env_config
|
12
|
+
{}
|
13
|
+
end
|
14
|
+
|
15
|
+
def routes
|
16
|
+
@routes ||= ActionDispatch::Routing::RouteSet.new.tap do |routes|
|
17
|
+
routes.draw do
|
18
|
+
resources :birds
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.root
|
25
|
+
''
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.application
|
29
|
+
@app ||= App.new
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class ApplicationController < ActionController::Base
|
34
|
+
include Rails.application.routes.url_helpers
|
35
|
+
end
|
36
|
+
|
37
|
+
class BirdsController < ApplicationController
|
38
|
+
def index
|
39
|
+
head :ok
|
40
|
+
end
|
41
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: presenter_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Máximo Mussini
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-04-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -25,19 +25,61 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: railties
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: actionmailer
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - ">="
|
32
46
|
- !ruby/object:Gem::Version
|
33
47
|
version: '0'
|
34
|
-
type: :
|
48
|
+
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-given
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
41
83
|
description: Presenter helps you expose view models to your views with a declarative
|
42
84
|
approach.
|
43
85
|
email:
|
@@ -49,19 +91,24 @@ extra_rdoc_files:
|
|
49
91
|
files:
|
50
92
|
- README.md
|
51
93
|
- lib/presenter_rails.rb
|
52
|
-
- lib/presenter_rails/
|
94
|
+
- lib/presenter_rails/controller.rb
|
95
|
+
- lib/presenter_rails/version.rb
|
96
|
+
- spec/features/birds_controller_spec.rb
|
97
|
+
- spec/features/birds_mailer_spec.rb
|
98
|
+
- spec/presenter_rails/controller_spec.rb
|
99
|
+
- spec/spec_helper.rb
|
100
|
+
- spec/support/rails_app.rb
|
53
101
|
homepage: https://github.com/ElMassimo/presenter_rails
|
54
102
|
licenses:
|
55
103
|
- MIT
|
56
104
|
metadata: {}
|
57
105
|
post_install_message:
|
58
|
-
rdoc_options:
|
59
|
-
- "--charset=UTF-8"
|
106
|
+
rdoc_options: []
|
60
107
|
require_paths:
|
61
108
|
- lib
|
62
109
|
required_ruby_version: !ruby/object:Gem::Requirement
|
63
110
|
requirements:
|
64
|
-
- - "
|
111
|
+
- - "~>"
|
65
112
|
- !ruby/object:Gem::Version
|
66
113
|
version: '2.0'
|
67
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
@@ -71,9 +118,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
118
|
version: '0'
|
72
119
|
requirements: []
|
73
120
|
rubyforge_project:
|
74
|
-
rubygems_version: 2.
|
121
|
+
rubygems_version: 2.5.1
|
75
122
|
signing_key:
|
76
123
|
specification_version: 4
|
77
124
|
summary: ViewModels had a baby with helper_method
|
78
|
-
test_files:
|
79
|
-
|
125
|
+
test_files:
|
126
|
+
- spec/features/birds_controller_spec.rb
|
127
|
+
- spec/features/birds_mailer_spec.rb
|
128
|
+
- spec/presenter_rails/controller_spec.rb
|
129
|
+
- spec/spec_helper.rb
|
130
|
+
- spec/support/rails_app.rb
|
@@ -1,55 +0,0 @@
|
|
1
|
-
require 'pakiderm'
|
2
|
-
|
3
|
-
module PresenterRails
|
4
|
-
module Presenter
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
7
|
-
included do
|
8
|
-
extend Pakiderm
|
9
|
-
end
|
10
|
-
|
11
|
-
module ClassMethods
|
12
|
-
|
13
|
-
# Can take a list of presenter methods, or a name and block for a presenter method
|
14
|
-
def present(*methods, &block)
|
15
|
-
presenter_methods = methods.map {|name| Presenter.method_name_for(name) }
|
16
|
-
define_presenter_method!(presenter_methods, &block) if block_given?
|
17
|
-
|
18
|
-
expose_presenter *methods
|
19
|
-
memoize *presenter_methods, assignable: true
|
20
|
-
end
|
21
|
-
|
22
|
-
# Exposes a presenter method to the view for each provided name
|
23
|
-
def expose_presenter(*method_names)
|
24
|
-
presenters_module = Module.new do
|
25
|
-
method_names.each do |name|
|
26
|
-
module_eval <<-ruby_eval, __FILE__, __LINE__ + 1
|
27
|
-
def #{name}
|
28
|
-
controller.send('#{Presenter.method_name_for(name)}')
|
29
|
-
end
|
30
|
-
ruby_eval
|
31
|
-
end
|
32
|
-
end
|
33
|
-
helper presenters_module
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
# Defines a private presenter method that invokes the provided block
|
39
|
-
def define_presenter_method!(methods, &block)
|
40
|
-
if methods.size != 1
|
41
|
-
Kernel.abort "[ERROR] You are providing a block for the `#{methods.join(', ')}` methods, " \
|
42
|
-
"but you can only provide a block for a single presenter at a time.\n #{caller.second}"
|
43
|
-
end
|
44
|
-
presenter_method = methods.first
|
45
|
-
define_method presenter_method, &block
|
46
|
-
private presenter_method
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# Name of the presenter methods
|
51
|
-
def self.method_name_for(name)
|
52
|
-
"#{name}_presenter"
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|