draper 0.17.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/CHANGELOG.markdown +6 -0
  2. data/Readme.markdown +22 -45
  3. data/draper.gemspec +2 -1
  4. data/lib/draper.rb +1 -1
  5. data/lib/draper/base.rb +4 -4
  6. data/lib/draper/decorated_enumerable_proxy.rb +34 -1
  7. data/lib/draper/model_support.rb +11 -3
  8. data/lib/draper/railtie.rb +2 -2
  9. data/lib/draper/system.rb +10 -2
  10. data/lib/draper/test/minitest_integration.rb +5 -24
  11. data/lib/draper/test/rspec_integration.rb +0 -6
  12. data/lib/draper/version.rb +1 -1
  13. data/lib/draper/view_context.rb +28 -3
  14. data/lib/generators/rspec/decorator_generator.rb +1 -1
  15. data/lib/generators/test_unit/decorator_generator.rb +1 -1
  16. data/lib/generators/test_unit/templates/decorator_test.rb +0 -3
  17. data/spec/draper/base_spec.rb +12 -2
  18. data/spec/draper/decorated_enumerable_proxy_spec.rb +45 -0
  19. data/spec/generators/decorator/decorator_generator_spec.rb +21 -1
  20. data/spec/minitest-rails/spec_type_spec.rb +63 -0
  21. data/spec/spec_helper.rb +23 -5
  22. data/spec/support/samples/enumerable_proxy.rb +3 -0
  23. data/spec/support/samples/products_decorator.rb +10 -0
  24. metadata +32 -40
  25. data/doc/ApplicationDecorator.html +0 -147
  26. data/doc/Draper.html +0 -123
  27. data/doc/Draper/AllHelpers.html +0 -256
  28. data/doc/Draper/Base.html +0 -1222
  29. data/doc/Draper/DecoratorGenerator.html +0 -216
  30. data/doc/Draper/LazyHelpers.html +0 -174
  31. data/doc/Draper/ModelSupport.html +0 -164
  32. data/doc/Draper/System.html +0 -179
  33. data/doc/_index.html +0 -172
  34. data/doc/class_list.html +0 -47
  35. data/doc/css/common.css +0 -1
  36. data/doc/css/full_list.css +0 -53
  37. data/doc/css/style.css +0 -320
  38. data/doc/file_list.html +0 -46
  39. data/doc/frames.html +0 -13
  40. data/doc/index.html +0 -172
  41. data/doc/js/app.js +0 -205
  42. data/doc/js/full_list.js +0 -150
  43. data/doc/js/jquery.js +0 -16
  44. data/doc/method_list.html +0 -174
  45. data/doc/top-level-namespace.html +0 -103
  46. data/lib/draper/test/view_context.rb +0 -12
  47. data/spec/draper/helper_support_spec.rb +0 -18
  48. data/spec/draper/view_context_spec.rb +0 -30
  49. data/spec/support/samples/active_model.rb +0 -9
  50. data/spec/support/samples/application_controller.rb +0 -42
  51. data/spec/support/samples/application_helper.rb +0 -8
data/CHANGELOG.markdown CHANGED
@@ -1,5 +1,11 @@
1
1
  # Draper Changelog
2
2
 
