draper 0.5.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
1
  *.gem
2
+ *.rvmrc
2
3
  .bundle
3
4
  Gemfile.lock
4
5
  pkg/*
5
6
  coverage.data
6
- coverage/*
7
+ coverage/*
data/Rakefile CHANGED
@@ -1,19 +1,47 @@
1
1
  require 'bundler/gem_tasks'
2
+ require 'rake'
3
+ require 'rspec/core/rake_task'
2
4
 
3
- namespace :cover_me do
5
+ RCOV = RUBY_VERSION.to_f == 1.8
6
+
7
+ namespace :spec do
8
+
9
+ RSpec::Core::RakeTask.new(:coverage) do |t|
10
+ t.pattern = 'spec/**/*_spec.rb'
11
+
12
+ if RCOV
13
+ t.rcov = true
14
+ t.rcov_opts = '--exclude osx\/objc,spec,gems\/'
15
+ end
16
+ end
4
17
 
5
- desc "Generates and opens code coverage report."
6
- task :report do
7
- require 'cover_me'
8
- CoverMe.complete!
18
+ RSpec::Core::RakeTask.new(:normal) do |t|
19
+ t.pattern ='spec/**/*_spec.rb'
20
+ t.rcov = false
21
+ end
22
+
23
+ namespace :coverage do
24
+ desc "Cleanup coverage data"
25
+ task :cleanup do
26
+ rm_rf 'coverage.data'
27
+ rm_rf 'coverage'
28
+ end
29
+
30
+ desc "Browse the code coverage report."
31
+ task :report => ["spec:coverage:cleanup", "spec:coverage"] do
32
+ if RCOV
33
+ require "launchy"
34
+ Launchy.open("coverage/index.html")
35
+ else
36
+ require 'cover_me'
37
+ CoverMe.complete!
38
+ end
39
+ end
9
40
  end
10
41
 
11
42
  end
12
43
 
13
- task :test do
14
- Rake::Task['cover_me:report'].invoke
15
- end
44
+ desc "RSpec tests"
45
+ task "spec" => "spec:normal"
16
46
 
17
- task :spec do
18
- Rake::Task['cover_me:report'].invoke
19
- end
47
+ task "default" => "spec"
data/Readme.markdown CHANGED
@@ -3,25 +3,29 @@
3
3
  ## Quick Start
4
4
 
5
5
  1. Add `gem 'draper'` to your `Gemfile` and `bundle`
6
- 2. Run `rails g draper:model YourModel`
7
- 3. Edit `app/decorators/your_model_decorator.rb` using:
6
+ 2. Run `rails g draper:decorator YourModel`
7
+ 3. Edit `app/decorators/[your_model]_decorator.rb` using:
8
8
  1. `h` to proxy to Rails/application helpers like `h.current_user`
9
9
  2. `model` to access the wrapped object like `model.created_at`
10
- 4. Wrap models in your controller with the decorator using:
11
- 1. `.find` automatic lookup & wrap: `ArticleDecorator.find(1)`
12
- 2. `.decorate` method with single object or collection: `ArticleDecorator.decorate(Article.all)`
13
- 3. `.new` method with single object: `ArticleDecorator.new(Article.first)`
14
- 5. Call and output the methods in your view templates as normal
10
+ 4. Put common decorations in `app/decorators/application.rb`
11
+ 5. Wrap models in your controller with the decorator using:
12
+ 1. `.find` automatic lookup & wrap
13
+ ex: `ArticleDecorator.find(1)`
14
+ 2. `.decorate` method with single object or collection,
15
+ ex: `ArticleDecorator.decorate(Article.all)`
16
+ 3. `.new` method with single object
17
+ ex: `ArticleDecorator.new(Article.first)`
18
+ 6. Output the instance methods in your view templates
19
+ ex: `@article_decorator.created_at`
15
20
 
16
21
  ## Goals
17
22
 
18
- This gem makes it easy to apply the decorator pattern to the data models in a Rails application. This gives you three wins:
23
+ This gem makes it easy to apply the decorator pattern to domain models in a Rails application. This pattern gives you three wins:
19
24
 
20
25
  1. Replace most helpers with an object-oriented approach
21
26
  2. Filter data at the presentation level
22
27
  3. Enforce an interface between your controllers and view templates.
23
28
 
24
-
25
29
  ### 1. Object Oriented Helpers
26
30
 
27
31
  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 with a decorator.
@@ -29,7 +33,7 @@ Why hate helpers? In Ruby/Rails we approach everything from an Object-Oriented p
29
33
  A decorator wraps an object with presentation-related accessor methods. For instance, if you had an `Article` object, then the decorator could override `.published_at` to use formatted output like this:
30
34
 
31
35
  ```ruby
32
- class ArticleDecorator < Draper::Base
36
+ class ArticleDecorator < ApplicationDecorator
33
37
  decorates :article
34
38
  def published_at
35
39
  date = h.content_tag(:span, model.published_at.strftime("%A, %B %e").squeeze(" "), :class => 'date')
@@ -50,7 +54,7 @@ How would you handle this in the model layer? You'd probably pass the `current_u
50
54
  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. You can access your `current_user` helper method using the `h` proxy available in the decorator:
