draper 0.10.0 → 0.11.0

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.
Files changed (45) hide show
  1. data/.travis.yml +0 -1
  2. data/Gemfile +3 -3
  3. data/Rakefile +8 -8
  4. data/Readme.markdown +41 -22
  5. data/doc/css/full_list.css +2 -2
  6. data/doc/css/style.css +30 -30
  7. data/doc/js/app.js +10 -10
  8. data/doc/js/full_list.js +8 -8
  9. data/draper.gemspec +2 -2
  10. data/lib/draper.rb +1 -1
  11. data/lib/draper/base.rb +65 -22
  12. data/lib/draper/decorated_enumerable_proxy.rb +6 -1
  13. data/lib/draper/model_support.rb +2 -2
  14. data/lib/draper/railtie.rb +19 -0
  15. data/lib/draper/system.rb +7 -4
  16. data/lib/draper/version.rb +1 -1
  17. data/lib/generators/draper/decorator/decorator_generator.rb +26 -4
  18. data/lib/generators/draper/decorator/templates/decorator.rb +2 -2
  19. data/lib/generators/{rspec → draper/decorator}/templates/decorator_spec.rb +1 -1
  20. data/lib/generators/{test_unit → draper/decorator}/templates/decorator_test.rb +1 -1
  21. data/lib/generators/draper/install/install_generator.rb +6 -6
  22. data/lib/generators/rails/decorator_generator.rb +3 -3
  23. data/performance/bechmark.rb +5 -5
  24. data/performance/decorators.rb +4 -4
  25. data/performance/models.rb +2 -2
  26. data/spec/draper/base_spec.rb +176 -10
  27. data/spec/draper/helper_support_spec.rb +1 -1
  28. data/spec/draper/model_support_spec.rb +23 -14
  29. data/spec/draper/view_context_spec.rb +4 -4
  30. data/spec/generators/draper/decorator/decorator_generator_spec.rb +81 -1
  31. data/spec/generators/draper/install/install_generator_spec.rb +5 -5
  32. data/spec/spec_helper.rb +3 -0
  33. data/spec/support/samples/application_controller.rb +8 -11
  34. data/spec/support/samples/decorator_with_allows.rb +1 -1
  35. data/spec/support/samples/decorator_with_application_helper.rb +5 -5
  36. data/spec/support/samples/decorator_with_denies.rb +1 -1
  37. data/spec/support/samples/decorator_with_multiple_allows.rb +4 -0
  38. data/spec/support/samples/product.rb +28 -7
  39. data/spec/support/samples/some_thing.rb +2 -0
  40. data/spec/support/samples/some_thing_decorator.rb +3 -0
  41. metadata +62 -37
  42. data/lib/generators/rspec/decorator_generator.rb +0 -11
  43. data/lib/generators/test_unit/decorator_generator.rb +0 -11
  44. data/spec/generators/rspec/decorator_generator_spec.rb +0 -22
  45. data/spec/generators/test_unit/decorator_generator_spec.rb +0 -22
@@ -7,5 +7,5 @@ require 'draper/helper_support'
7
7
  require 'draper/view_context'
8
8
  require 'draper/decorated_enumerable_proxy'
9
9
  require 'draper/rspec_integration' if defined?(RSpec) and RSpec.respond_to?(:configure)
10
+ require 'draper/railtie'
10
11
 
11
- Draper::System.setup
@@ -2,9 +2,10 @@ module Draper
2
2
  class Base
3
3
  require 'active_support/core_ext/class/attribute'
4
4
  class_attribute :denied, :allowed, :model_class
5
- attr_accessor :context, :model
5
+ attr_accessor :model, :options
6
6
 
7
7
  DEFAULT_DENIED = Object.new.methods << :method_missing
8
+ DEFAULT_ALLOWED = []
8
9
  FORCED_PROXY = [:to_param, :id]
9
10
  FORCED_PROXY.each do |method|
10
11
  define_method method do |*args, &block|
@@ -12,6 +13,7 @@ module Draper
12
13
  end
13
14
  end
14
15
  self.denied = DEFAULT_DENIED
16
+ self.allowed = DEFAULT_ALLOWED
15
17
 
16
18
  # Initialize a new decorator instance by passing in