3
+ ## 0.17.0
4
+
5
+ * [Fix earlier fix of `view_context` priming](https://github.com/jcasimir/draper/commit/5da44336)
6
+ * [Add `denies_all`](https://github.com/jcasimir/draper/commit/148e732)
7
+ * [Properly proxy associations with regard to `find`](https://github.com/jcasimir/draper/commit/d46d19205e)
8
+
3
9
  ## 0.16.0
4
10
 
5
11
  * [Automatically prime `view_context`](https://github.com/jcasimir/draper/commit/057ab4e8)
data/Readme.markdown CHANGED
@@ -6,19 +6,19 @@
6
6
 
7
7
  1. Add `gem 'draper'` to your `Gemfile` and `bundle`
8
8
  2. When you generate a resource with `rails g resource YourModel`, you get a decorator automatically!
9
- 3. If YourModel exists, run `rails g decorator YourModel`
9
+ 3. If `YourModel` already exists, run `rails g decorator YourModel` to create `YourModelDecorator`
10
10
  4. Edit `app/decorators/[your_model]_decorator.rb` using:
11
11
  1. `h` to proxy to Rails/application helpers like `h.current_user`
12
- 2. `[your_model]` to access the wrapped object like `article.created_at`
12
+ 2. the name of your decorated model to access the wrapped object like `article.created_at`
13
13
  5. Wrap models in your controller with the decorator using:
14
14
  1. `.find` automatic lookup & wrap
15
15
  ex: `ArticleDecorator.find(1)`
16
- 2. `.decorate` method with single object or collection,
16
+ 2. `.decorate` method with a single object or collection,
17
17
  ex: `ArticleDecorator.decorate(Article.all)`
18
18
  3. `.new` method with single object
19
19
  ex: `ArticleDecorator.new(Article.first)`
20
- 6. Output the instance methods in your view templates
21
- ex: `@article_decorator.created_at`
20
+ 6. Call decorator methods from your view templates
21
+ ex: `<%= @article_decorator.created_at %>`
22
22
 
23
23
  If you need common methods in your decorators, create an `app/decorators/application_decorator.rb`:
24
24
 
@@ -30,26 +30,6 @@ end
30
30
 
31
31
  and make your decorators inherit from it. Newly generated decorators will respect this choice and inherit from `ApplicationDecorator`.
32
32
 
33
- ## Watch the RailsCast
34
-
35
- Ryan Bates has put together an excellent RailsCast on Draper based on the 0.8.0 release:
36
-
37
- [![RailsCast #286](https://img.skitch.com/20111021-dgxmqntq22d37fthky6pttk59n.jpg "RailsCast #286 - Draper")](http://railscasts.com/episodes/286-draper)
38
-
39
- ## What's New
40
-
41
- Check out the full commit history at http://github.com/jcasimir/draper/commits
42
-
43
- In summary, you can now:
44
-
45
- * [Namespace the `decorates` call](https://github.com/jcasimir/draper/commit/1c3d5667b8406b80b490d876257379087b129f92)
46
- * [Use your decorators with CanCan](https://github.com/jcasimir/draper/commit/ac1f3083989107d877e2b1c918c3a3e792db99e8)
47
- * [Use a more generalized `options` hash in decorator initialization](https://github.com/jcasimir/draper/commit/03910877d0461356da0968a87346592908f292a7)
48
- * [Get better performance by generating methods](https://github.com/jcasimir/draper/commit/ebe30511b79eac82276413ca7ae54a4a4d86d4dc)
49
- * [Automatically decorate associated objects](https://github.com/jcasimir/draper/commit/1580baa287997ed4e356aae0ffeeb8fe9c326ced) See Example near bottom of Readme
50
-
51
- Thanks to [steveklabnik](http://github.com/steveklabnik), [i0rek](http://github.com/i0rek), [laserlemon](http://github.com/laserlemon), [michaelfairley](http://github.com/michaelfairley), [dnagir](http://github.com/dnagir), [ubermajestix](http://github.com/ubermajestix), [tmaier](http://github.com/tmaier), [angelim](http://github.com/angelim), [duncanbeevers](http://github.com/duncanbeevers), Albert Peng & JR Boyens, [leocassarani](http://github.com/leocassarani), [Jeff Felchner](http://github.com/Felchner), [shingara](http://github.com/shingara), [momolog](http://github.com/momolog), and [ayamomiji](http://github.com/ayamomiji) for their contributions to this version!
52
-
53
33
  ## Goals
54
34
 
55
35
  This gem makes it easy to apply the decorator pattern to domain models in a Rails application. This pattern gives you three wins:
@@ -260,7 +240,13 @@ Here are some ideas of what you might do in decorator methods:
260
240
  * Format dates and times using `strftime`
261
241
  * Implement a commonly used representation of the data object like a `.name` method that combines `first_name` and `last_name` attributes
262
242
 
263
- ## Example Using a Decorator
243
+ ## Learning Resources
244
+
245
+ ### RailsCast
246
+
247
+ Ryan Bates has put together an excellent RailsCast on Draper based on the 0.8.0 release: http://railscasts.com/episodes/286-draper
248
+
249
+ ### Example Using a Decorator
264
250
 
265
251
  For a brief tutorial with sample project, check this out: http://tutorials.jumpstartlab.com/topics/decorators.html
266
252
 
@@ -296,7 +282,7 @@ end
296
282
  Then you need to perform the wrapping in your controller. Here's the simplest method:
297
283
 
298
284
  ```ruby
299
- class ArticlesController < Draper::Decorator
285
+ class ArticlesController < ApplicationController
300
286
  def show
301
287
  @article = ArticleDecorator.find params[:id]
302
288
  end
@@ -380,29 +366,20 @@ For automatic decoration, check out [decorates_before_rendering](https://github.
380
366
  4. Push to the branch (`git push origin my_awesome_branch`)
381
367
  5. Send pull request
382
368
 
383
- ## Issues / Pending
384
-
385
- * Documentation
386
- * Add more information about using "context"
387
- * Add information about the `.decorator` method
388
- * Make clear the pattern of overriding accessor methods of the wrapped model
389
- * Build sample Rails application(s)
390
- * Generators
391
- * Implement hook so generating a controller/scaffold generates a decorator
392
- * Add generators for...
393
- * `draper:model`: Model + Decorator
394
- * `draper:controller`: Controller setup with decoration calls
395
- * `draper:scaffold`: Controller, Model, Decorator, Views, Tests
396
- * Other
397
- * Implement a HATEOAS helper, maybe as a separate gem
398
- * Build a fly website like http://fabricationgem.com
369
+ ## Contributors
370
+
371
+ Draper was conceived by Jeff Casimir and heavily refined by Steve Klabnik and a great community of open source contributors.
372
+
373
+ ### Core Team
374
+
375
+ * Jeff Casimir (jeff@jumpstartlab.com)
376
+ * Steve Klabnik (steve@jumpstartlab.com)
377
+ * Vasiliy Ermolovich
399
378
 
400
379
  ## License
401
380
 
402
381
  (The MIT License)
403
382
 
404
- Copyright © 2011 Jeff Casimir
405
-
406
383
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
407
384
 
408
385
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
data/draper.gemspec CHANGED
@@ -19,9 +19,10 @@ Gem::Specification.new do |s|
19
19
  s.add_dependency 'activesupport', '~> 3.2'
20
20
  s.add_dependency 'actionpack', '~> 3.2'
21
21
 
22
- s.add_development_dependency 'ammeter', '0.2.2'
22
+ s.add_development_dependency 'ammeter'
23
23
  s.add_development_dependency 'rake', '~> 0.9.2'
24
24
  s.add_development_dependency 'rspec', '~> 2.10'
25
25
  s.add_development_dependency 'yard'
26
+ s.add_development_dependency 'minitest-rails', '~> 0.2'
26
27
  s.add_development_dependency 'minitest', '~> 3.0' if RUBY_PLATFORM == "java"
27
28
  end
data/lib/draper.rb CHANGED
@@ -13,4 +13,4 @@ require 'draper/railtie' if defined?(Rails)
13
13
 
14
14
  # Test Support
15
15
  require 'draper/test/rspec_integration' if defined?(RSpec) and RSpec.respond_to?(:configure)
16
- require 'draper/test/minitest_integration' if defined?(MiniTest::Spec)
16
+ require 'draper/test/minitest_integration' if defined?(MiniTest::Rails)
data/lib/draper/base.rb CHANGED
@@ -18,7 +18,7 @@ module Draper
18
18
  # @param [Object] instance to wrap
19
19
  # @param [Hash] options (optional)
20
20
  def initialize(input, options = {})
21
- input.inspect # forces evaluation of a lazy query from AR
21
+ input.to_a if input.respond_to?(:to_a) # forces evaluation of a lazy query from AR
22
22
  self.class.model_class = input.class if model_class.nil?
23
23
  @model = input.kind_of?(Draper::Base) ? input.model : input
24
24
  self.options = options
@@ -148,7 +148,7 @@ module Draper
148
148
  if input.instance_of?(self)
149
149
  input.options = options unless options.empty?
150
150
  return input
151
- elsif input.respond_to?(:each) && (!defined?(Sequel) || !input.is_a?(Sequel::Model))
151
+ elsif input.respond_to?(:each) && !input.is_a?(Struct) && (!defined?(Sequel) || !input.is_a?(Sequel::Model))
152
152
  Draper::DecoratedEnumerableProxy.new(input, self, options)
153
153
  elsif options[:infer]
154
154
  input.decorator(options)
@@ -203,8 +203,8 @@ module Draper
203
203
  # Localize is something that's used quite often. Even though
204
204
  # it's available through helpers, that's annoying. Aliased
205
205
  # to `.l` for convenience.
206
- def localize(str)
207
- self.class.helpers.localize(str)
206
+ def localize(object, options = {})
207
+ self.class.helpers.localize(object, options)
208
208
  end
209
209
  alias :l :localize
210
210
 
@@ -3,7 +3,26 @@ module Draper
3
3
  class DecoratedEnumerableProxy
4
4
  include Enumerable
5
5
 
6
- delegate :as_json, :collect, :map, :each, :[], :all?, :include?, :first, :last, :shift, :to => :decorated_collection
6
+ delegate :as_json, :collect, :map, :each, :[], :all?, :include?, :first, :last, :shift, :in_groups_of, :to => :decorated_collection
7
+
8
+ # Initialize a new collection decorator instance by passing in
9
+ # an instance of a collection. Pass in an optional
10
+ # context into the options hash is stored for later use.
11
+ #
12
+ #
13
+ # @param [Object] instances to wrap
14
+ # @param [Hash] options (optional)
15
+ # @option options [Class] :class The decorator class to use
16
+ # for each item in the collection.
17
+ # @option options all other options are passed to Decorator
18
+ # class for each item.
19
+
20
+ def self.decorate(collection, options = {})
21
+ new( collection, discern_class_from_my_class(options.delete(:class)), options)
22
+ end
23
+ class << self
24
+ alias_method :decorates, :decorate
25
+ end
7
26
 
8
27
  def initialize(collection, klass, options = {})
9
28
  @wrapped_collection, @klass, @options = collection, klass, options
@@ -53,5 +72,19 @@ module Draper
53
72
  @wrapped_collection
54
73
  end
55
74
  alias_method :to_source, :source
75
+
76
+ def helpers
77
+ Draper::ViewContext.current
78
+ end
79
+ alias_method :h, :helpers
80
+
81
+ private
82
+ def self.discern_class_from_my_class default_class
83
+ return default_class if default_class
84
+ name = self.to_s.gsub("Decorator", "")
85
+ "#{name.singularize}Decorator".constantize
86
+ rescue NameError
87
+ raise NameError("You must supply a class (as the klass option) for the members of your collection or the class must be inferable from the name of this class ('#{new.class}')")
88
+ end
56
89
  end
57
90
  end
@@ -2,16 +2,24 @@ module Draper::ModelSupport
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def decorator(options = {})
5
- @decorator ||= "#{self.class.name}Decorator".constantize.decorate(self, options.merge(:infer => false))
5
+ @decorator ||= decorator_class.decorate(self, options.merge(:infer => false))
6
6
  block_given? ? yield(@decorator) : @decorator
7
7
  end
8
8
 
9
+ def decorator_class
10
+ "#{self.class.name}Decorator".constantize
11
+ end
12
+
9
13
  alias :decorate :decorator
10
14
 
11
15
  module ClassMethods
12
16
  def decorate(options = {})
13
- decorator_proxy = "#{model_name}Decorator".constantize.decorate(self.scoped, options)
17
+ decorator_proxy = decorator_class.decorate(self.scoped, options)
14
18
  block_given? ? yield(decorator_proxy) : decorator_proxy
15
19
  end
20
+
21
+ def decorator_class
22
+ "#{model_name}Decorator".constantize
23
+ end
16
24
  end
17
- end
25
+ end
@@ -24,13 +24,13 @@ module Draper
24
24
 
25
25
  initializer "draper.extend_action_controller_base" do |app|
26
26
  ActiveSupport.on_load(:action_controller) do
27
- Draper::System.setup(self)
27
+ Draper::System.setup_action_controller(self)
28
28
  end
29
29
  end
30
30
 
31
31
  initializer "draper.extend_action_mailer_base" do |app|
32
32
  ActiveSupport.on_load(:action_mailer) do
33
- Draper::System.setup(self)
33
+ Draper::System.setup_action_mailer(self)
34
34
  end
35
35
  end
36
36
 
data/lib/draper/system.rb CHANGED
@@ -1,10 +1,18 @@
1
1
  module Draper
2
2
  class System
3
- def self.setup(component)
3
+ def self.setup_action_controller(component)
4
4
  component.class_eval do
5
5
  include Draper::ViewContext
6
- extend Draper::HelperSupport unless defined?(::ActionMailer) && self.is_a?(::ActionMailer::Base)
6
+ extend Draper::HelperSupport
7
+ before_filter lambda {|controller|
8
+ Draper::ViewContext.current = nil
9
+ Draper::ViewContext.current_controller = controller
10
+ }
7
11
  end
8
12
  end
13
+
14
+ def self.setup_action_mailer(component)
15
+ include Draper::ViewContext
16
+ end
9
17
  end
10
18
  end
@@ -1,26 +1,7 @@
1
- require 'draper/test/view_context'
2
-
3
- module MiniTest
4
- class Spec
5
- class Decorator < Spec
6
- before { Draper::ViewContext.infect!(self) }
7
- end
1
+ class MiniTest::Rails::ActiveSupport::TestCase
2
+ # Use AS::TestCase for the base class when describing a decorator
3
+ register_spec_type(self) do |desc|
4
+ desc < Draper::Base if desc.is_a?(Class)
8
5
  end
9
- end
10
-
11
- class MiniTest::Unit::DecoratorTestCase < MiniTest::Unit::TestCase
12
- if method_defined?(:before_setup)
13
- # for minitext >= 2.11
14
- def before_setup
15
- super
16
- Draper::ViewContext.infect!(self)
17
- end
18
- else
19
- # for older minitest, like what ships w/Ruby 1.9
20
- add_setup_hook { Draper::ViewContext.infect!(self) }
21
- end
22
- end
23
-
24
- MiniTest::Spec.register_spec_type(MiniTest::Spec::Decorator) do |desc|
25
- desc.superclass == Draper::Base
6
+ register_spec_type(/Decorator( ?Test)?\z/i, self)
26
7
  end
@@ -1,5 +1,3 @@
1
- require 'draper/test/view_context'
2
-
3
1
  module Draper
4
2
  module DecoratorExampleGroup
5
3
  extend ActiveSupport::Concern
@@ -13,10 +11,6 @@ RSpec.configure do |config|
13
11
  :file_path => /spec[\\\/]decorators/
14
12
  }
15
13
 
16
- # Specs tagged type: :decorator set the Draper view context
17
- config.before :type => :decorator do
18
- Draper::ViewContext.infect!(self)
19
- end
20
14
  end
21
15
 
22
16
  if defined?(Capybara)
@@ -1,3 +1,3 @@
1
1
  module Draper
2
- VERSION = "0.17.0"
2
+ VERSION = "0.18.0"
3
3
  end
@@ -1,11 +1,19 @@
1
1
  module Draper
2
2
  module ViewContext
3
+ def self.current_controller
4
+ Thread.current[:current_controller] || ApplicationController.new
5
+ end
6
+
7
+ def self.current_controller=(controller)
8
+ Thread.current[:current_controller] = controller
9
+ end
10
+
3
11
  def self.current
4
- Thread.current[:current_view_context] || ApplicationController.new.view_context
12
+ Thread.current[:current_view_context] ||= build_view_context
5
13
  end
6
14
 
7
- def self.current=(input)
8
- Thread.current[:current_view_context] = input
15
+ def self.current=(context)
16
+ Thread.current[:current_view_context] = context
9
17
  end
10
18
 
11
19
  def view_context
@@ -13,5 +21,22 @@ module Draper
13
21
  Draper::ViewContext.current = context
14
22
  end
15
23
  end
24
+
25
+ private
26
+
27
+ def self.build_view_context
28
+ current_controller.view_context.tap do |context|
29
+ context.instance_eval do
30
+ def url_options
31
+ ActionMailer::Base.default_url_options
32
+ end
33
+ end unless context.request
34
+ if defined?(ActionController::TestRequest)
35
+ context.controller.request ||= ActionController::TestRequest.new
36
+ context.request ||= context.controller.request
37
+ context.params ||= {}
38
+ end
39
+ end
40
+ end
16
41
  end
17
42
  end
@@ -3,7 +3,7 @@ module Rspec
3
3
  source_root File.expand_path('../templates', __FILE__)
4
4
 
5
5
  def create_spec_file
6
- template 'decorator_spec.rb', File.join('spec/decorators', "#{singular_name}_decorator_spec.rb")
6
+ template 'decorator_spec.rb', File.join('spec/decorators', class_path, "#{singular_name}_decorator_spec.rb")
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module TestUnit
3
3
  source_root File.expand_path('../templates', __FILE__)
4
4
 
5
5
  def create_test_file
6
- template 'decorator_test.rb', File.join('test/decorators', "#{singular_name}_decorator_test.rb")
6
+ template 'decorator_test.rb', File.join('test/decorators', class_path, "#{singular_name}_decorator_test.rb")
7
7
  end
8
8
  end
9
9
  end
@@ -1,7 +1,4 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class <%= class_name %>DecoratorTest < ActiveSupport::TestCase
4
- def setup
5
- ApplicationController.new.view_context
6
- end
7
4
  end
@@ -376,6 +376,15 @@ describe Draper::Base do
376
376
  end
377
377
  end
378
378
 
379
+ context "when given a struct" do
380
+ # Struct objects implement #each
381
+ let(:source) { Struct.new(:title).new("Godzilla") }
382
+
383
+ it "returns a wrapped object" do
384
+ subject.should be_instance_of(Draper::Base)
385
+ end
386
+ end
387
+
379
388
  context "when given a collection of sequel models" do
380
389
  # Sequel models implement #each
381
390
  let(:source) { [SequelProduct.new, SequelProduct.new] }
@@ -794,8 +803,9 @@ describe Draper::Base do
794
803
 
795
804
  it "is able to use l rather than helpers.l" do
796
805
  now = Time.now
797
- decorator.helpers.instance_variable_get(:@helpers).should_receive(:localize).with(now)
798
- decorator.l now
806
+ helper_proxy = decorator.helpers.instance_variable_get(:@helpers)
807
+ helper_proxy.should_receive(:localize).with(now, :format => :long)
808
+ decorator.l now, :format => :long
799
809
  end
800
810
 
801
811
  it "is able to access html_escape, a private method" do