actionpresenter 0.1.0 → 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 +1 -0
- data/Gemfile.lock +3 -0
- data/README.md +118 -0
- data/VERSION +1 -1
- data/actionpresenter.gemspec +9 -6
- data/lib/action_presenter.rb +2 -1
- data/lib/action_presenter/base.rb +16 -12
- data/lib/action_presenter/helpers.rb +49 -11
- data/lib/action_presenter/template_delegation.rb +3 -2
- data/spec/helpers/helpers_spec.rb +76 -8
- data/spec/presenters/base_spec.rb +186 -0
- data/spec/spec_helper.rb +7 -1
- data/spec/support/models.rb +26 -1
- data/spec/support/presenters.rb +6 -0
- metadata +19 -5
- data/README.rdoc +0 -21
- data/spec/presenters/.keep +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d45c11dc2f8df677e12c23a12353e25c03c8a7d
|
4
|
+
data.tar.gz: 78851cae029d7bf731fb22404e83260f67d32008
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 229ae78380434fd9be7f881a04794ea631915713aa444413b6a9be9fe7862e1e776a3cb7836af25da9ef7c03085cce00cbe5b9073e98642d53411ef0c9df2640
|
7
|
+
data.tar.gz: 3b936e60e9591507261277abfeb93080c1bb1db70291016182b9482690ee438f3d699e7e0cccaa322260070a6be8ffcd9a16104c1d5ec2b8ed831011f0c8529f
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -41,6 +41,8 @@ GEM
|
|
41
41
|
thread_safe (~> 0.3, >= 0.3.1)
|
42
42
|
diff-lcs (1.2.5)
|
43
43
|
erubis (2.7.0)
|
44
|
+
faker (1.4.3)
|
45
|
+
i18n (~> 0.5)
|
44
46
|
faraday (0.9.0)
|
45
47
|
multipart-post (>= 1.2, < 3)
|
46
48
|
git (1.2.8)
|
@@ -158,6 +160,7 @@ PLATFORMS
|
|
158
160
|
|
159
161
|
DEPENDENCIES
|
160
162
|
bundler (~> 1.0)
|
163
|
+
faker
|
161
164
|
jeweler (~> 2.0.1)
|
162
165
|
pry-byebug
|
163
166
|
rails (~> 4.0)
|
data/README.md
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# ActionPresenter
|
2
|
+
|
3
|
+
[![Code Climate](https://codeclimate.com/github/tlux/actionpresenter/badges/gpa.svg)](https://codeclimate.com/github/tlux/actionpresenter)
|
4
|
+
|
5
|
+
A lightweight presenter implementation for Ruby on Rails.
|
6
|
+
|
7
|
+
### Requirements
|
8
|
+
* Ruby on Rails >= 4
|
9
|
+
|
10
|
+
### Installation
|
11
|
+
1. Add this to your Gemfile:
|
12
|
+
```ruby
|
13
|
+
gem 'actionpresenter', '0.2.0'
|
14
|
+
```
|
15
|
+
|
16
|
+
2. Create the `app/presenters` folder in your project which will be holding your presenter files.
|
17
|
+
|
18
|
+
### Usage
|
19
|
+
Lets say we have a model named `User`.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class User < ActiveRecord::Base
|
23
|
+
# Model with columns first_name and last_name
|
24
|
+
# ...
|
25
|
+
def name
|
26
|
+
"#{first_name} #{last_name}".strip
|
27
|
+
end
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
##### Creating a Presenter
|
32
|
+
In our views we always want to display the name of a specific user with a link to his profile. The simplest way of providing similar behavior to all our views is to create a `UserPresenter` in `app/presenters/user_presenter.rb`.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class UserPresenter < ActionPresenter::Base
|
36
|
+
presents :user
|
37
|
+
|
38
|
+
def name(linked = true)
|
39
|
+
h.link_to_if linked, super(), h.user_profile_path(user)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
We can use `super` as alternative to `user.name` or `object.name` in this case to get the value from the original `User` instance.
|
45
|
+
|
46
|
+
All helpers from the view context are available through the `template` accessor (or its short variant `h`).
|
47
|
+
|
48
|
+
You can also make all view helpers available to the presenter as if they were mixed into the presenter by including `ActionPresenter::TemplateDelegation`:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
class UserPresenter < ActionPresenter::Base
|
52
|
+
include ActionPresenter::TemplateDelegation
|
53
|
+
|
54
|
+
presents :user
|
55
|
+
|
56
|
+
def name(linked = true)
|
57
|
+
link_to_if linked, super(), user_profile_path(user)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
##### Using Presenters in a View
|
63
|
+
A presenter can be embedded into a view by using the `present` helper.
|
64
|
+
```erb
|
65
|
+
<% present @user do |user| %>
|
66
|
+
<strong>Name:</strong>
|
67
|
+
<br>
|
68
|
+
<%= user.name %>
|
69
|
+
<% end %>
|
70
|
+
```
|
71
|
+
You can also use reference to any custom presenter:
|
72
|
+
```erb
|
73
|
+
<% present @user, with: Admin::UserPresenter do |user| %>
|
74
|
+
...
|
75
|
+
<% end %>
|
76
|
+
```
|
77
|
+
|
78
|
+
Alternatively, you can use:
|
79
|
+
```erb
|
80
|
+
<% present [:admin, @user] do |user| %>
|
81
|
+
...
|
82
|
+
<% end %>
|
83
|
+
```
|
84
|
+
|
85
|
+
If you have a collection of objects you can rely on `present_collection` which will wrap each element of the collection with its own presenter:
|
86
|
+
|
87
|
+
```erb
|
88
|
+
<% present_collection(@users).each do |user| %>
|
89
|
+
...
|
90
|
+
<% end %>
|
91
|
+
```
|
92
|
+
|
93
|
+
The following snippet will search for matching presenter classes within the `Admin` module:
|
94
|
+
```erb
|
95
|
+
<% present_collection(@users, scope: :admin).each do |user| %>
|
96
|
+
...
|
97
|
+
<% end %>
|
98
|
+
```
|
99
|
+
|
100
|
+
##### Keeping it DRY
|
101
|
+
To share common functionality between different presenters we encourage you to use presenter concerns. Latter should be put into `app/presenters/concerns`. Note: As this folder is not recognized by the default Rails setup, it has to be added manually to `config/application.rb`:
|
102
|
+
```ruby
|
103
|
+
config.autoload_paths << Rails.root.join('app', 'presenters', 'concerns').to_s
|
104
|
+
```
|
105
|
+
|
106
|
+
### Contributing to ActionPresenter
|
107
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
108
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
109
|
+
* Fork the project.
|
110
|
+
* Start a feature/bugfix branch.
|
111
|
+
* Commit and push until you are happy with your contribution.
|
112
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
113
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
114
|
+
|
115
|
+
### Copyright
|
116
|
+
Copyright (c) 2015 Tobias Casper. See LICENSE.txt for
|
117
|
+
further details.
|
118
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/actionpresenter.gemspec
CHANGED
@@ -2,28 +2,28 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: actionpresenter 0.
|
5
|
+
# stub: actionpresenter 0.2.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "actionpresenter"
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.2.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Tobias Casper"]
|
14
|
-
s.date = "
|
14
|
+
s.date = "2015-01-18"
|
15
15
|
s.description = "A lightweight presenter implementation for Ruby on Rails."
|
16
16
|
s.email = "tobias.casper@gmail.com"
|
17
17
|
s.extra_rdoc_files = [
|
18
18
|
"LICENSE.txt",
|
19
|
-
"README.
|
19
|
+
"README.md"
|
20
20
|
]
|
21
21
|
s.files = [
|
22
22
|
".document",
|
23
23
|
"Gemfile",
|
24
24
|
"Gemfile.lock",
|
25
25
|
"LICENSE.txt",
|
26
|
-
"README.
|
26
|
+
"README.md",
|
27
27
|
"Rakefile",
|
28
28
|
"VERSION",
|
29
29
|
"actionpresenter.gemspec",
|
@@ -33,7 +33,7 @@ Gem::Specification.new do |s|
|
|
33
33
|
"lib/action_presenter/template_delegation.rb",
|
34
34
|
"lib/actionpresenter.rb",
|
35
35
|
"spec/helpers/helpers_spec.rb",
|
36
|
-
"spec/presenters
|
36
|
+
"spec/presenters/base_spec.rb",
|
37
37
|
"spec/spec_helper.rb",
|
38
38
|
"spec/support/faked_view_context.rb",
|
39
39
|
"spec/support/models.rb",
|
@@ -53,12 +53,14 @@ Gem::Specification.new do |s|
|
|
53
53
|
s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
|
54
54
|
s.add_development_dependency(%q<rspec-rails>, ["~> 3.0"])
|
55
55
|
s.add_development_dependency(%q<pry-byebug>, [">= 0"])
|
56
|
+
s.add_development_dependency(%q<faker>, [">= 0"])
|
56
57
|
else
|
57
58
|
s.add_dependency(%q<rails>, ["~> 4.0"])
|
58
59
|
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
59
60
|
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
60
61
|
s.add_dependency(%q<rspec-rails>, ["~> 3.0"])
|
61
62
|
s.add_dependency(%q<pry-byebug>, [">= 0"])
|
63
|
+
s.add_dependency(%q<faker>, [">= 0"])
|
62
64
|
end
|
63
65
|
else
|
64
66
|
s.add_dependency(%q<rails>, ["~> 4.0"])
|
@@ -66,6 +68,7 @@ Gem::Specification.new do |s|
|
|
66
68
|
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
67
69
|
s.add_dependency(%q<rspec-rails>, ["~> 3.0"])
|
68
70
|
s.add_dependency(%q<pry-byebug>, [">= 0"])
|
71
|
+
s.add_dependency(%q<faker>, [">= 0"])
|
69
72
|
end
|
70
73
|
end
|
71
74
|
|
data/lib/action_presenter.rb
CHANGED
@@ -9,6 +9,7 @@ module ActionPresenter
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
Dir[ActionPresenter.root.join('lib
|
12
|
+
required_files = Dir[ActionPresenter.root.join('lib/action_presenter/*.rb')]
|
13
|
+
required_files.each do |filename|
|
13
14
|
require filename
|
14
15
|
end
|
@@ -6,37 +6,41 @@ class ActionPresenter::Base
|
|
6
6
|
end
|
7
7
|
|
8
8
|
attr_reader :object, :options
|
9
|
-
delegate :present, to: :h
|
10
|
-
protected :present, :options
|
9
|
+
delegate :present, :present_collection, to: :h
|
10
|
+
protected :present, :present_collection, :options
|
11
11
|
|
12
12
|
def self.presents(name)
|
13
13
|
define_method name do
|
14
|
-
|
14
|
+
object
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.delegate_presented(name, options = {})
|
19
|
-
delegate_opts = options.slice(:to, :prefix, :allow_nil)
|
19
|
+
delegate_opts = options.slice(:to, :prefix, :allow_nil)
|
20
|
+
.reverse_merge(to: :object)
|
20
21
|
delegate name, delegate_opts
|
21
22
|
|
22
23
|
define_method "#{name}_with_presenter" do
|
23
|
-
|
24
|
-
|
24
|
+
object = public_send("#{name}_without_presenter")
|
25
|
+
return if object.nil?
|
26
|
+
helper_options = options.except(*delegate_opts.keys)
|
27
|
+
if object.respond_to?(:each)
|
28
|
+
present_collection object, helper_options
|
29
|
+
else
|
30
|
+
present object, helper_options
|
31
|
+
end
|
25
32
|
end
|
26
33
|
|
27
34
|
alias_method_chain name, :presenter
|
28
35
|
end
|
29
36
|
|
30
37
|
def inspect
|
31
|
-
|
32
|
-
|
33
|
-
else
|
34
|
-
"#<#{self.class.name} object: #{object.inspect}>"
|
35
|
-
end
|
38
|
+
object_str = " object: #{object.inspect}" unless object.nil?
|
39
|
+
"#<#{self.class.name}#{object_str}>"
|
36
40
|
end
|
37
41
|
|
38
42
|
def method_missing(name, *args, &block)
|
39
|
-
if object
|
43
|
+
if object && object.respond_to?(name, false)
|
40
44
|
object.send(name, *args, &block)
|
41
45
|
else
|
42
46
|
super
|
@@ -1,22 +1,60 @@
|
|
1
1
|
require 'action_view/base'
|
2
2
|
|
3
3
|
module ActionPresenter::Helpers
|
4
|
+
VALID_OPTIONS = :with, :scope
|
5
|
+
|
4
6
|
def present(*args)
|
5
7
|
options = args.extract_options!
|
6
|
-
|
7
|
-
object =
|
8
|
-
presenter_class = options
|
9
|
-
|
10
|
-
|
11
|
-
item.is_a?(Symbol) ? item.to_s.camelize : item.class.name
|
12
|
-
}.join('::')
|
13
|
-
"#{scoped_class_name}Presenter"
|
14
|
-
end
|
15
|
-
presenter_class = presenter_class.to_s.constantize unless presenter_class.is_a?(Class)
|
16
|
-
presenter = presenter_class.new(self, object, options)
|
8
|
+
scoped_object = Array(args.first)
|
9
|
+
object = scoped_object.last
|
10
|
+
presenter_class = extract_presenter_class(scoped_object, options)
|
11
|
+
presenter_opts = options.except(*VALID_OPTIONS)
|
12
|
+
presenter = presenter_class.new(self, object, presenter_opts)
|
17
13
|
yield(presenter) if block_given?
|
18
14
|
presenter
|
19
15
|
end
|
16
|
+
|
17
|
+
def present_collection(collection, options = {}, &block)
|
18
|
+
if collection.nil? || !collection.respond_to?(:to_a)
|
19
|
+
fail ArgumentError, 'No valid collection specified'
|
20
|
+
end
|
21
|
+
|
22
|
+
collection.to_a.compact.map do |object|
|
23
|
+
present(object, options, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def extract_presenter_class(scope_and_object, options)
|
30
|
+
scope_and_object = Array(scope_and_object).compact
|
31
|
+
object = scope_and_object.last
|
32
|
+
|
33
|
+
presenter_class = options.fetch(:with) do
|
34
|
+
if object.nil?
|
35
|
+
fail ArgumentError, 'Neither object nor presenter class specified'
|
36
|
+
end
|
37
|
+
|
38
|
+
if options[:scope]
|
39
|
+
fail ArgumentError, 'You cannot use :scope in conjunction with ' \
|
40
|
+
'a scoped object' if scope_and_object.many?
|
41
|
+
scope_and_object = [*options[:scope], object]
|
42
|
+
end
|
43
|
+
|
44
|
+
scoped_class_name = scope_and_object.map do |item|
|
45
|
+
case item
|
46
|
+
when Symbol then item.to_s.camelize
|
47
|
+
when Class then item.name
|
48
|
+
else item.class.name
|
49
|
+
end
|
50
|
+
end.join('::')
|
51
|
+
|
52
|
+
"#{scoped_class_name}Presenter"
|
53
|
+
end
|
54
|
+
|
55
|
+
return presenter_class.to_s.constantize unless presenter_class.is_a?(Class)
|
56
|
+
presenter_class
|
57
|
+
end
|
20
58
|
end
|
21
59
|
|
22
60
|
ActionView::Base.send :include, ActionPresenter::Helpers
|
@@ -16,8 +16,9 @@ module ActionPresenter::TemplateDelegation
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def respond_to_missing_with_template_delegation?(name,
|
19
|
+
def respond_to_missing_with_template_delegation?(name,
|
20
|
+
include_private = false)
|
20
21
|
@_template.respond_to?(name, include_private) ||
|
21
|
-
|
22
|
+
respond_to_missing_without_template_delegation?(name, include_private)
|
22
23
|
end
|
23
24
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe ActionPresenter::Helpers do
|
4
|
-
|
4
|
+
subject(:helper) { FakedViewContext.new }
|
5
5
|
|
6
6
|
describe '#present' do
|
7
7
|
it 'is available in view context' do
|
@@ -9,7 +9,8 @@ describe ActionPresenter::Helpers do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
it 'cannot be invoked without arguments' do
|
12
|
-
expect { helper.present }.to raise_error
|
12
|
+
expect { helper.present }.to raise_error(
|
13
|
+
ArgumentError, 'Neither object nor presenter class specified')
|
13
14
|
end
|
14
15
|
|
15
16
|
it 'initializes a presenter by a given object' do
|
@@ -18,7 +19,9 @@ describe ActionPresenter::Helpers do
|
|
18
19
|
end
|
19
20
|
|
20
21
|
it 'fails initialization if no matching presenter class could be found' do
|
21
|
-
expect { helper.present(Location.new) }.to
|
22
|
+
expect { helper.present(Location.new) }.to(
|
23
|
+
raise_error(/uninitialized constant/)
|
24
|
+
)
|
22
25
|
end
|
23
26
|
|
24
27
|
it 'initializes a presenter by a given object and presenter class' do
|
@@ -36,17 +39,82 @@ describe ActionPresenter::Helpers do
|
|
36
39
|
expect(presenter).to be_an Admin::PersonPresenter
|
37
40
|
end
|
38
41
|
|
39
|
-
it 'initializes a presenter by a given scoped object and presenter class
|
40
|
-
|
42
|
+
it 'initializes a presenter by a given scoped object and presenter class ' \
|
43
|
+
'name (ignoring the scope)' do
|
44
|
+
presenter = helper.present([:unknown, :scope, Person.new],
|
45
|
+
with: Admin::PersonPresenter)
|
41
46
|
expect(presenter).to be_an Admin::PersonPresenter
|
42
47
|
end
|
43
48
|
|
44
49
|
it 'fails initializing a presenter with an invalidly scoped object' do
|
45
|
-
expect { helper.present([:unknown, :scope, Person.new]) }.to
|
50
|
+
expect { helper.present([:unknown, :scope, Person.new]) }.to(
|
51
|
+
raise_error(/uninitialized constant/)
|
52
|
+
)
|
46
53
|
end
|
47
54
|
|
48
|
-
it 'takes a block and yields the just initialized presenter instance as
|
49
|
-
|
55
|
+
it 'takes a block and yields the just initialized presenter instance as ' \
|
56
|
+
'argument' do
|
57
|
+
expect { |block| helper.present(Person.new, &block) }.to(
|
58
|
+
yield_with_args(PersonPresenter)
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'cannot use :scope together with a scoped object' do
|
63
|
+
expect { helper.present([:admin, Person.new], scope: :something) }.to(
|
64
|
+
raise_error('You cannot use :scope in conjunction with a scoped object')
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#present_collection' do
|
70
|
+
it 'is available in view context' do
|
71
|
+
expect(helper).to respond_to(:present_collection)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'cannot be invoked without arguments' do
|
75
|
+
expect { helper.present_collection }.to raise_error(
|
76
|
+
ArgumentError, /wrong number of arguments/)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'takes a collection as first argument' do
|
80
|
+
expect(helper.present_collection([Person.new, Person.new]))
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'does not take objects as first argument that do not respond to to_a' do
|
84
|
+
expect { helper.present_collection(Person.new) }.to raise_error(
|
85
|
+
ArgumentError, 'No valid collection specified'
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'initializes a collection of presenters by their given objects' do
|
90
|
+
items = helper.present_collection [Person.new, Company.new]
|
91
|
+
|
92
|
+
expect(items.first).to be_a PersonPresenter
|
93
|
+
expect(items.last).to be_a CompanyPresenter
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'ignores nil elements in the collection' do
|
97
|
+
items = helper.present_collection [Person.new, nil, Company.new]
|
98
|
+
|
99
|
+
expect(items.count).to eq 2
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'initializes a collection of presenters by their given objects and ' \
|
103
|
+
'presenter class' do
|
104
|
+
items = helper.present_collection [Person.new, Person.new],
|
105
|
+
with: Admin::PersonPresenter
|
106
|
+
expect(
|
107
|
+
items.all? { |item| item.is_a?(Admin::PersonPresenter) }
|
108
|
+
).to be true
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'initializes a collection of presenters by their given object and ' \
|
112
|
+
'presenter class name' do
|
113
|
+
items = helper.present_collection [Person.new, Person.new],
|
114
|
+
with: 'Admin::PersonPresenter'
|
115
|
+
expect(
|
116
|
+
items.all? { |item| item.is_a?(Admin::PersonPresenter) }
|
117
|
+
).to be true
|
50
118
|
end
|
51
119
|
end
|
52
120
|
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActionPresenter::Base do
|
4
|
+
let(:view_context) { FakedViewContext.new }
|
5
|
+
|
6
|
+
it 'cannot be initialized without argument' do
|
7
|
+
expect { ActionPresenter::Base.new }.to raise_error(
|
8
|
+
ArgumentError, /wrong number of arguments/
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '.presents' do
|
13
|
+
let(:presenter_class) { Class.new(ActionPresenter::Base) }
|
14
|
+
|
15
|
+
before :each do
|
16
|
+
presenter_class.presents :person
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'creates an alias for #object on a particular presenter class' do
|
20
|
+
expect(presenter_class.instance_methods).to include :person
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns the original object' do
|
24
|
+
person = Person.new
|
25
|
+
presenter = presenter_class.new(view_context, person)
|
26
|
+
expect(presenter.person).to eq person
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '.delegate_presented' do
|
31
|
+
let(:person) { Person.new }
|
32
|
+
let!(:presenter_class) { Class.new(ActionPresenter::Base) }
|
33
|
+
|
34
|
+
before :each do
|
35
|
+
presenter_class.presents :person
|
36
|
+
end
|
37
|
+
|
38
|
+
subject(:presenter) { presenter_class.new(view_context, person) }
|
39
|
+
|
40
|
+
it 'delegates to the original object and wraps it in a presenter' do
|
41
|
+
presenter_class.delegate_presented :company
|
42
|
+
|
43
|
+
expect(presenter.company).to be_a CompanyPresenter
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'delegates to the original collection and wraps each element in its' \
|
47
|
+
'own presenter' do
|
48
|
+
presenter_class.delegate_presented :associates
|
49
|
+
|
50
|
+
expect(presenter.associates).to be_an Array
|
51
|
+
expect(presenter.associates.all? { |item|
|
52
|
+
item.is_a?(PersonPresenter)
|
53
|
+
}).to be true
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'delegates to the original collection and wraps each element in its ' \
|
57
|
+
'own custom defined presenter' do
|
58
|
+
presenter_class.delegate_presented :associates, scope: :admin
|
59
|
+
|
60
|
+
expect(presenter.associates.all? { |item|
|
61
|
+
item.is_a?(Admin::PersonPresenter)
|
62
|
+
}).to be true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#object', '#to_model' do
|
67
|
+
let(:person) { Person.new }
|
68
|
+
subject(:presenter) { ActionPresenter::Base.new(view_context, person) }
|
69
|
+
|
70
|
+
it 'includes the original object' do
|
71
|
+
expect(presenter.object).to eq person
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'includes the original object (through the #to_model alias)' do
|
75
|
+
expect(presenter.to_model).to eq person
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#template', '#h' do
|
80
|
+
subject(:presenter) { ActionPresenter::Base.new(view_context) }
|
81
|
+
|
82
|
+
it 'is protected' do
|
83
|
+
expect(presenter.protected_methods).to include :template
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'is protected (#h alias)' do
|
87
|
+
expect(presenter.protected_methods).to include :h
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'returns the view context object' do
|
91
|
+
expect(presenter.send(:template)).to eq view_context
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'returns the view context object (through the #h alias)' do
|
95
|
+
expect(presenter.send(:h)).to eq view_context
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#present' do
|
100
|
+
let(:person) { Person.new }
|
101
|
+
subject(:presenter) { PersonPresenter.new(view_context, person) }
|
102
|
+
let :other_presenter do
|
103
|
+
presenter.send(:present, presenter.object.company)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'is protected' do
|
107
|
+
expect(presenter.protected_methods).to include :present
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'returns a presenter object' do
|
111
|
+
expect(other_presenter).to be_a CompanyPresenter
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'returns a presenter object with the presented object wrapped inside' do
|
115
|
+
expect(other_presenter.object).to eq presenter.object.company
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe '#present_collection' do
|
120
|
+
let(:presenter) { PersonPresenter.new(view_context, Person.new) }
|
121
|
+
|
122
|
+
it 'is protected' do
|
123
|
+
expect(presenter.protected_methods).to include :present_collection
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'returns a collection of presenter objects' do
|
127
|
+
other_presenter_collection = presenter.send :present_collection,
|
128
|
+
presenter.object.associates
|
129
|
+
|
130
|
+
expect(other_presenter_collection.all? { |item|
|
131
|
+
item.is_a?(PersonPresenter)
|
132
|
+
}).to be true
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'dynamic delegation' do
|
137
|
+
let(:person) { Person.new }
|
138
|
+
let! :presenter_class do
|
139
|
+
Class.new(ActionPresenter::Base) do
|
140
|
+
presents :person
|
141
|
+
end
|
142
|
+
end
|
143
|
+
subject(:presenter) { presenter_class.new(view_context, person) }
|
144
|
+
|
145
|
+
it 'delegates to original object unless method has been overridden in ' \
|
146
|
+
'presenter' do
|
147
|
+
expect(presenter.name).to eq person.name
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'allows using super to delegate to methods of the original object' do
|
151
|
+
presenter_class.class_eval do
|
152
|
+
def name
|
153
|
+
"Name: #{super}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
expect(presenter.name).to eq "Name: #{person.name}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'template delegation' do
|
162
|
+
before :each do
|
163
|
+
allow(view_context).to receive(:content_tag) do |tag, content, opts = {}|
|
164
|
+
"<#{tag}>#{content}</#{tag}>"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
let! :presenter_class do
|
169
|
+
Class.new(ActionPresenter::Base) do
|
170
|
+
include ActionPresenter::TemplateDelegation
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
let(:presenter) { presenter_class.new(view_context, Person.new) }
|
175
|
+
|
176
|
+
it 'delegates unknown presenter methods to view context' do
|
177
|
+
presenter_class.class_eval do
|
178
|
+
def name
|
179
|
+
content_tag :strong, super
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
expect { presenter.name }.not_to raise_error
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,7 +3,13 @@ require 'bundler/setup'
|
|
3
3
|
require 'pry-byebug'
|
4
4
|
require 'active_model'
|
5
5
|
require 'action_presenter'
|
6
|
+
require 'faker'
|
6
7
|
|
7
8
|
require 'support/faked_view_context'
|
8
9
|
require 'support/models'
|
9
|
-
require 'support/presenters'
|
10
|
+
require 'support/presenters'
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.filter_run focus: true
|
14
|
+
config.run_all_when_everything_filtered = true
|
15
|
+
end
|
data/spec/support/models.rb
CHANGED
@@ -1,7 +1,32 @@
|
|
1
1
|
class Person
|
2
2
|
include ActiveModel::Model
|
3
|
+
|
4
|
+
def name
|
5
|
+
@name ||= Faker::Name.name
|
6
|
+
end
|
7
|
+
|
8
|
+
def address
|
9
|
+
@address ||= Faker::Address.street_address
|
10
|
+
end
|
11
|
+
|
12
|
+
def company
|
13
|
+
@company ||= Company.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def associates
|
17
|
+
@associates ||= 3.times.collect { Person.new }
|
18
|
+
end
|
3
19
|
end
|
4
20
|
|
5
21
|
class Location
|
6
22
|
include ActiveModel::Model
|
7
|
-
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Company
|
26
|
+
include ActiveModel::Model
|
27
|
+
|
28
|
+
def name
|
29
|
+
@name ||= Faker::Company.name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
data/spec/support/presenters.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actionpresenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tobias Casper
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-01-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -80,19 +80,33 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: faker
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
description: A lightweight presenter implementation for Ruby on Rails.
|
84
98
|
email: tobias.casper@gmail.com
|
85
99
|
executables: []
|
86
100
|
extensions: []
|
87
101
|
extra_rdoc_files:
|
88
102
|
- LICENSE.txt
|
89
|
-
- README.
|
103
|
+
- README.md
|
90
104
|
files:
|
91
105
|
- ".document"
|
92
106
|
- Gemfile
|
93
107
|
- Gemfile.lock
|
94
108
|
- LICENSE.txt
|
95
|
-
- README.
|
109
|
+
- README.md
|
96
110
|
- Rakefile
|
97
111
|
- VERSION
|
98
112
|
- actionpresenter.gemspec
|
@@ -102,7 +116,7 @@ files:
|
|
102
116
|
- lib/action_presenter/template_delegation.rb
|
103
117
|
- lib/actionpresenter.rb
|
104
118
|
- spec/helpers/helpers_spec.rb
|
105
|
-
- spec/presenters
|
119
|
+
- spec/presenters/base_spec.rb
|
106
120
|
- spec/spec_helper.rb
|
107
121
|
- spec/support/faked_view_context.rb
|
108
122
|
- spec/support/models.rb
|
data/README.rdoc
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
= ActionPresenter
|
2
|
-
|
3
|
-
{<img src="https://codeclimate.com/github/tlux/actionpresenter/badges/gpa.svg" />}[https://codeclimate.com/github/tlux/actionpresenter]
|
4
|
-
|
5
|
-
A lightweight presenter implementation for Ruby on Rails.
|
6
|
-
|
7
|
-
== Contributing to actionpresenter
|
8
|
-
|
9
|
-
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
10
|
-
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
11
|
-
* Fork the project.
|
12
|
-
* Start a feature/bugfix branch.
|
13
|
-
* Commit and push until you are happy with your contribution.
|
14
|
-
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
15
|
-
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
16
|
-
|
17
|
-
== Copyright
|
18
|
-
|
19
|
-
Copyright (c) 2014 Tobias Casper. See LICENSE.txt for
|
20
|
-
further details.
|
21
|
-
|
data/spec/presenters/.keep
DELETED
File without changes
|