17
19
  # an instance of the source class. Pass in an optional
@@ -23,7 +25,7 @@ module Draper
23
25
  input.inspect # forces evaluation of a lazy query from AR
24
26
  self.class.model_class = input.class if model_class.nil?
25
27
  @model = input
26
- self.context = options.fetch(:context, {})
28
+ self.options = options
27
29
  end
28
30
 
29
31
  # Proxies to the class specified by `decorates` to automatically
@@ -48,7 +50,8 @@ module Draper
48
50
  #
49
51
  # @param [Symbol] class_name snakecase name of the decorated class, like `:product`
50
52
  def self.decorates(input, options = {})
51
- self.model_class = options[:class] || input.to_s.camelize.constantize
53
+ self.model_class = options[:class] || options[:class_name] || input.to_s.camelize
54
+ self.model_class = model_class.constantize if model_class.respond_to?(:constantize)
52
55
  model_class.send :include, Draper::ModelSupport
53
56
  define_method(input){ @model }
54
57
  end
@@ -61,13 +64,21 @@ module Draper
61
64
  def self.decorates_association(association_symbol, options = {})
62
65
  define_method(association_symbol) do
63
66
  orig_association = model.send(association_symbol)
64
- return orig_association if orig_association.nil?
65
- if options[:with]
66
- options[:with].decorate(orig_association)
67
+
68
+ return orig_association if orig_association.nil?
69
+
70
+ return options[:with].decorate(orig_association) if options[:with]
71
+
72
+ if options[:polymorphic]
73
+ klass = orig_association.class
74
+ elsif model.class.respond_to?(:reflect_on_association) && model.class.reflect_on_association(association_symbol)
75
+ klass = model.class.reflect_on_association(association_symbol).klass
76
+ elsif orig_association.respond_to?(:first)
77
+ klass = orig_association.first.class
67
78
  else
68
- reflection = model.class.reflect_on_association(association_symbol)
69
- "#{reflection.klass}Decorator".constantize.decorate(orig_association)
79
+ klass = orig_association.class
70
80
  end
81
+ "#{klass}Decorator".constantize.decorate(orig_association, options)
71
82
  end
72
83
  end
73
84
 
@@ -88,7 +99,7 @@ module Draper
88
99
  # @param [Symbols*] methods to deny like `:find, :find_by_name`
89
100
  def self.denies(*input_denied)
90
101
  raise ArgumentError, "Specify at least one method (as a symbol) to exclude when using denies" if input_denied.empty?
91
- raise ArgumentError, "Use either 'allows' or 'denies', but not both." if self.allowed?
102
+ raise ArgumentError, "Use either 'allows' or 'denies', but not both." unless (self.allowed == DEFAULT_ALLOWED)
92
103
  self.denied += input_denied
93
104
  end
94
105
 
@@ -104,7 +115,7 @@ module Draper
104
115
  def self.allows(*input_allows)
105
116
  raise ArgumentError, "Specify at least one method (as a symbol) to allow when using allows" if input_allows.empty?
106
117
  raise ArgumentError, "Use either 'allows' or 'denies', but not both." unless (self.denied == DEFAULT_DENIED)
107
- self.allowed = input_allows
118
+ self.allowed += input_allows
108
119
  end
109
120
 
110
121
  # Initialize a new decorator instance by passing in
@@ -119,7 +130,10 @@ module Draper
119
130
  # @param [Object] instance(s) to wrap
120
131
  # @param [Hash] options (optional)
121
132
  def self.decorate(input, options = {})
122
- if input.respond_to?(:each)
133
+ if input.instance_of?(self)
134
+ input.options = options unless options.empty?
135
+ return input
136
+ elsif input.respond_to?(:each)
123
137
  Draper::DecoratedEnumerableProxy.new(input, self, options)
124
138
  elsif options[:infer]
125
139
  input.decorator(options)
@@ -145,7 +159,7 @@ module Draper
145
159
  end
146
160
 
147
161
  # Access the helpers proxy to call built-in and user-defined
148
- # Rails helpers. Aliased to `.h` for convinience.
162
+ # Rails helpers. Aliased to `.h` for convenience.
149
163
  #
150
164
  # @return [Object] proxy
151
165
  def helpers
