draper 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,16 +1,59 @@
1
- Draper
2
- ================
1
+ ## Heads Up!
3
2
 
4
- This gem makes it easy to apply the decorator pattern to the models in a Rails application.
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
- ## Why use decorators?
5
+ Please only use it if you're playing around and helping me experiment! Thanks :)
7
6
 
8
- Helpers, as they're commonly used, are a bit odd. In both Ruby and Rails we approach everything from an Object-Oriented perspective, then with helpers we get procedural.
7
+ # Draper
9
8
 
10
- The job of a helper is to take in data or a data object and output presentation-ready results. We can do that job in an OO fashion with a decorator.
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:
@@ -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
- #raise ArgumentError, "Use either 'allows' or 'denies', but not both." unless (self.denies == DEFAULT_EXCLUSIONS)
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 initialize(subject)
27
- self.source = subject
28
- build_methods
29
- end
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
@@ -1,3 +1,3 @@
1
1
  module Draper
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  end
@@ -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 DecoratorWithInvalidMixing < Draper::Base
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 DecoratorWithInvalidMixing < Draper::Base
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,5 @@
1
+ module ApplicationHelper
2
+ def hello
3
+ "Hello, World!"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class DecoratorApplicationHelper < Draper::Base
2
+ def uses_hello
3
+ self.hello
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ class DecoratorWithAllows < Draper::Base
2
+ allows :upcase
3
+ 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
@@ -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
- date: 2011-06-30 00:00:00.000000000 -04:00
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: &2154360120 !ruby/object:Gem::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: *2154360120
26
- - !ruby/object:Gem::Dependency
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
27
28
  name: rspec
28
- requirement: &2154359620 !ruby/object:Gem::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: *2154359620
37
- - !ruby/object:Gem::Dependency
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
38
39
  name: activesupport
39
- requirement: &2154359160 !ruby/object:Gem::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: *2154359160
48
- - !ruby/object:Gem::Dependency
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
49
50
  name: actionpack
50
- requirement: &2154358700 !ruby/object:Gem::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: *2154358700
59
- - !ruby/object:Gem::Dependency
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
60
61
  name: ruby-debug19
61
- requirement: &2154358320 !ruby/object:Gem::Requirement
62
+ requirement: &id005 !ruby/object:Gem::Requirement
62
63
  none: false
63
- requirements:
64
- - - ! '>='
65
- - !ruby/object:Gem::Version
66
- version: '0'
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
67
68
  type: :development
68
69
  prerelease: false
69
- version_requirements: *2154358320
70
- - !ruby/object:Gem::Dependency
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
71
72
  name: guard
72
- requirement: &2154357860 !ruby/object:Gem::Requirement
73
+ requirement: &id006 !ruby/object:Gem::Requirement
73
74
  none: false
74
- requirements:
75
- - - ! '>='
76
- - !ruby/object:Gem::Version
77
- version: '0'
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
78
79
  type: :development
79
80
  prerelease: false
80
- version_requirements: *2154357860
81
- - !ruby/object:Gem::Dependency
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
82
83
  name: guard-rspec
83
- requirement: &2154381220 !ruby/object:Gem::Requirement
84
+ requirement: &id007 !ruby/object:Gem::Requirement
84
85
  none: false
85
- requirements:
86
- - - ! '>='
87
- - !ruby/object:Gem::Version
88
- version: '0'
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
89
90
  type: :development
90
91
  prerelease: false
91
- version_requirements: *2154381220
92
- - !ruby/object:Gem::Dependency
92
+ version_requirements: *id007
93
+ - !ruby/object:Gem::Dependency
93
94
  name: rb-fsevent
94
- requirement: &2154380800 !ruby/object:Gem::Requirement
95
+ requirement: &id008 !ruby/object:Gem::Requirement
95
96
  none: false
96
- requirements:
97
- - - ! '>='
98
- - !ruby/object:Gem::Version
99
- version: '0'
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: "0"
100
101
  type: :development
101
102
  prerelease: false
102
- version_requirements: *2154380800
103
- description: Draper reimagines the role of helpers in the view layer of a Rails application,
104
- allowing an object-oriented approach rather than procedural.
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
- files:
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
- require_paths:
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
- version: '0'
138
- segments:
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ hash: -258105380500550580
147
+ segments:
139
148
  - 0
140
- hash: 50059517492085377
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
- version: '0'
147
- segments:
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ hash: -258105380500550580
156
+ segments:
148
157
  - 0
149
- hash: 50059517492085377
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