draper 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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)