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.
@@ -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