@@ -153,6 +167,14 @@ module Draper
153
167
  end
154
168
  alias :h :helpers
155
169
 
170
+ # Localize is something that's used quite often. Even though
171
+ # it's available through helpers, that's annoying. Aliased
172
+ # to `.l` for convenience.
173
+ def localize(str)
174
+ self.class.helpers.localize(str)
175
+ end
176
+ alias :l :localize
177
+
156
178
  # Access the helpers proxy to call built-in and user-defined
157
179
  # Rails helpers from a class context.
158
180
  #
@@ -188,31 +210,52 @@ module Draper
188
210
  end
189
211
 
190
212
  def method_missing(method, *args, &block)
191
- if allow?(method)
192
- begin
193
- self.class.send :define_method, method do |*args, &block|
194
- model.send(method, *args, &block)
195
- end
196
- self.send(method, *args, &block)
197
- rescue NoMethodError
198
- super
213
+ super unless allow?(method)
214
+
215
+ if model.respond_to?(method)
216
+ self.class.send :define_method, method do |*args, &block|
217
+ model.send method, *args, &block
199
218
  end
219
+
220
+ send method, *args, &block
200
221
  else
201
222
  super
202
223
  end
224
+
225
+ rescue NoMethodError => no_method_error
226
+ super if no_method_error.name == method
227
+ raise no_method_error
203
228
  end
204
229
 
205
230
  def self.method_missing(method, *args, &block)
206
- model_class.send(method, *args, &block)
231
+ if method.to_s.match(/^find_by.*/)
232
+ self.decorate(model_class.send(method, *args, &block))
233
+ else
234
+ model_class.send(method, *args, &block)
235
+ end
207
236
  end
208
237
 
209
238
  def self.respond_to?(method, include_private = false)
210
239
  super || model_class.respond_to?(method)
211
240
  end
212
241
 
242
+ def context
243
+ options.fetch(:context, {})
244
+ end
245
+
246
+ def context=(input)
247
+ options[:context] = input
248
+ end
249
+
250
+ def source
251
+ model
252
+ end
253
+ alias_method :to_source, :model
254
+
213
255
  private
256
+
214
257
  def allow?(method)
215
- (!allowed? || allowed.include?(method) || FORCED_PROXY.include?(method)) && !denied.include?(method)
258
+ (allowed.empty? || allowed.include?(method) || FORCED_PROXY.include?(method)) && !denied.include?(method)
216
259
  end
217
260
  end
218
261
  end
@@ -21,7 +21,7 @@ module Draper
21
21
  def respond_to?(method)
22
22
  super || @wrapped_collection.respond_to?(method)
23
23
  end
24
-
24
+
25
25
  def kind_of?(klass)
26
26
  super || @wrapped_collection.kind_of?(klass)
27
27
  end
@@ -38,5 +38,10 @@ module Draper
38
38
  def to_s
39
39
  "#<DecoratedEnumerableProxy of #{@klass} for #{@wrapped_collection.inspect}>"
40
40
  end
41
+
42
+ def source
43
+ @wrapped_collection
44
+ end
45
+ alias_method :to_source, :source
41
46
  end
42
47
  end
@@ -8,8 +8,8 @@ module Draper::ModelSupport
8
8
 
9
9
  module ClassMethods
10
10
  def decorate(options = {})
11
- @decorator_proxy ||= "#{model_name}Decorator".constantize.decorate(self.scoped, options)
12
- block_given? ? yield(@decorator_proxy) : @decorator_proxy
11
+ decorator_proxy = "#{model_name}Decorator".constantize.decorate(self.scoped, options)
12
+ block_given? ? yield(decorator_proxy) : decorator_proxy
13
13
  end
14
14
  end
15
15
 
@@ -0,0 +1,19 @@
1
+ require 'rails/railtie'
2
+
3
+ module Draper
4
+ class Railtie < Rails::Railtie
5
+
6
+ initializer "draper.extend_action_controller_base" do |app|
7
+ ActiveSupport.on_load(:action_controller) do
8
+ Draper::System.setup(:action_controller)
9
+ end
10
+ end
11
+
12
+ initializer "draper.extend_action_mailer_base" do |app|
13
+ ActiveSupport.on_load(:action_mailer) do
14
+ Draper::System.setup(:action_mailer)
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -1,9 +1,12 @@
1
1
  module Draper
