draper 0.5.0 → 0.7.1

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/.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
@@ -18,13 +18,19 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
20
 
21
- s.add_development_dependency "rake", "0.8.7"
22
- s.add_development_dependency "rspec", "~> 2.0.1"
23
- s.add_development_dependency "activesupport", "~> 3.0.9"
24
- s.add_development_dependency "actionpack", "~> 3.0.9"
25
- s.add_development_dependency "ruby-debug19"
21
+ s.add_development_dependency "rake", ["0.8.7"]
22
+ s.add_development_dependency "rspec", ["~> 2.0.1"]
23
+ s.add_development_dependency "activesupport", ["~> 3.0.9"]
24
+ s.add_development_dependency "actionpack", ["~> 3.0.9"]
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
@@ -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.
@@ -25,12 +26,18 @@ module Draper
25
26
  proxy.extend(mod)
26
27
  end
27
28
 
29
+ proxy.instance_eval do
30
+ def controller
31
+ #Object.controller
32
+ end
33
+ end
34
+
28
35
  proxy.instance_eval do
29
36
  # A hack since this proxy doesn't pick up default_url_options from anywhere
30
37
  def url_for(*args)
31
38
  if args.last.is_a?(Hash) && !args.last[:only_path]
32
39
  args = args.dup
33
- args << args.pop.merge(host: ActionMailer::Base.default_url_options[:host])
40
+ args << args.pop.merge('host' => ActionMailer::Base.default_url_options[:host])
34
41
  end
35
42
  super(*args)
36
43
  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.1"
3
3
  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
@@ -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,8 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: draper
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 1
4
5
  prerelease:
5
- version: 0.5.0
6
+ segments:
7
+ - 0
8
+ - 7
9
+ - 1
10
+ version: 0.7.1
6
11
  platform: ruby
7
12
  authors:
8
13
  - Jeff Casimir
@@ -10,8 +15,7 @@ autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
17
 
13
- date: 2011-07-27 00:00:00 -05:00
14
- default_executable:
18
+ date: 2011-08-28 00:00:00 Z
15
19
  dependencies:
16
20
  - !ruby/object:Gem::Dependency
17
21
  name: rake
@@ -21,6 +25,11 @@ dependencies:
21
25
  requirements:
22
26
  - - "="
23
27
  - !ruby/object:Gem::Version
28
+ hash: 49
29
+ segments:
30
+ - 0
31
+ - 8
32
+ - 7
24
33
  version: 0.8.7
25
34
  type: :development
26
35
  version_requirements: *id001
@@ -32,6 +41,11 @@ dependencies:
32
41
  requirements:
33
42
  - - ~>
34
43
  - !ruby/object:Gem::Version
44
+ hash: 13
45
+ segments:
46
+ - 2
47
+ - 0
48
+ - 1
35
49
  version: 2.0.1
36
50
  type: :development
37
51
  version_requirements: *id002
@@ -43,6 +57,11 @@ dependencies:
43
57
  requirements:
44
58
  - - ~>
45
59
  - !ruby/object:Gem::Version
60
+ hash: 21
61
+ segments:
62
+ - 3
63
+ - 0
64
+ - 9
46
65
  version: 3.0.9
47
66
  type: :development
48
67
  version_requirements: *id003
@@ -54,64 +73,98 @@ dependencies:
54
73
  requirements:
55
74
  - - ~>
56
75
  - !ruby/object:Gem::Version
76
+ hash: 21
77
+ segments:
78
+ - 3
79
+ - 0
80
+ - 9
57
81
  version: 3.0.9
58
82
  type: :development
59
83
  version_requirements: *id004
60
84
  - !ruby/object:Gem::Dependency
61
- name: ruby-debug19
85
+ name: guard
62
86
  prerelease: false
63
87
  requirement: &id005 !ruby/object:Gem::Requirement
64
88
  none: false
65
89
  requirements:
66
90
  - - ">="
67
91
  - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
68
95
  version: "0"
69
96
  type: :development
70
97
  version_requirements: *id005
71
98
  - !ruby/object:Gem::Dependency
72
- name: guard
99
+ name: guard-rspec
73
100
  prerelease: false
74
101
  requirement: &id006 !ruby/object:Gem::Requirement
75
102
  none: false
76
103
  requirements:
77
104
  - - ">="
78
105
  - !ruby/object:Gem::Version
106
+ hash: 3
107
+ segments:
108
+ - 0
79
109
  version: "0"