51
55
 
52
56
  ```ruby
53
- class ArticleDecorator < Draper::Base
57
+ class ArticleDecorator < ApplicationDecorator
54
58
  decorates :article
55
59
  ADMIN_VISIBLE_ATTRIBUTES = [:title, :body, :author, :status]
56
60
  PUBLIC_VISIBLE_ATTRIBUTES = [:title, :body]
@@ -71,7 +75,7 @@ Want to strictly control what methods are proxied to the original object? Use `d
71
75
  The `denies` method takes a blacklist approach. For instance:
72
76
 
73
77
  ```ruby
74
- class ArticleDecorator < Draper::Base
78
+ class ArticleDecorator < ApplicationDecorator
75
79
  decorates :article
76
80
  denies :title
77
81
  end
@@ -91,7 +95,7 @@ NoMethodError: undefined method `title' for #<ArticleDecorator:0x000001020d7728>
91
95
  A better approach is to define a whitelist using `allows`:
92
96
 
93
97
  ```ruby
94
- class ArticleDecorator < Draper::Base
98
+ class ArticleDecorator < ApplicationDecorator
95
99
  decorates :article
96
100
  allows :title, :description
97
101
  end
@@ -106,7 +110,7 @@ ruby-1.9.2-p290 :003 > ad.created_at
106
110
  NoMethodError: undefined method `created_at' for #<ArticleDecorator:0x000001020d7728>
107
111
  ```
108
112
 
109
- ## Up an Running
113
+ ## Up and Running
110
114
 
111
115
  ### Setup
112
116
 
@@ -122,12 +126,24 @@ Run bundle:
122
126
  bundle
123
127
  ```
124
128
 
129
+ #### Disable Rails Helper Generation (Optional)
130
+
131
+ When you generate a scaffold, Rails will create a matching helper file. If you're using decorators, this is probably unnecessary. You can disable the helper file creation by adding this to your `config/application.rb`
132
+
133
+ ```ruby
134
+ config.generators do |g|
135
+ g.helper false
136
+ end
137
+ ```
138
+
139
+ If you want a helper, you can still call `rails generate helper` directly.
140
+
125
141
  ### Generate the Decorator
126
142
 
127
143
  To decorate a model named `Article`:
128
144
 
129
145
  ```
130
- rails generate draper:model Article
146
+ rails generate draper:decorator Article
131
147
  ```
132
148
 
133
149
  ### Writing Methods
@@ -135,7 +151,7 @@ rails generate draper:model Article
135
151
  Open the decorator model (ex: `app/decorators/article_decorator.rb`) and add normal instance methods. To access the wrapped source object, use the `model` method:
136
152
 
137
153
  ```ruby
138
- class Article < Draper::Base
154
+ class ArticleDecorator < ApplicationDecorator
139
155
  decorates :article
140
156
 
141
157
  def author_name
@@ -144,13 +160,12 @@ class Article < Draper::Base
144
160
  end
145
161
  ```
146
162
 
147
-
148
163
  ### Using Existing Helpers
149
164
 
150
165
  You probably want to make use of Rails helpers and those defined in your application. Use the `helpers` or `h` method proxy:
151
166
 
152
167
  ```ruby
153
- class Article < Draper::Base
168
+ class ArticleDecorator < ApplicationDecorator
154
169
  decorates :article
155
170
 
156
171
  def published_at
@@ -161,6 +176,23 @@ class Article < Draper::Base
161
176
  end
162
177
  ```
163
178
 
179
+ #### Lazy Helpers
180
+
181
+ Hate seeing that `h.` proxy all over? Willing to mix a bazillion methods into your decorator? Then try lazy helpers:
182
+
183
+ ```ruby
184
+ class ArticleDecorator < ApplicationDecorator
185
+ decorates :article
186
+ lazy_helpers
187
+
188
+ def published_at
189
+ date = content_tag(:span, model.published_at.strftime("%A, %B %e").squeeze(" "), :class => 'date')
190
+ time = content_tag(:span, model.published_at.strftime("%l:%M%p"), :class => 'time').delete(" ")
191
+ content_tag :span, date + time, :class => 'created_at'
192
+ end
193
+ end
194
+ ```
195
+
164
196
  ### In the Controller
165
197
 
166
198
  When writing your controller actions, you have three options:
@@ -218,7 +250,7 @@ First, follow the steps above to add the dependency and update your bundle.
218
250
  Since we're talking about the `Article` model we'll create an `ArticleDecorator` class. You could do it by hand, but use the provided generator:
219
251
 
220
252
  ```
221
- rails generate draper:model Article
253
+ rails generate draper:decorator Article
222
254
  ```
223
255
 
224
256
  Now open up the created `app/decorators/article_decorator.rb` and you'll find an `ArticleDecorator` class. Add this method:
@@ -250,22 +282,38 @@ Then within your views you can utilize both the normal data methods and your new
250
282
  Ta-da! Object-oriented data formatting for your view layer. Below is the complete decorator with extra comments removed:
251
283
 