2
2
  class System
3
- def self.setup
4
- ActionController::Base.send(:include, Draper::ViewContextFilter) if defined?(ActionController::Base)
5
- ActionMailer::Base.send(:include, Draper::ViewContextFilter) if defined?(ActionMailer::Base)
6
- ActionController::Base.send(:helper, Draper::HelperSupport) if defined?(ActionController::Base)
3
+ def self.setup(component)
4
+ if component == :action_controller
5
+ ActionController::Base.send(:include, Draper::ViewContextFilter)
6
+ ActionController::Base.extend(Draper::HelperSupport)
7
+ elsif component == :action_mailer
8
+ ActionMailer::Base.send(:include, Draper::ViewContextFilter)
9
+ end
7
10
  end
8
11
  end
9
12
  end
@@ -1,3 +1,3 @@
1
1
  module Draper
2
- VERSION = "0.10.0"
2
+ VERSION = "0.11.0"
3
3
  end
@@ -1,5 +1,5 @@
1
1
  module Draper
2
- class DecoratorGenerator < Rails::Generators::NamedBase
2
+ class DecoratorGenerator < Rails::Generators::Base
3
3
  desc <<-DESC
4
4
  Description:
5
5
  Generate a decorator for the given model.
@@ -7,15 +7,37 @@ module Draper
7
7
  generates: "app/decorators/article_decorator"
8
8
  "spec/decorators/article_decorator_spec"
9
9
  DESC
10
-
10
+
11
+ argument :resource_name, :type => :string
12
+ class_option "test-framework", :type => :string, :default => "rspec", :aliases => "-t", :desc => "Test framework to be invoked"
13
+
11
14
  source_root File.expand_path('../templates', __FILE__)
12
15
 
13
16
  DECORATORS_ROOT = 'app/decorators/'
14
17
 
15
18
  def build_model_decorator
16
- template 'decorator.rb', "#{DECORATORS_ROOT}#{singular_name}_decorator.rb"
19
+ template 'decorator.rb', "#{DECORATORS_ROOT}#{resource_name.singularize}_decorator.rb"
20
+ end
21
+
22
+ def build_decorator_tests
23
+ case options["test-framework"]
24
+ when "rspec"
25
+ build_decorator_spec
26
+ when "test_unit"
27
+ build_decorator_test
28
+ end
29
+ end
30
+
31
+ private
32
+ def build_decorator_spec
33
+ empty_directory 'spec/decorators'
34
+ template 'decorator_spec.rb', File.join('spec/decorators', "#{resource_name.singularize}_decorator_spec.rb")
35
+ end
36
+
37
+ def build_decorator_test
38
+ empty_directory 'test/decorators/'
39
+ template 'decorator_test.rb', File.join('test/decorators', "#{resource_name.singularize}_decorator_test.rb")
17
40
  end
18
41
 
19
- hook_for :test_framework
20
42
  end
21
43
  end
@@ -1,5 +1,5 @@
1
- class <%= singular_name.camelize %>Decorator < ApplicationDecorator
2
- decorates :<%= singular_name.to_sym %>
1
+ class <%= resource_name.singularize.camelize %>Decorator < ApplicationDecorator
2
+ decorates :<%= resource_name.singularize.to_sym %>
3
3
 
4
4
  # Accessing Helpers
5
5
  # You can access any helper via a proxy
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe <%= singular_name.camelize %>Decorator do
3
+ describe <%= resource_name.singularize.camelize %>Decorator do
4
4
  before { ApplicationController.new.set_current_view_context }
5
5
  end
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class <%= singular_name.camelize %>DecoratorTest < ActiveSupport::TestCase
3
+ class <%= resource_name.singularize.camelize %>DecoratorTest < ActiveSupport::TestCase
4
4
  def setup
5
5
  ApplicationController.new.set_current_view_context
6
6
  end
@@ -1,20 +1,20 @@
1
1
  module Draper
2
2
  class InstallGenerator < Rails::Generators::Base
3
-
3
+
4
4
  desc <<-DESC
5
5
  Description:
6
6
  Generate application and spec decorators in your application.
7
7
  DESC
