draper 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Readme.markdown +49 -6
- data/lib/draper/base.rb +14 -9
- data/lib/draper/version.rb +1 -1
- data/spec/base_spec.rb +29 -24
- data/spec/samples/application_helper.rb +5 -0
- data/spec/samples/decorator_application_helper.rb +5 -0
- data/spec/samples/decorator_with_allows.rb +3 -0
- data/spec/samples/decorator_with_denies.rb +15 -0
- data/spec/spec_helper.rb +3 -0
- metadata +89 -75
data/Readme.markdown
CHANGED
@@ -1,16 +1,59 @@
|
|
1
|
-
|
2
|
-
================
|
1
|
+
## Heads Up!
|
3
2
|
|
4
|
-
This gem
|
3
|
+
This gem is not yet production ready. The API is sure to change quickly and there could be side-effects I haven't discovered yet.
|
5
4
|
|
6
|
-
|
5
|
+
Please only use it if you're playing around and helping me experiment! Thanks :)
|
7
6
|
|
8
|
-
|
7
|
+
# Draper
|
9
8
|
|
10
|
-
|
9
|
+
This gem makes it easy to apply the decorator pattern to the models in a Rails application. This gives you three wins:
|
10
|
+
|
11
|
+
1. Replace most helpers with an object-oriented approach
|
12
|
+
2. Filter data at the presentation level
|
13
|
+
3. Enforce an interface between your controllers and view templates.
|
14
|
+
|
15
|
+
## 1. Object Oriented Helpers
|
16
|
+
|
17
|
+
Why hate helpers? In Ruby/Rails we approach everything from an Object-Oriented perspective, then with helpers we get procedural.The job of a helper is to take in data and output a presentation-ready string. We can do that job in an OO style with a decorator.
|
11
18
|
|
12
19
|
In general, a decorator wraps an object with presentation-related accessor methods. For instance, if you had an `Article` object, then a decorator might add instance methods like `.formatted_published_at` or `.formatted_title` that output actual HTML.
|
13
20
|
|
21
|
+
For example:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class ArticleDecorator < Draper::Base
|
25
|
+
def formatted_published_at
|
26
|
+
date = content_tag(:span, published_at.strftime("%A, %B %e").squeeze(" "), :class => 'date')
|
27
|
+
time = content_tag(:span, published_at.strftime("%l:%M%p"), :class => 'time').delete(" ")
|
28
|
+
content_tag :span, date + time, :class => 'created_at'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
## 2. View-Layer Data Filtering
|
34
|
+
|
35
|
+
Have you ever written a `to_xml` or `to_json` method in your model? Did it feel weird to put what is essentially view logic in your model?
|
36
|
+
|
37
|
+
Or, in the course of formatting this data, did you wish you could access `current_user` down in the model? Maybe for guests your `to_json` is only going to show three attributes, but if the user is an admin they get to see them all.
|
38
|
+
|
39
|
+
How would you handle this in the model layer? You'd probably pass the `current_user` or some role/flag down to `to_json`. That should still feel slimy.
|
40
|
+
|
41
|
+
When you use a decorator you have the power of a Ruby object but it's a part of the view layer. This is where your `to_xml` belongs. It has access to the core data from the model, but it also knows about `current_user` because it can see the `ApplicationHelper` where that method is typically defined.
|
42
|
+
|
43
|
+
For example:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
class ArticleDecorator < Draper::Base
|
47
|
+
ADMIN_VISIBLE_ATTRIBUTES = [:title, :body, :author, :status]
|
48
|
+
PUBLIC_VISIBLE_ATTRIBUTES = [:title, :body]
|
49
|
+
|
50
|
+
def to_xml
|
51
|
+
attr_set = current_user.admin? ? ADMIN_VISIBLE_ATTRIBUTES : PUBLIC_VISIBLE_ATTRIBUTES
|
52
|
+
self.subject.to_xml(:only => attr_set)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
14
57
|
## How is it implemented?
|
15
58
|
|
16
59
|
To implement the pattern in Rails we can:
|
data/lib/draper/base.rb
CHANGED
@@ -3,6 +3,7 @@ module Draper
|
|
3
3
|
include ActionView::Helpers::TagHelper
|
4
4
|
include ActionView::Helpers::UrlHelper
|
5
5
|
include ActionView::Helpers::TextHelper
|
6
|
+
include ApplicationHelper #if defined?(ApplicationHelper)
|
6
7
|
|
7
8
|
require 'active_support/core_ext/class/attribute'
|
8
9
|
class_attribute :denied, :allowed
|
@@ -10,6 +11,11 @@ module Draper
|
|
10
11
|
|
11
12
|
DEFAULT_DENIED = Object.new.methods << :method_missing
|
12
13
|
self.denied = DEFAULT_DENIED
|
14
|
+
|
15
|
+
def initialize(subject)
|
16
|
+
self.source = subject
|
17
|
+
build_methods
|
18
|
+
end
|
13
19
|
|
14
20
|
def self.denies(*input_denied)
|
15
21
|
raise ArgumentError, "Specify at least one method (as a symbol) to exclude when using denies" if input_denied.empty?
|
@@ -19,15 +25,14 @@ module Draper
|
|
19
25
|
|
20
26
|
def self.allows(*input_allows)
|
21
27
|
raise ArgumentError, "Specify at least one method (as a symbol) to allow when using allows" if input_allows.empty?
|
22
|
-
|
28
|
+
raise ArgumentError, "Use either 'allows' or 'denies', but not both." unless (self.denied == DEFAULT_DENIED)
|
23
29
|
self.allowed = input_allows
|
24
30
|
end
|
25
|
-
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
+
|
32
|
+
def self.decorate(input)
|
33
|
+
input.respond_to?(:each) ? input.map{|i| new(i)} : new(input)
|
34
|
+
end
|
35
|
+
|
31
36
|
private
|
32
37
|
def select_methods
|
33
38
|
self.allowed || (source.public_methods - denied)
|
@@ -36,8 +41,8 @@ module Draper
|
|
36
41
|
def build_methods
|
37
42
|
select_methods.each do |method|
|
38
43
|
(class << self; self; end).class_eval do
|
39
|
-
define_method method do |*args|
|
40
|
-
source.send method, *args
|
44
|
+
define_method method do |*args, &block|
|
45
|
+
source.send method, *args, &block
|
41
46
|
end
|
42
47
|
end
|
43
48
|
end
|
data/lib/draper/version.rb
CHANGED
data/spec/base_spec.rb
CHANGED
@@ -3,12 +3,31 @@ require 'draper'
|
|
3
3
|
|
4
4
|
describe Draper::Base do
|
5
5
|
subject{ Draper::Base.new(source) }
|
6
|
-
let(:source){ "Sample String" }
|
6
|
+
let(:source){ "Sample String" }
|
7
7
|
|
8
8
|
it "should return the wrapped object when asked for source" do
|
9
9
|
subject.source.should == source
|
10
10
|
end
|
11
11
|
|
12
|
+
it "should wrap source methods so they still accept blocks" do
|
13
|
+
subject.gsub("Sample"){|match| "Super"}.should == "Super String"
|
14
|
+
end
|
15
|
+
|
16
|
+
context ".draper" do
|
17
|
+
it "should return a collection of wrapped objects when given a collection of source objects" do
|
18
|
+
sources = ["one", "two", "three"]
|
19
|
+
output = Draper::Base.decorate(sources)
|
20
|
+
output.should respond_to(:each)
|
21
|
+
output.size.should == sources.size
|
22
|
+
output.each{ |decorated| decorated.should be_instance_of(Draper::Base) }
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return a single wrapped object when given a single source object" do
|
26
|
+
output = Draper::Base.decorate(source)
|
27
|
+
output.should be_instance_of(Draper::Base)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
12
31
|
it "echos the methods of the wrapped class" do
|
13
32
|
source.methods.each do |method|
|
14
33
|
subject.should respond_to(method)
|
@@ -23,21 +42,6 @@ describe Draper::Base do
|
|
23
42
|
|
24
43
|
describe "a sample usage with denies" do
|
25
44
|
before(:all) do
|
26
|
-
class DecoratorWithDenies < Draper::Base
|
27
|
-
denies :upcase
|
28
|
-
|
29
|
-
def sample_content
|
30
|
-
content_tag :span, "Hello, World!"
|
31
|
-
end
|
32
|
-
|
33
|
-
def sample_link
|
34
|
-
link_to "Hello", "/World"
|
35
|
-
end
|
36
|
-
|
37
|
-
def sample_truncate
|
38
|
-
ActionView::Helpers::TextHelper.truncate("Once upon a time", :length => 7)
|
39
|
-
end
|
40
|
-
end
|
41
45
|
end
|
42
46
|
|
43
47
|
let(:subject_with_denies){ DecoratorWithDenies.new(source) }
|
@@ -69,12 +73,6 @@ describe Draper::Base do
|
|
69
73
|
end
|
70
74
|
|
71
75
|
describe "a sample usage with allows" do
|
72
|
-
before(:all) do
|
73
|
-
class DecoratorWithAllows < Draper::Base
|
74
|
-
allows :upcase
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
76
|
let(:subject_with_allows){ DecoratorWithAllows.new(source) }
|
79
77
|
|
80
78
|
it "should echo the allowed method" do
|
@@ -100,14 +98,14 @@ describe Draper::Base do
|
|
100
98
|
}
|
101
99
|
|
102
100
|
let(:using_allows_then_denies){
|
103
|
-
class
|
101
|
+
class DecoratorWithAllowsAndDenies < Draper::Base
|
104
102
|
allows :upcase
|
105
103
|
denies :downcase
|
106
104
|
end
|
107
105
|
}
|
108
106
|
|
109
107
|
let(:using_denies_then_allows){
|
110
|
-
class
|
108
|
+
class DecoratorWithDeniesAndAllows < Draper::Base
|
111
109
|
denies :downcase
|
112
110
|
allows :upcase
|
113
111
|
end
|
@@ -129,4 +127,11 @@ describe Draper::Base do
|
|
129
127
|
expect {using_denies_then_allows}.should raise_error(ArgumentError)
|
130
128
|
end
|
131
129
|
end
|
130
|
+
|
131
|
+
context "in a Rails application" do
|
132
|
+
it "should include ApplicationHelper if one exists" do
|
133
|
+
decorator = DecoratorApplicationHelper.decorate(Object.new)
|
134
|
+
decorator.uses_hello == "Hello, World!"
|
135
|
+
end
|
136
|
+
end
|
132
137
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class DecoratorWithDenies < Draper::Base
|
2
|
+
denies :upcase
|
3
|
+
|
4
|
+
def sample_content
|
5
|
+
content_tag :span, "Hello, World!"
|
6
|
+
end
|
7
|
+
|
8
|
+
def sample_link
|
9
|
+
link_to "Hello", "/World"
|
10
|
+
end
|
11
|
+
|
12
|
+
def sample_truncate
|
13
|
+
ActionView::Helpers::TextHelper.truncate("Once upon a time", :length => 7)
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bundler'
|
3
|
+
require './spec/samples/application_helper.rb'
|
3
4
|
Bundler.require
|
5
|
+
Dir.glob('./spec/samples/*') {|file| require file}
|
4
6
|
require 'active_support'
|
5
7
|
require 'action_view'
|
6
8
|
require 'bundler/setup'
|
9
|
+
|
7
10
|
require 'draper'
|
8
11
|
|
9
12
|
Dir["spec/support/**/*.rb"].each do |file|
|
metadata
CHANGED
@@ -1,113 +1,116 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: draper
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.1
|
3
|
+
version: !ruby/object:Gem::Version
|
5
4
|
prerelease:
|
5
|
+
version: 0.3.2
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Jeff Casimir
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
12
|
+
|
13
|
+
date: 2011-07-10 00:00:00 -04:00
|
13
14
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
- !ruby/object:Gem::Dependency
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
16
17
|
name: rake
|
17
|
-
requirement: &
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
19
|
none: false
|
19
|
-
requirements:
|
20
|
-
- - =
|
21
|
-
- !ruby/object:Gem::Version
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
22
23
|
version: 0.8.7
|
23
24
|
type: :development
|
24
25
|
prerelease: false
|
25
|
-
version_requirements: *
|
26
|
-
- !ruby/object:Gem::Dependency
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
27
28
|
name: rspec
|
28
|
-
requirement: &
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
29
30
|
none: false
|
30
|
-
requirements:
|
31
|
+
requirements:
|
31
32
|
- - ~>
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
+
- !ruby/object:Gem::Version
|
33
34
|
version: 2.0.1
|
34
35
|
type: :development
|
35
36
|
prerelease: false
|
36
|
-
version_requirements: *
|
37
|
-
- !ruby/object:Gem::Dependency
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
38
39
|
name: activesupport
|
39
|
-
requirement: &
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
40
41
|
none: false
|
41
|
-
requirements:
|
42
|
+
requirements:
|
42
43
|
- - ~>
|
43
|
-
- !ruby/object:Gem::Version
|
44
|
+
- !ruby/object:Gem::Version
|
44
45
|
version: 3.0.9
|
45
46
|
type: :development
|
46
47
|
prerelease: false
|
47
|
-
version_requirements: *
|
48
|
-
- !ruby/object:Gem::Dependency
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
49
50
|
name: actionpack
|
50
|
-
requirement: &
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
51
52
|
none: false
|
52
|
-
requirements:
|
53
|
+
requirements:
|
53
54
|
- - ~>
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
+
- !ruby/object:Gem::Version
|
55
56
|
version: 3.0.9
|
56
57
|
type: :development
|
57
58
|
prerelease: false
|
58
|
-
version_requirements: *
|
59
|
-
- !ruby/object:Gem::Dependency
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
60
61
|
name: ruby-debug19
|
61
|
-
requirement: &
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
62
63
|
none: false
|
63
|
-
requirements:
|
64
|
-
- -
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version:
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
67
68
|
type: :development
|
68
69
|
prerelease: false
|
69
|
-
version_requirements: *
|
70
|
-
- !ruby/object:Gem::Dependency
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
71
72
|
name: guard
|
72
|
-
requirement: &
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
73
74
|
none: false
|
74
|
-
requirements:
|
75
|
-
- -
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version:
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
78
79
|
type: :development
|
79
80
|
prerelease: false
|
80
|
-
version_requirements: *
|
81
|
-
- !ruby/object:Gem::Dependency
|
81
|
+
version_requirements: *id006
|
82
|
+
- !ruby/object:Gem::Dependency
|
82
83
|
name: guard-rspec
|
83
|
-
requirement: &
|
84
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
84
85
|
none: false
|
85
|
-
requirements:
|
86
|
-
- -
|
87
|
-
- !ruby/object:Gem::Version
|
88
|
-
version:
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: "0"
|
89
90
|
type: :development
|
90
91
|
prerelease: false
|
91
|
-
version_requirements: *
|
92
|
-
- !ruby/object:Gem::Dependency
|
92
|
+
version_requirements: *id007
|
93
|
+
- !ruby/object:Gem::Dependency
|
93
94
|
name: rb-fsevent
|
94
|
-
requirement: &
|
95
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
95
96
|
none: false
|
96
|
-
requirements:
|
97
|
-
- -
|
98
|
-
- !ruby/object:Gem::Version
|
99
|
-
version:
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: "0"
|
100
101
|
type: :development
|
101
102
|
prerelease: false
|
102
|
-
version_requirements: *
|
103
|
-
description: Draper reimagines the role of helpers in the view layer of a Rails application,
|
104
|
-
|
105
|
-
email:
|
103
|
+
version_requirements: *id008
|
104
|
+
description: Draper reimagines the role of helpers in the view layer of a Rails application, allowing an object-oriented approach rather than procedural.
|
105
|
+
email:
|
106
106
|
- jeff@casimircreative.com
|
107
107
|
executables: []
|
108
|
+
|
108
109
|
extensions: []
|
110
|
+
|
109
111
|
extra_rdoc_files: []
|
110
|
-
|
112
|
+
|
113
|
+
files:
|
111
114
|
- .gitignore
|
112
115
|
- Gemfile
|
113
116
|
- Guardfile
|
@@ -121,38 +124,49 @@ files:
|
|
121
124
|
- lib/generators/draper/model/model_generator.rb
|
122
125
|
- lib/generators/draper/model/templates/model.rb
|
123
126
|
- spec/base_spec.rb
|
127
|
+
- spec/samples/application_helper.rb
|
128
|
+
- spec/samples/decorator_application_helper.rb
|
129
|
+
- spec/samples/decorator_with_allows.rb
|
130
|
+
- spec/samples/decorator_with_denies.rb
|
124
131
|
- spec/spec_helper.rb
|
125
132
|
has_rdoc: true
|
126
133
|
homepage: http://github.com/jcasimir/draper
|
127
134
|
licenses: []
|
135
|
+
|
128
136
|
post_install_message:
|
129
137
|
rdoc_options: []
|
130
|
-
|
138
|
+
|
139
|
+
require_paths:
|
131
140
|
- lib
|
132
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
133
142
|
none: false
|
134
|
-
requirements:
|
135
|
-
- -
|
136
|
-
- !ruby/object:Gem::Version
|
137
|
-
|
138
|
-
segments:
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
hash: -258105380500550580
|
147
|
+
segments:
|
139
148
|
- 0
|
140
|
-
|
141
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
version: "0"
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
151
|
none: false
|
143
|
-
requirements:
|
144
|
-
- -
|
145
|
-
- !ruby/object:Gem::Version
|
146
|
-
|
147
|
-
segments:
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
hash: -258105380500550580
|
156
|
+
segments:
|
148
157
|
- 0
|
149
|
-
|
158
|
+
version: "0"
|
150
159
|
requirements: []
|
160
|
+
|
151
161
|
rubyforge_project: draper
|
152
162
|
rubygems_version: 1.6.2
|
153
163
|
signing_key:
|
154
164
|
specification_version: 3
|
155
165
|
summary: Decorator pattern implmentation for Rails.
|
156
|
-
test_files:
|
166
|
+
test_files:
|
157
167
|
- spec/base_spec.rb
|
168
|
+
- spec/samples/application_helper.rb
|
169
|
+
- spec/samples/decorator_application_helper.rb
|
170
|
+
- spec/samples/decorator_with_allows.rb
|
171
|
+
- spec/samples/decorator_with_denies.rb
|
158
172
|
- spec/spec_helper.rb
|