252
284
  ```ruby
253
- class ArticleDecorator < Draper::Base
285
+ class ArticleDecorator < ApplicationDecorator
254
286
  decorates :article
255
287
 
256
288
  def published_at
257
- date = h.content_tag(:span, published_at.strftime("%A, %B %e").squeeze(" "), :class => 'date')
258
- time = h.content_tag(:span, published_at.strftime("%l:%M%p"), :class => 'time').delete(" ")
259
- h.content_tag :span, date + time, :class => 'created_at'
289
+ date = h.content_tag(:span, model.published_at.strftime("%A, %B %e").squeeze(" "), :class => 'date')
290
+ time = h.content_tag(:span, model.published_at.strftime("%l:%M%p"), :class => 'time').delete(" ")
291
+ h.content_tag :span, date + time, :class => 'published_at'
260
292
  end
261
293
  end
262
294
  ```
263
295
 
264
296
  ## Issues / Pending
265
297
 
266
- * Test coverage for generators
267
- * Keep revising Readme for better organization/clarity
268
- * Build sample Rails application
298
+ * Documentation
299
+ * Keep revising Readme for better organization/clarity
300
+ * Add more information about using "context"
301
+ * Add information about the `.decorator` method
302
+ * Make clear the pattern of overriding accessor methods of the wrapped model
303
+ * Build sample Rails application(s)
304
+ * Add a short screencast
305
+ * Add YARD documentation to source
306
+ * Add a section about contributing
307
+ * Generators
308
+ * Test coverage for generators (help!)
309
+ * Implement hook so generating a controller/scaffold generates a decorator
310
+ * Add generators for...
311
+ * `draper:model`: Model + Decorator
312
+ * `draper:controller`: Controller setup with decoration calls
313
+ * `draper:scaffold`: Controller, Model, Decorator, Views, Tests
314
+ * Other
315
+ * Implement a HATEOAS helper, maybe as a separate gem
316
+ * Build a fly website like http://fabricationgem.com
269
317
 
270
318
  ## License
271
319
 
@@ -277,4 +325,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
277
325
 
278
326
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
279
327
 
280
- THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
328
+ THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/draper.gemspec CHANGED
@@ -22,9 +22,15 @@ Gem::Specification.new do |s|
22
22
  s.add_development_dependency "rspec", "~> 2.0.1"
23
23
  s.add_development_dependency "activesupport", "~> 3.0.9"
24
24
  s.add_development_dependency "actionpack", "~> 3.0.9"
25
- s.add_development_dependency "ruby-debug19"
26
25
  s.add_development_dependency "guard"
27
26
  s.add_development_dependency "guard-rspec"
28
27
  s.add_development_dependency "rb-fsevent"
29
- s.add_development_dependency 'cover_me', '>= 1.0.0.rc6'
28
+ if RUBY_VERSION.to_f == 1.8
29
+ s.add_development_dependency "ruby-debug"
30
+ s.add_development_dependency "rcov"
31
+ s.add_development_dependency "launchy"
32
+ else
33
+ s.add_development_dependency "ruby-debug19"
34
+ s.add_development_dependency 'cover_me', '>= 1.0.0.rc6'
35
+ end
30
36
  end
data/lib/draper.rb CHANGED
@@ -3,5 +3,6 @@ require 'draper/system'
3
3
  require 'draper/all_helpers'
4
4
  require 'draper/base'
5
5
  require 'draper/lazy_helpers'
6
+ require 'draper/model_support'
6
7
 
7
8
  Draper::System.setup
@@ -1,5 +1,6 @@
1
1
  module Draper
2
2
  module AllHelpers
3
+ # Most of the black magic here thanks to Xavier Shay (@xshay)
3
4
  # Provide access to helper methods from outside controllers and views,
4
5
  # such as in Presenter objects. Rails provides ActionController::Base.helpers,
5
6
  # but this does not include any of our application helpers.
@@ -30,7 +31,7 @@ module Draper
30
31
  def url_for(*args)
31
32
  if args.last.is_a?(Hash) && !args.last[:only_path]
32
33
  args = args.dup
33
- args << args.pop.merge(host: ActionMailer::Base.default_url_options[:host])
34
+ args << args.pop.merge('host' => ActionMailer::Base.default_url_options[:host])
34
35
  end
35
36
  super(*args)
36
37
  end
data/lib/draper/base.rb CHANGED
@@ -1,44 +1,46 @@
1
1
  module Draper
2
- class Base
2
+ class Base
3
3
  require 'active_support/core_ext/class/attribute'
4
4
  class_attribute :denied, :allowed, :model_class
5
- attr_accessor :model
6
-
5
+ attr_accessor :context, :model
6
+
7
7
  DEFAULT_DENIED = Object.new.methods << :method_missing
8
8
  FORCED_PROXY = [:to_param]
9
9
  self.denied = DEFAULT_DENIED
10
10
 
11
- def initialize(input)
11
+ def initialize(input, context = nil)
12
12
  input.inspect
13
13
  self.class.model_class = input.class if model_class.nil?
14
- @model = input
14
+ @model = input
15
+ self.context = context
15
16
  build_methods
16
17
  end
17
-
18
+
18
19
  def self.find(input)
19
20
  self.new(model_class.find(input))
20
21
  end
21
-
22
+
22
23
  def self.decorates(input)
23
24
  self.model_class = input.to_s.classify.constantize