8
-
8
+
9
9
  class_option "test-framework", :type => :string, :default => "rspec", :aliases => "-t", :desc => "Test framework to be invoked"
10
-
10
+
11
11
  source_root File.expand_path('../templates', __FILE__)
12
12
 
13
13
  def build_application_decorator
14
14
  empty_directory 'app/decorators'
15
15
  template 'application_decorator.rb', File.join('app/decorators', 'application_decorator.rb')
16
16
  end
17
-
17
+
18
18
  def build_decorator_tests
19
19
  case options["test-framework"]
20
20
  when "rspec"
@@ -23,13 +23,13 @@ module Draper
23
23
  build_application_decorator_test
24
24
  end
25
25
  end
26
-
26
+
27
27
  private
28
28
  def build_application_decorator_spec
29
29
  empty_directory 'spec/decorators'
30
30
  template 'application_decorator_spec.rb', File.join('spec/decorators', 'application_decorator_spec.rb')
31
31
  end
32
-
32
+
33
33
  def build_application_decorator_test
34
34
  empty_directory 'test/decorators/'
35
35
  template 'application_decorator_test.rb', File.join('test/decorators', 'application_decorator_test.rb')
@@ -1,14 +1,14 @@
1
1
  require File.expand_path('../../draper/decorator/decorator_generator.rb', __FILE__)
2
2
  class Rails::DecoratorGenerator < Draper::DecoratorGenerator
3
-
3
+
4
4
  source_root File.expand_path('../../draper/decorator/templates', __FILE__)
5
-
5
+
6
6
  class_option :invoke_after_finished, :type => :string, :description => "Generator to invoke when finished"
7
7
 
8
8
  def build_model_and_application_decorators
9
9
  super
10
10
  if self.options[:invoke_after_finished]
11
- Rails::Generators.invoke(self.options[:invoke_after_finished], [@name, @_initializer.first[1..-1]])
11
+ Rails::Generators.invoke(self.options[:invoke_after_finished], [@name, @_initializer.first[1..-1]])
12
12
  end
13
13
  end
14
14
 
@@ -17,20 +17,20 @@ Benchmark.bm do |bm|
17
17
  ProductDecorator.decorate(Product.new)
18
18
  end
19
19
  end
20
-
20
+
21
21
  bm.report("#hello_world ") do
22
22
  i.times do |n|
23
23
  ProductDecorator.decorate(Product.new).hello_world
24
24
  end
25
25
  end
26
-
26
+
27
27
  bm.report("#sample_class_method ") do
28
28
  i.times do |n|
29
29
  ProductDecorator.decorate(Product.new).class.sample_class_method
30
30
  end
31
31
  end
32
32
  end
33
-
33
+
34
34
  puts "\n[ Defining methods on method_missing first hit ]"
35
35
  [ 1_000, 10_000, 100_000 ].each do |i|
36
36
  puts "\n[ #{i} ]"
@@ -39,13 +39,13 @@ Benchmark.bm do |bm|
39
39
  FastProductDecorator.decorate(FastProduct.new)
40
40
  end
41
41
  end
42
-
42
+
43
43
  bm.report("#hello_world ") do
44
44
  i.times do |n|
45
45
  FastProductDecorator.decorate(FastProduct.new).hello_world
46
46
  end
47
47
  end
48
-
48
+
49
49
  bm.report("#sample_class_method ") do
50
50
  i.times do |n|
51
51
  FastProductDecorator.decorate(FastProduct.new).class.sample_class_method
@@ -1,11 +1,11 @@
1
1
  require "./performance/models"
2
2
  class ProductDecorator < Draper::Base
3
3
  decorates :product
4
-
4
+
5
5
  def awesome_title
6
6
  "Awesome Title"
7
7
  end
8
-
8
+
9
9
  # Original #method_missing
10
10
  def method_missing(method, *args, &block)
11
11
  if allow?(method)
@@ -23,11 +23,11 @@ end
23
23
 
24
24
  class FastProductDecorator < Draper::Base
25
25
  decorates :product
26
-
26
+
27
27
  def awesome_title
28
28
  "Awesome Title"
29
29
  end
30
-
30
+
31
31
  # Modified #method_missing
32
32
  def method_missing(method, *args, &block)
33
33
  if allow?(method)