80
110
  type: :development
81
111
  version_requirements: *id006
82
112
  - !ruby/object:Gem::Dependency
83
- name: guard-rspec
113
+ name: rb-fsevent
84
114
  prerelease: false
85
115
  requirement: &id007 !ruby/object:Gem::Requirement
86
116
  none: false
87
117
  requirements:
88
118
  - - ">="
89
119
  - !ruby/object:Gem::Version
120
+ hash: 3
121
+ segments:
122
+ - 0
90
123
  version: "0"
91
124
  type: :development
92
125
  version_requirements: *id007
93
126
  - !ruby/object:Gem::Dependency
94
- name: rb-fsevent
127
+ name: ruby-debug
95
128
  prerelease: false
96
129
  requirement: &id008 !ruby/object:Gem::Requirement
97
130
  none: false
98
131
  requirements:
99
132
  - - ">="
100
133
  - !ruby/object:Gem::Version
134
+ hash: 3
135
+ segments:
136
+ - 0
101
137
  version: "0"
102
138
  type: :development
103
139
  version_requirements: *id008
104
140
  - !ruby/object:Gem::Dependency
105
- name: cover_me
141
+ name: rcov
106
142
  prerelease: false
107
143
  requirement: &id009 !ruby/object:Gem::Requirement
108
144
  none: false
109
145
  requirements:
110
146
  - - ">="
111
147
  - !ruby/object:Gem::Version
112
- version: 1.0.0.rc6
148
+ hash: 3
149
+ segments:
150
+ - 0
151
+ version: "0"
113
152
  type: :development
114
153
  version_requirements: *id009
154
+ - !ruby/object:Gem::Dependency
155
+ name: launchy
156
+ prerelease: false
157
+ requirement: &id010 !ruby/object:Gem::Requirement
158
+ none: false
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ hash: 3
163
+ segments:
164
+ - 0
165
+ version: "0"
166
+ type: :development
167
+ version_requirements: *id010
115
168
  description: Draper reimagines the role of helpers in the view layer of a Rails application, allowing an object-oriented approach rather than procedural.
116
169
  email:
117
170
  - jeff@casimircreative.com
@@ -132,12 +185,15 @@ files:
132
185
  - lib/draper/all_helpers.rb
133
186
  - lib/draper/base.rb
134
187
  - lib/draper/lazy_helpers.rb
188
+ - lib/draper/model_support.rb
135
189
  - lib/draper/system.rb
136
190
  - 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
191
+ - lib/generators/draper/decorator/USAGE
192
+ - lib/generators/draper/decorator/decorator_generator.rb
193
+ - lib/generators/draper/decorator/templates/application_decorator.rb
194
+ - lib/generators/draper/decorator/templates/decorator.rb
140
195
  - spec/base_spec.rb
196
+ - spec/draper/model_support_spec.rb
141
197
  - spec/samples/active_record.rb
142
198
  - spec/samples/application_controller.rb
143
199
  - spec/samples/application_helper.rb
@@ -147,7 +203,6 @@ files:
147
203
  - spec/samples/product.rb
148
204
  - spec/samples/product_decorator.rb
149
205
  - spec/spec_helper.rb
150
- has_rdoc: true
151
206
  homepage: http://github.com/jcasimir/draper
152
207
  licenses: []
153
208
 
@@ -161,22 +216,29 @@ required_ruby_version: !ruby/object:Gem::Requirement
161
216
  requirements:
162
217
  - - ">="
163
218
  - !ruby/object:Gem::Version
219
+ hash: 3
220
+ segments:
221
+ - 0
164
222
  version: "0"
165
223
  required_rubygems_version: !ruby/object:Gem::Requirement
166
224
  none: false
167
225
  requirements:
168
226
  - - ">="
169
227
  - !ruby/object:Gem::Version
228
+ hash: 3
229
+ segments:
230
+ - 0
170
231
  version: "0"
171
232
  requirements: []
172
233
 
173
234
  rubyforge_project: draper
174
- rubygems_version: 1.6.2
235
+ rubygems_version: 1.8.6
175
236
  signing_key:
176
237
  specification_version: 3
177
238
  summary: Decorator pattern implmentation for Rails.
178
239
  test_files:
179
240
  - spec/base_spec.rb
241
+ - spec/draper/model_support_spec.rb
180
242
  - spec/samples/active_record.rb
181
243
  - spec/samples/application_controller.rb
182
244
  - 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