25
+ model_class.send :include, Draper::ModelSupport
24
26
  end
25
-
27
+
26
28
  def self.denies(*input_denied)
27
29
  raise ArgumentError, "Specify at least one method (as a symbol) to exclude when using denies" if input_denied.empty?
28
30
  raise ArgumentError, "Use either 'allows' or 'denies', but not both." if self.allowed?
29
31
  self.denied += input_denied
30
32
  end
31
-
33
+
32
34
  def self.allows(*input_allows)
33
35
  raise ArgumentError, "Specify at least one method (as a symbol) to allow when using allows" if input_allows.empty?
34
36
  raise ArgumentError, "Use either 'allows' or 'denies', but not both." unless (self.denied == DEFAULT_DENIED)
35
37
  self.allowed = input_allows
36
38
  end
37
39
 
38
- def self.decorate(input)
39
- input.respond_to?(:each) ? input.map{|i| new(i)} : new(input)
40
+ def self.decorate(input, context = nil)
41
+ input.respond_to?(:each) ? input.map{|i| new(i, context)} : new(input, context)
40
42
  end
41
-
43
+
42
44
  def helpers
43
45
  @helpers ||= ApplicationController::all_helpers
44
46
  end
@@ -47,19 +49,19 @@ module Draper
47
49
  def self.lazy_helpers
48
50
  self.send(:include, Draper::LazyHelpers)
49
51
  end
50
-
52
+
51
53
  def self.model_name
52
54
  ActiveModel::Name.new(model_class)
53
55
  end
54
-
56
+
55
57
  def to_model
56
58
  @model
57
59
  end
58
-
59
- private
60
+
61
+ private
60
62
  def select_methods
61
- specified = self.allowed || (model.public_methods - denied)
62
- (specified - self.public_methods) + FORCED_PROXY
63
+ specified = self.allowed || (model.public_methods.map{|s| s.to_sym} - denied.map{|s| s.to_sym})
64
+ (specified - self.public_methods.map{|s| s.to_sym}) + FORCED_PROXY
63
65
  end
64
66
 
65
67
  def build_methods
@@ -69,7 +71,7 @@ module Draper
69
71
  model.send method, *args, &block
70
72
  end
71
73
  end
72
- end
73
- end
74
+ end
75
+ end
74
76
  end
75
- end
77
+ end
@@ -0,0 +1,5 @@
1
+ module Draper::ModelSupport
2
+ def decorator
3
+ @decorator ||= "#{self.class.name}Decorator".constantize.decorate(self)
4
+ end
5
+ end
@@ -1,3 +1,3 @@
1
1
  module Draper
2
- VERSION = "0.5.0"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -0,0 +1,7 @@
1
+ Description:
2
+ The draper:decorator generator creates a decorator model in /app/decorators.
3
+
4
+ Examples:
5
+ rails generate draper:decorator Article
6
+
7
+ file: app/decorators/article_decorator.rb
@@ -0,0 +1,17 @@
1
+ module Draper
2
+ class DecoratorGenerator < Rails::Generators::NamedBase
3
+ source_root File.expand_path('../templates', __FILE__)
4
+
5
+ DECORATORS_ROOT = 'app/decorators/'
6
+ APPLICATION_DECORATOR = 'application_decorator.rb'
7
+ APPLICATION_DECORATOR_PATH = DECORATORS_ROOT + APPLICATION_DECORATOR
8
+
9
+ def build_model_and_application_decorators
10
+ empty_directory "app/decorators"
11
+ unless File.exists?(APPLICATION_DECORATOR_PATH)
12
+ template APPLICATION_DECORATOR, APPLICATION_DECORATOR_PATH
13
+ end
14
+ template 'decorator.rb', "#{DECORATORS_ROOT}#{singular_name}_decorator.rb"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ class ApplicationDecorator < Draper::Base
2
+ # Lazy Helpers
3
+ # PRO: Call Rails helpers without the h. proxy
4
+ # ex: number_to_currency(model.price)
5
+ # CON: Add a bazillion methods into your decorator's namespace
6
+ # and probably sacrifice performance/memory
7
+ #
8
+ # Enable them by uncommenting this line:
9
+ # lazy_helpers
10
+
11
+ # Shared Decorations
12
+ # Consider defining shared methods common to all your models.
13
+ #
14
+ # Example: standardize the formatting of timestamps
15
+ #
16
+ # def formatted_timestamp(time)
17
+ # h.content_tag :span, time.strftime("%a %m/%d/%y"),
18
+ # :class => 'timestamp'
19
+ # end
20
+ #
21
+ # def created_at
22
+ # formatted_timestamp(model.created_at)
23
+ # end
24
+ #
25
+ # def updated_at
26
+ # formatted_timestamp(model.updated_at)
27
+ # end
28
+ end
@@ -0,0 +1,32 @@
1
+ class <%= singular_name.camelize %>Decorator < ApplicationDecorator
2
+ decorates :<%= singular_name.to_sym %>
3
+
4
+ # Accessing Helpers
5
+ # You can access any helper via a proxy
6
+ #
7
+ # Normal Usage: helpers.number_to_currency(2)
8
+ # Abbreviated : h.number_to_currency(2)
9
+ #
10
+ # Or, optionally enable "lazy helpers" by calling this method:
11
+ # lazy_helpers
12
+ # Then use the helpers with no proxy:
13
+ # number_to_currency(2)
14
+
15
+ # Defining an Interface
16
+ # Control access to the wrapped subject's methods using one of the following:
17
+ #
18
+ # To allow only the listed methods (whitelist):
19
+ # allows :method1, :method2
20
+ #
21
+ # To allow everything except the listed methods (blacklist):
22
+ # denies :method1, :method2
23
+
24
+ # Presentation Methods
25
+ # Define your own instance methods, even overriding accessors
26
+ # generated by ActiveRecord:
27
+ #
28
+ # def created_at
29
+ # h.content_tag :span, time.strftime("%a %m/%d/%y"),
30
+ # :class => 'timestamp'
31
+ # end
32
+ end
data/spec/base_spec.rb CHANGED
@@ -3,7 +3,7 @@ require 'draper'
3
3
 
4
4
  describe Draper::Base do
5
5
  subject{ Draper::Base.new(source) }
6
- let(:source){ Product.new }
6
+ let(:source){ Product.new }
7
7
 
8
8
  context(".lazy_helpers") do
9
9
  it "makes Rails helpers available without using the h. proxy" do
@@ -11,7 +11,7 @@ describe Draper::Base do
11
11
  subject.send(:pluralize, 5, "cat").should == "5 cats"
12
12
  end
13
13
  end
14
-
14
+
15
15
  context(".model_name") do
16
16
  it "should return an ActiveModel::Name instance" do
17
17
  Draper::Base.model_name.should be_instance_of(ActiveModel::Name)
@@ -23,14 +23,14 @@ describe Draper::Base do
23
23
  ProductDecorator.new(source).model_class == Product
24
24
  end
25
25
  end
26
-
26
+
27
27
  context(".model / .to_model") do
28
28
  it "should return the wrapped object" do
29
29
  subject.to_model.should == source
30
30
  subject.model.should == source
31
31
  end
32
32
  end
33
-
33
+
34
34
  context("selecting methods") do
35
35
  it "echos the methods of the wrapped class except default exclusions" do
36
36
  source.methods.each do |method|
@@ -39,149 +39,182 @@ describe Draper::Base do
39
39
  end
40
40
  end
41
41
  end
42
-
42
+
43
43
  it "should not override a defined method with a source method" do
44
44
  DecoratorWithApplicationHelper.new(source).length.should == "overridden"
45
45
  end
46
-
46
+
47
47
  it "should always proxy to_param" do
48
48
  source.send :class_eval, "def to_param; 1; end"
49
49
  Draper::Base.new(source).to_param.should == 1
50
50
  end
51
-
51
+
52
52
  it "should not copy the .class, .inspect, or other existing methods" do
53
53
  source.class.should_not == subject.class
54
54
  source.inspect.should_not == subject.inspect
55
55
  source.to_s.should_not == subject.to_s
56
56
  end
57
- end
57
+ end
58
+
59
+ context 'the decorated model' do
60
+ it 'receives the mixin' do
61
+ source.class.ancestors.include?(Draper::ModelSupport)
62
+ end
63
+ end
58
64
 
59
65
  it "should wrap source methods so they still accept blocks" do
60
66
  subject.block{"marker"}.should == "marker"
61
67
  end
62
-
68
+
63
69
  context ".find" do
64
70
  it "should lookup the associated model when passed an integer" do
65
71
  pd = ProductDecorator.find(1)
66
72
  pd.should be_instance_of(ProductDecorator)
67
73
  pd.model.should be_instance_of(Product)
68
74
  end
69
-
75
+
70
76
  it "should lookup the associated model when passed a string" do
71
77
  pd = ProductDecorator.find("1")
72
78
  pd.should be_instance_of(ProductDecorator)
73
79
  pd.model.should be_instance_of(Product)
74
80
  end
75
81
  end
76
-
82
+
77
83
  context ".decorate" do
78
- it "should return a collection of wrapped objects when given a collection of source objects" do
79
- sources = [Product.new, Product.new]
80
- output = Draper::Base.decorate(sources)
81
- output.should respond_to(:each)
82
- output.size.should == sources.size
83
- output.each{ |decorated| decorated.should be_instance_of(Draper::Base) }
84
- end
85
-
86
- it "should return a single wrapped object when given a single source object" do
87
- output = Draper::Base.decorate(source)
88
- output.should be_instance_of(Draper::Base)
84
+ context "without any context" do
85
+ subject { Draper::Base.decorate(source) }
86
+
87
+ context "when given a collection of source objects" do
88
+ let(:source) { [Product.new, Product.new] }
89
+
90
+ its(:size) { should == source.size }
91
+
92
+ it "returns a collection of wrapped objects" do
93
+ subject.each{ |decorated| decorated.should be_instance_of(Draper::Base) }
94
+ end
95
+ end
96
+
97
+ context "when given a single source object" do
98
+ let(:source) { Product.new }
99
+
100
+ it { should be_instance_of(Draper::Base) }
101
+ end
102
+ end
103
+
104
+ context "with a context" do
105
+ let(:context) {{ :some => 'data' }}
106
+
107
+ subject { Draper::Base.decorate(source, context) }
108
+
109
+ context "when given a collection of source objects" do
110
+ let(:source) { [Product.new, Product.new] }
111
+
112
+ it "returns a collection of wrapped objects with the context" do
113
+ subject.each{ |decorated| decorated.context.should eq(context) }
114
+ end
115
+ end
116
+
117
+ context "when given a single source object" do
118
+ let(:source) { Product.new }
119
+
120
+ its(:context) { should eq(context) }
121
+ end
89
122
  end
90
123
  end
91
-
92
- describe "a sample usage with denies" do
124
+
125
+ describe "a sample usage with denies" do
93
126
  let(:subject_with_denies){ DecoratorWithDenies.new(source) }
94
-
127
+
95
128
  it "should proxy methods not listed in denies" do
96
129
  subject_with_denies.should respond_to(:hello_world)
97
130
  end
98
-
131
+
99
132
  it "should not echo methods specified with denies" do
100
133
  subject_with_denies.should_not respond_to(:goodnight_moon)
101
134
  end
102
135
 
103
136
  it "should not clobber other decorators' methods" do
104
137
  subject.should respond_to(:hello_world)
105
- end
106
-
138
+ end
139
+
107
140
  it "should not allow method_missing to circumvent a deny" do
108
141
  expect{subject_with_denies.title}.to raise_error(NoMethodError)
109
- end
142
+ end
110
143
  end
111
-
144
+
112
145
  describe "a sample usage with allows" do
113
146
  let(:subject_with_allows){ DecoratorWithAllows.new(source) }
114
-
147
+
115
148
  it "should echo the allowed method" do
116
149
  subject_with_allows.should respond_to(:upcase)
117
150
  end
118
-
151
+
119
152
  it "should echo _only_ the allowed method" do
120
153
  subject_with_allows.should_not respond_to(:downcase)
121
154
  end
122
155
  end
123
-
156
+
124
157
  describe "invalid usages of allows and denies" do
125
158
  let(:blank_allows){
126
- class DecoratorWithInvalidAllows < Draper::Base
159
+ class DecoratorWithInvalidAllows < Draper::Base
127
160
  allows
128
161
  end
129
162
  }
130
-
163
+
131
164
  let(:blank_denies){
132
- class DecoratorWithInvalidDenies < Draper::Base
165
+ class DecoratorWithInvalidDenies < Draper::Base
133
166
  denies
134
167
  end
135
168
  }
136
-
169
+
137
170
  let(:using_allows_then_denies){
138
- class DecoratorWithAllowsAndDenies < Draper::Base
171
+ class DecoratorWithAllowsAndDenies < Draper::Base
139
172
  allows :hello_world
140
173
  denies :goodnight_moon
141
174
  end
142
- }
143
-
175
+ }
176
+
144
177
  let(:using_denies_then_allows){
145
- class DecoratorWithDeniesAndAllows < Draper::Base
178
+ class DecoratorWithDeniesAndAllows < Draper::Base
146
179
  denies :goodnight_moon
147
- allows :hello_world
180
+ allows :hello_world
148
181
  end
149
182
  }
150
183
 
151
184
  it "should raise an exception for a blank allows" do
152
185
  expect {blank_allows}.should raise_error(ArgumentError)
153
186
  end
154
-
187
+
155
188
  it "should raise an exception for a blank denies" do
156
189
  expect {blank_denies}.should raise_error(ArgumentError)
157
190
  end
158
-
191
+
159
192
  it "should raise an exception for calling allows then denies" do
160
193
  expect {using_allows_then_denies}.should raise_error(ArgumentError)
161
194
  end
162
-
195
+
163
196
  it "should raise an exception for calling denies then allows" do
164
197
  expect {using_denies_then_allows}.should raise_error(ArgumentError)
165
198
  end
166
199
  end
167
-
200
+
168
201
  context "in a Rails application" do
169
202
  let(:decorator){ DecoratorWithApplicationHelper.decorate(Object.new) }
170
-
203
+
171
204
  it "should have access to ApplicationHelper helpers" do
172
205
  decorator.uses_hello_world == "Hello, World!"
173
206
  end
174
-
207
+
175
208
  it "should be able to use the content_tag helper" do
176
209
  decorator.sample_content.to_s.should == "<span>Hello, World!</span>"
177
210
  end
178
-
211
+
179
212
  it "should be able to use the link_to helper" do
180
213
  decorator.sample_link.should == "<a href=\"/World\">Hello</a>"
181
214
  end
182
-
215
+
183
216
  it "should be able to use the pluralize helper" do
184
217
  decorator.sample_truncate.should == "Once..."
185
218
  end
186
219
  end
187
- end
220
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe Draper::ModelSupport do
4
+ subject { Product.new }
5
+
6
+ describe '#decorator' do
7
+ its(:decorator) { should be_kind_of(ProductDecorator) }
8
+ its(:decorator) { should be(subject.decorator) }
9
+ end
10
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,12 @@
1
1
  require 'rubygems'
2
2
  require 'bundler'
3
- require 'cover_me'
3
+
4
+ require 'rspec'
5
+ begin
6
+ require 'cover_me'
7
+ rescue LoadError
8
+ # Silently fail
9
+ end
4
10
  require './spec/samples/application_helper.rb'
5
11
  Bundler.require
6
12
  Dir.glob('./spec/samples/*') {|file| require file}
metadata CHANGED
@@ -1,127 +1,123 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: draper
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.0
4
5
  prerelease:
5
- version: 0.5.0
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Jeff Casimir
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-07-27 00:00:00 -05:00
14
- default_executable:
15
- dependencies:
16
- - !ruby/object:Gem::Dependency
12
+ date: 2011-08-17 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
17
15
  name: rake
18
- prerelease: false
19
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &70309492944640 !ruby/object:Gem::Requirement
20
17
  none: false
21
- requirements:
22
- - - "="
23
- - !ruby/object:Gem::Version
18
+ requirements:
19
+ - - =
20
+ - !ruby/object:Gem::Version
24
21
  version: 0.8.7
25
22
  type: :development
26
- version_requirements: *id001
27
- - !ruby/object:Gem::Dependency
28
- name: rspec
29
23
  prerelease: false
30
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: *70309492944640
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &70309492944140 !ruby/object:Gem::Requirement
31
28
  none: false
32
- requirements:
29
+ requirements:
33
30
  - - ~>
34
- - !ruby/object:Gem::Version
31
+ - !ruby/object:Gem::Version
35
32
  version: 2.0.1
36
33
  type: :development
37
- version_requirements: *id002
38
- - !ruby/object:Gem::Dependency
39
- name: activesupport
40
34
  prerelease: false
41
- requirement: &id003 !ruby/object:Gem::Requirement
35
+ version_requirements: *70309492944140
36
+ - !ruby/object:Gem::Dependency
37
+ name: activesupport
38
+ requirement: &70309492943680 !ruby/object:Gem::Requirement
42
39
  none: false
43
- requirements:
40
+ requirements:
44
41
  - - ~>
45
- - !ruby/object:Gem::Version
42
+ - !ruby/object:Gem::Version
46
43
  version: 3.0.9
47
44
  type: :development
48
- version_requirements: *id003
49
- - !ruby/object:Gem::Dependency
50
- name: actionpack
51
45
  prerelease: false
52
- requirement: &id004 !ruby/object:Gem::Requirement
46
+ version_requirements: *70309492943680
47
+ - !ruby/object:Gem::Dependency
48
+ name: actionpack
49
+ requirement: &70309492943220 !ruby/object:Gem::Requirement
53
50
  none: false
54
- requirements:
51
+ requirements:
55
52
  - - ~>
56
- - !ruby/object:Gem::Version
53
+ - !ruby/object:Gem::Version
57
54
  version: 3.0.9
58
55
  type: :development
59
- version_requirements: *id004
60
- - !ruby/object:Gem::Dependency
61
- name: ruby-debug19
62
56
  prerelease: false
63
- requirement: &id005 !ruby/object:Gem::Requirement
57
+ version_requirements: *70309492943220
58
+ - !ruby/object:Gem::Dependency
59
+ name: guard
60
+ requirement: &70309492942840 !ruby/object:Gem::Requirement
64
61
  none: false
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: "0"
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
69
66
  type: :development
70
- version_requirements: *id005
71
- - !ruby/object:Gem::Dependency
72
- name: guard
73
67
  prerelease: false
74
- requirement: &id006 !ruby/object:Gem::Requirement
68
+ version_requirements: *70309492942840
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rspec
71
+ requirement: &70309492942380 !ruby/object:Gem::Requirement
75
72
  none: false
76
- requirements:
77
- - - ">="
78
- - !ruby/object:Gem::Version
79
- version: "0"
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
80
77
  type: :development
81
- version_requirements: *id006
82
- - !ruby/object:Gem::Dependency
83
- name: guard-rspec
84
78
  prerelease: false
85
- requirement: &id007 !ruby/object:Gem::Requirement
79
+ version_requirements: *70309492942380
80
+ - !ruby/object:Gem::Dependency
81
+ name: rb-fsevent
82
+ requirement: &70309492941960 !ruby/object:Gem::Requirement
86
83
  none: false
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- version: "0"
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
91
88
  type: :development
92
- version_requirements: *id007
93
- - !ruby/object:Gem::Dependency
94
- name: rb-fsevent
95
89
  prerelease: false
96
- requirement: &id008 !ruby/object:Gem::Requirement
90
+ version_requirements: *70309492941960
91
+ - !ruby/object:Gem::Dependency
92
+ name: ruby-debug19
93
+ requirement: &70309492941520 !ruby/object:Gem::Requirement
97
94
  none: false
98
- requirements:
99
- - - ">="
100
- - !ruby/object:Gem::Version
101
- version: "0"
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
102
99
  type: :development
103
- version_requirements: *id008
104
- - !ruby/object:Gem::Dependency
105
- name: cover_me
106
100
  prerelease: false
107
- requirement: &id009 !ruby/object:Gem::Requirement
101
+ version_requirements: *70309492941520
102
+ - !ruby/object:Gem::Dependency
103
+ name: cover_me
104
+ requirement: &70309492941020 !ruby/object:Gem::Requirement
108
105
  none: false
109
- requirements:
110
- - - ">="
111
- - !ruby/object:Gem::Version
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
112
109
  version: 1.0.0.rc6
113
110
  type: :development
114
- version_requirements: *id009
115
- description: Draper reimagines the role of helpers in the view layer of a Rails application, allowing an object-oriented approach rather than procedural.
116
- email:
111
+ prerelease: false
112
+ version_requirements: *70309492941020
113
+ description: Draper reimagines the role of helpers in the view layer of a Rails application,
114
+ allowing an object-oriented approach rather than procedural.
115
+ email:
117
116
  - jeff@casimircreative.com
118
117
  executables: []
119
-
120
118
  extensions: []
121
-
122
119
  extra_rdoc_files: []
123
-
124
- files:
120
+ files:
125
121
  - .gitignore
126
122
  - Gemfile
127
123
  - Guardfile
@@ -132,12 +128,15 @@ files:
132
128
  - lib/draper/all_helpers.rb
133
129
  - lib/draper/base.rb
134
130
  - lib/draper/lazy_helpers.rb
131
+ - lib/draper/model_support.rb
135
132
  - lib/draper/system.rb
136
133
  - lib/draper/version.rb
137
- - lib/generators/draper/model/USAGE
138
- - lib/generators/draper/model/model_generator.rb
139
- - lib/generators/draper/model/templates/model.rb
134
+ - lib/generators/draper/decorator/USAGE
135
+ - lib/generators/draper/decorator/decorator_generator.rb
136
+ - lib/generators/draper/decorator/templates/application_decorator.rb
137
+ - lib/generators/draper/decorator/templates/decorator.rb
140
138
  - spec/base_spec.rb
139
+ - spec/draper/model_support_spec.rb
141
140
  - spec/samples/active_record.rb
142
141
  - spec/samples/application_controller.rb
143
142
  - spec/samples/application_helper.rb
@@ -147,36 +146,33 @@ files:
147
146
  - spec/samples/product.rb
148
147
  - spec/samples/product_decorator.rb
149
148
  - spec/spec_helper.rb
150
- has_rdoc: true
151
149
  homepage: http://github.com/jcasimir/draper
152
150
  licenses: []
153
-
154
151
  post_install_message:
155
152
  rdoc_options: []
156
-
157
- require_paths:
153
+ require_paths:
158
154
  - lib
159
- required_ruby_version: !ruby/object:Gem::Requirement
155
+ required_ruby_version: !ruby/object:Gem::Requirement
160
156
  none: false
161
- requirements:
162
- - - ">="
163
- - !ruby/object:Gem::Version
164
- version: "0"
165
- required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ! '>='
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
162
  none: false
167
- requirements:
168
- - - ">="
169
- - !ruby/object:Gem::Version
170
- version: "0"
163
+ requirements:
164
+ - - ! '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
171
167
  requirements: []
172
-
173
168
  rubyforge_project: draper
174
- rubygems_version: 1.6.2
169
+ rubygems_version: 1.8.6
175
170
  signing_key:
176
171
  specification_version: 3
177
172
  summary: Decorator pattern implmentation for Rails.
178
- test_files:
173
+ test_files:
179
174
  - spec/base_spec.rb
175
+ - spec/draper/model_support_spec.rb
180
176
  - spec/samples/active_record.rb
181
177
  - spec/samples/application_controller.rb
182
178
  - spec/samples/application_helper.rb
@@ -1,7 +0,0 @@
1
- Description:
2
- The draper:model generator creates a decorator model in /app/decorators.
3
-
4
- Examples:
5
- rails generate draper:model Article
6
-
7
- file: app/decorators/article_decorator.rb
@@ -1,10 +0,0 @@
1
- module Draper
2
- class ModelGenerator < Rails::Generators::NamedBase
3
- source_root File.expand_path('../templates', __FILE__)
4
-
5
- def build_model
6
- empty_directory "app/decorators"
7
- template 'model.rb', "app/decorators/#{singular_name}_decorator.rb"
8
- end
9
- end
10
- end
@@ -1,29 +0,0 @@
1
- class <%= singular_name.camelize %>Decorator < Draper::Base
2
- decorates :<%= singular_name.to_sym %>
3
-
4
- # Helpers from Rails an Your Application
5
- # You can access any helper via a proxy to ApplicationController
6
- #
7
- # Normal Usage: helpers.number_to_currency(2)
8
- # Abbreviated : h.number_to_currency(2)
9
- #
10
- # You can optionally enable "lazy helpers" by including this module:
11
- # include Draper::LazyHelpers
12
- # Then use the helpers with no prefix:
13
- # number_to_currency(2)
14
-
15
- # Wrapper Methods
16
- # Control access to the wrapped subject's methods using one of the following:
17
- #
18
- # To allow only the listed methods (whitelist):
19
- # allows :method1, :method2
20
- #
21
- # To allow everything except the listed methods (blacklist):
22
- # denies :method1, :method2
23
-
24
- # Presentation Methods
25
- # Define your own instance methods. Ex:
26
- # def formatted_created_at
27
- # content_tag :span, created_at.strftime("%A")
28
- # end
29
- end