draper 0.11.1 → 0.12.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 (49) hide show
  1. data/CHANGELOG.txt +23 -0
  2. data/Gemfile +4 -0
  3. data/Readme.markdown +32 -8
  4. data/draper.gemspec +1 -0
  5. data/lib/draper.rb +1 -0
  6. data/lib/draper/active_model_support.rb +24 -0
  7. data/lib/draper/base.rb +30 -22
  8. data/lib/draper/decorated_enumerable_proxy.rb +12 -9
  9. data/lib/draper/helper_support.rb +1 -1
  10. data/lib/draper/lazy_helpers.rb +1 -1
  11. data/lib/draper/model_support.rb +2 -4
  12. data/lib/draper/railtie.rb +31 -0
  13. data/lib/draper/rspec_integration.rb +22 -11
  14. data/lib/draper/system.rb +9 -0
  15. data/lib/draper/version.rb +1 -1
  16. data/lib/generators/decorator/decorator_generator.rb +28 -0
  17. data/lib/generators/{draper/decorator → decorator}/templates/decorator.rb +5 -3
  18. data/lib/generators/resource_override.rb +12 -0
  19. data/lib/generators/rspec/decorator_generator.rb +9 -0
  20. data/lib/generators/rspec/templates/decorator_spec.rb +4 -0
  21. data/lib/generators/test_unit/decorator_generator.rb +9 -0
  22. data/lib/generators/test_unit/templates/decorator_test.rb +7 -0
  23. data/performance/active_record.rb +1 -1
  24. data/performance/models.rb +1 -1
  25. data/spec/draper/base_spec.rb +74 -15
  26. data/spec/draper/helper_support_spec.rb +4 -4
  27. data/spec/draper/model_support_spec.rb +1 -1
  28. data/spec/draper/view_context_spec.rb +2 -7
  29. data/spec/generators/decorator/decorator_generator_spec.rb +52 -0
  30. data/spec/spec_helper.rb +21 -17
  31. data/spec/support/samples/active_model.rb +9 -0
  32. data/spec/support/samples/active_record.rb +8 -0
  33. data/spec/support/samples/application_helper.rb +1 -1
  34. data/spec/support/samples/decorator.rb +1 -1
  35. data/spec/support/samples/decorator_with_allows.rb +1 -1
  36. data/spec/support/samples/non_active_model_product.rb +2 -0
  37. data/spec/support/samples/specific_product_decorator.rb +1 -1
  38. metadata +30 -18
  39. data/lib/generators/draper/decorator/USAGE +0 -7
  40. data/lib/generators/draper/decorator/decorator_generator.rb +0 -43
  41. data/lib/generators/draper/decorator/templates/decorator_spec.rb +0 -5
  42. data/lib/generators/draper/decorator/templates/decorator_test.rb +0 -12
  43. data/lib/generators/draper/install/install_generator.rb +0 -39
  44. data/lib/generators/draper/install/templates/application_decorator.rb +0 -28
  45. data/lib/generators/draper/install/templates/application_decorator_spec.rb +0 -4
  46. data/lib/generators/draper/install/templates/application_decorator_test.rb +0 -11
  47. data/lib/generators/rails/decorator_generator.rb +0 -15
  48. data/spec/generators/draper/decorator/decorator_generator_spec.rb +0 -102
  49. data/spec/generators/draper/install/install_generator_spec.rb +0 -46
@@ -1,3 +1,3 @@
1
1
  module Draper
2
- VERSION = "0.11.1"
2
+ VERSION = "0.12.0"
3
3
  end
@@ -0,0 +1,28 @@
1
+ module Rails
2
+ module Generators
3
+ class DecoratorGenerator < NamedBase
4
+ source_root File.expand_path("../templates", __FILE__)
5
+ check_class_collision :suffix => "Decorator"
6
+
7
+ class_option :parent, :type => :string, :desc => "The parent class for the generated decorator"
8
+
9
+ def create_decorator_file
10
+ template 'decorator.rb', File.join('app/decorators', class_path, "#{file_name}_decorator.rb")
11
+ end
12
+
13
+ hook_for :test_framework
14
+
15
+ private
16
+
17
+ def parent_class_name
18
+ if options[:parent]
19
+ options[:parent]
20
+ elsif defined?(:ApplicationDecorator)
21
+ "ApplicationDecorator"
22
+ else
23
+ "Draper::Base"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,5 +1,6 @@
1
- class <%= resource_name.singularize.camelize %>Decorator < ApplicationDecorator
2
- decorates :<%= resource_name.singularize.to_sym %>
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %>Decorator < ApplicationDecorator
3
+ decorates :<%= singular_name %>
3
4
 
4
5
  # Accessing Helpers
5
6
  # You can access any helper via a proxy
@@ -29,4 +30,5 @@ class <%= resource_name.singularize.camelize %>Decorator < ApplicationDecorator
29
30
  # h.content_tag :span, time.strftime("%a %m/%d/%y"),
30
31
  # :class => 'timestamp'
31
32
  # end
32
- end
33
+ end
34
+ <% end -%>
@@ -0,0 +1,12 @@
1
+ require "rails/generators"
2
+ require "rails/generators/rails/resource/resource_generator"
3
+
4
+ module Rails
5
+ module Generators
6
+ ResourceGenerator.class_eval do
7
+ def add_decorator
8
+ invoke "decorator"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ module Rspec
2
+ class DecoratorGenerator < ::Rails::Generators::NamedBase
3
+ source_root File.expand_path('../templates', __FILE__)
4
+
5
+ def create_spec_file
6
+ template 'decorator_spec.rb', File.join('spec/decorators', "#{singular_name}_decorator_spec.rb")
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe <%= class_name %>Decorator do
4
+ end
@@ -0,0 +1,9 @@
1
+ module TestUnit
2
+ class DecoratorGenerator < ::Rails::Generators::NamedBase
3
+ source_root File.expand_path('../templates', __FILE__)
4
+
5
+ def create_test_file
6
+ template 'decorator_test.rb', File.join('test/decorators', "#{singular_name}_decorator_test.rb")
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class <%= class_name %>DecoratorTest < ActiveSupport::TestCase
4
+ def setup
5
+ ApplicationController.new.set_current_view_context
6
+ end
7
+ end
@@ -1,4 +1,4 @@
1
1
  module ActiveRecord
2
2
  class Base
3
3
  end
4
- end
4
+ end
@@ -17,4 +17,4 @@ class FastProduct < ActiveRecord::Base
17
17
  def hello_world
18
18
  "Hello, World"
19
19
  end
20
- end
20
+ end
@@ -4,6 +4,7 @@ describe Draper::Base do
4
4
  before(:each){ ApplicationController.new.set_current_view_context }
5
5
  subject{ Decorator.new(source) }
6
6
  let(:source){ Product.new }
7
+ let(:non_active_model_source){ NonActiveModelProduct.new }
7
8
 
8
9
  context("proxying class methods") do
9
10
  it "should pass missing class method calls on to the wrapped class" do
@@ -14,7 +15,7 @@ describe Draper::Base do
14
15
  subject.class.should respond_to(:sample_class_method)
15
16
  end
16
17
 
17
- it "should still respond_to it's own class methods" do
18
+ it "should still respond_to its own class methods" do
18
19
  subject.class.should respond_to(:own_class_method)
19
20
  end
20
21
  end
@@ -44,6 +45,11 @@ describe Draper::Base do
44
45
  ProductDecorator.new(source).model_class.should == Product
45
46
  end
46
47
 
48
+ it "returns decorator if it's decorated model already" do
49
+ product_decorator = ProductDecorator.new(source)
50
+ ProductDecorator.new(product_decorator).model.should be_instance_of Product
51
+ end
52
+
47
53
  it "should handle plural-like words properly'" do
48
54
  class Business; end
49
55
  expect do
@@ -190,7 +196,7 @@ describe Draper::Base do
190
196
  end
191
197
  end
192
198
 
193
- context("selecting methods") do
199
+ describe "method selection" do
194
200
  it "echos the methods of the wrapped class except default exclusions" do
195
201
  source.methods.each do |method|
196
202
  unless Draper::Base::DEFAULT_DENIED.include?(method)
@@ -203,27 +209,48 @@ describe Draper::Base do
203
209
  DecoratorWithApplicationHelper.new(source).length.should == "overridden"
204
210
  end
205
211
 
206
- it "should always proxy to_param" do
207
- source.send :class_eval, "def to_param; 1; end"
208
- Draper::Base.new(source).to_param.should == 1
209
- end
210
-
211
- it "should always proxy id" do
212
- source.send :class_eval, "def id; 123456789; end"
213
- Draper::Base.new(source).id.should == 123456789
214
- end
215
-
216
212
  it "should not copy the .class, .inspect, or other existing methods" do
217
213
  source.class.should_not == subject.class
218
214
  source.inspect.should_not == subject.inspect
219
215
  source.to_s.should_not == subject.to_s
220
216
  end
217
+
218
+ context "when an ActiveModel descendant" do
219
+ it "should always proxy to_param" do
220
+ source.stub(:to_param).and_return(1)
221
+ Draper::Base.new(source).to_param.should == 1
222
+ end
223
+
224
+ it "should always proxy id" do
225
+ source.stub(:id).and_return(123456789)
226
+ Draper::Base.new(source).id.should == 123456789
227
+ end
228
+
229
+ it "should always proxy errors" do
230
+ Draper::Base.new(source).errors.should be_an_instance_of ActiveModel::Errors
231
+ end
232
+ end
233
+
234
+ context "when not an ActiveModel descendant" do
235
+ it "does not proxy to_param" do
236
+ non_active_model_source.stub(:to_param).and_return(1)
237
+ Draper::Base.new(non_active_model_source).to_param.should_not == 1
238
+ end
239
+
240
+ it "does not proxy errors" do
241
+ Draper::Base.new(non_active_model_source).should_not respond_to :errors
242
+ end
243
+ end
221
244
  end
222
245
 
223
246
  context 'the decorated model' do
224
247
  it 'receives the mixin' do
225
248
  source.class.ancestors.include?(Draper::ModelSupport)
226
249
  end
250
+
251
+ it 'includes ActiveModel support' do
252
+ source.class.ancestors.include?(Draper::ActiveModelSupport)
253
+ end
227
254
  end
228
255
 
229
256
  it "should wrap source methods so they still accept blocks" do
@@ -264,13 +291,32 @@ describe Draper::Base do
264
291
  ProductDecorator.find_by_name_and_size("apples", "large")
265
292
  end
266
293
 
294
+ it "runs find_all_by_(x) finders" do
295
+ Product.should_receive(:find_all_by_name_and_size)
296
+ ProductDecorator.find_all_by_name_and_size("apples", "large")
297
+ end
298
+
299
+ it "runs find_last_by_(x) finders" do
300
+ Product.should_receive(:find_last_by_name_and_size)
301
+ ProductDecorator.find_last_by_name_and_size("apples", "large")
302
+ end
303
+
304
+ it "runs find_or_initialize_by_(x) finders" do
305
+ Product.should_receive(:find_or_initialize_by_name_and_size)
306
+ ProductDecorator.find_or_initialize_by_name_and_size("apples", "large")
307
+ end
308
+
309
+ it "runs find_or_create_by_(x) finders" do
310
+ Product.should_receive(:find_or_create_by_name_and_size)
311
+ ProductDecorator.find_or_create_by_name_and_size("apples", "large")
312
+ end
313
+
267
314
  it "accepts an options hash" do
268
315
  Product.should_receive(:find_by_name_and_size).with("apples", "large", {:role => :admin})
269
316
  ProductDecorator.find_by_name_and_size("apples", "large", {:role => :admin})
270
317
  end
271
318
 
272
319
  it "uses the options hash in the decorator instantiation" do
273
- pending "Figure out an implementation that supports multiple args (find_by_name_and_count_and_size) plus an options hash"
274
320
  Product.should_receive(:find_by_name_and_size).with("apples", "large", {:role => :admin})
275
321
  pd = ProductDecorator.find_by_name_and_size("apples", "large", {:role => :admin})
276
322
  pd.context[:role].should == :admin
@@ -289,6 +335,11 @@ describe Draper::Base do
289
335
  it "returns a collection of wrapped objects" do
290
336
  subject.each{ |decorated| decorated.should be_instance_of(Draper::Base) }
291
337
  end
338
+
339
+ it 'should accepted and store a context for a collection' do
340
+ subject.context = :admin
341
+ subject.each { |decorated| decorated.context.should == :admin }
342
+ end
292
343
  end
293
344
 
294
345
  context "when given a single source object" do
@@ -395,6 +446,14 @@ describe Draper::Base do
395
446
  end
396
447
  end
397
448
 
449
+ context ".respond_to?" do
450
+ it "should delegate respond_to? to the decorated model" do
451
+ other = Draper::Base.new(source)
452
+ source.should_receive(:respond_to?).with(:whatever, true)
453
+ subject.respond_to?(:whatever, true)
454
+ end
455
+ end
456
+
398
457
  context 'position accessors' do
399
458
  [:first, :last].each do |method|
400
459
  context "##{method}" do
@@ -441,8 +500,8 @@ describe Draper::Base do
441
500
 
442
501
  it "should delegate respond_to? to the wrapped collection" do
443
502
  decorator = ProductDecorator.decorate(paged_array)
444
- paged_array.should_receive(:respond_to?).with(:whatever)
445
- decorator.respond_to?(:whatever)
503
+ paged_array.should_receive(:respond_to?).with(:whatever, true)
504
+ decorator.respond_to?(:whatever, true)
446
505
  end
447
506
 
448
507
  it "should return blank for a decorated empty collection" do
@@ -1,16 +1,16 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Draper::HelperSupport do
4
- before(:each){ @product = Product.new}
4
+ let(:product){ Product.new }
5
5
 
6
6
  context '#decorate' do
7
7
  it 'renders a block' do
8
- output = ApplicationController.decorate(@product){|p| p.model.object_id }
9
- output.should == @product.object_id
8
+ output = ApplicationController.decorate(product){|p| p.model.object_id }
9
+ output.should == product.object_id
10
10
  end
11
11
 
12
12
  it 'uses #capture so Rails only renders the content once' do
13
- ApplicationController.decorate(@product){|p| p.model.object_id }
13
+ ApplicationController.decorate(product){|p| p.model.object_id }
14
14
  ApplicationController.capture_triggered.should be
15
15
  end
16
16
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Draper::ModelSupport do
3
+ describe Draper::ActiveModelSupport do
4
4
  subject { Product.new }
5
5
 
6
6
  describe '#decorator' do
@@ -1,13 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Draper::ViewContext do
4
- let (:app_controller) do
5
- ApplicationController
6
- end
7
-
8
- let (:app_controller_instance) do
9
- app_controller.new
10
- end
4
+ let(:app_controller) { ApplicationController }
5
+ let(:app_controller_instance) { app_controller.new }
11
6
 
12
7
  it "implements #set_current_view_context" do
13
8
  app_controller_instance.should respond_to(:set_current_view_context)
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ # Generators are not automatically loaded by Rails
4
+ require 'generators/decorator/decorator_generator'
5
+
6
+ describe Rails::Generators::DecoratorGenerator do
7
+ # Tell the generator where to put its output (what it thinks of as Rails.root)
8
+ destination File.expand_path("../../../../../tmp", __FILE__)
9
+
10
+ before { prepare_destination }
11
+
12
+ context 'decorator context' do
13
+ before { run_generator ["YourModel"] }
14
+
15
+ describe 'app/decorators/your_model_decorator.rb' do
16
+ subject { file('app/decorators/your_model_decorator.rb') }
17
+ it { should exist }
18
+ it { should contain "class YourModelDecorator < ApplicationDecorator" }
19
+ it { should contain "decorates :your_model" }
20
+ end
21
+ end
22
+
23
+ context 'decorator name' do
24
+ before { run_generator ["YourModel", '-t=rspec'] }
25
+
26
+ describe 'spec/decorators/your_model_decorator_spec.rb' do
27
+ subject { file('spec/decorators/your_model_decorator_spec.rb') }
28
+ it { should exist }
29
+ it { should contain "describe YourModelDecorator" }
30
+ end
31
+ end
32
+
33
+ context 'using rspec' do
34
+ before { run_generator ["YourModel", "-t=rspec"] }
35
+
36
+ describe 'spec/decorators/your_model_decorator_spec.rb' do
37
+ subject { file('spec/decorators/your_model_decorator_spec.rb') }
38
+ it { should exist }
39
+ it { should contain "describe YourModelDecorator" }
40
+ end
41
+ end
42
+
43
+ context 'using rspec' do
44
+ before { run_generator ["YourModel", "-t=test_unit"] }
45
+
46
+ describe 'test/decorators/YourModel_decorator_test.rb' do
47
+ subject { file('test/decorators/your_model_decorator_test.rb') }
48
+ it { should exist }
49
+ it { should contain "class YourModelDecoratorTest < ActiveSupport::TestCase" }
50
+ end
51
+ end
52
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,21 +1,25 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
+ require 'rails'
4
+
3
5
  Bundler.require
4
6
 
5
- require './spec/support/samples/active_record.rb'
6
- require './spec/support/samples/application_controller.rb'
7
- require './spec/support/samples/application_helper.rb'
8
- require './spec/support/samples/decorator.rb'
9
- require './spec/support/samples/decorator_with_allows.rb'
10
- require './spec/support/samples/decorator_with_multiple_allows.rb'
11
- require './spec/support/samples/decorator_with_application_helper.rb'
12
- require './spec/support/samples/decorator_with_denies.rb'
13
- require './spec/support/samples/namespaced_product.rb'
14
- require './spec/support/samples/namespaced_product_decorator.rb'
15
- require './spec/support/samples/product.rb'
16
- require './spec/support/samples/product_decorator.rb'
17
- require './spec/support/samples/specific_product_decorator.rb'
18
- require './spec/support/samples/some_thing.rb'
19
- require './spec/support/samples/some_thing_decorator.rb'
20
- require './spec/support/samples/widget.rb'
21
- require './spec/support/samples/widget_decorator.rb'
7
+ require './spec/support/samples/active_model'
8
+ require './spec/support/samples/active_record'
9
+ require './spec/support/samples/application_controller'
10
+ require './spec/support/samples/application_helper'
11
+ require './spec/support/samples/decorator'
12
+ require './spec/support/samples/decorator_with_allows'
13
+ require './spec/support/samples/decorator_with_multiple_allows'
14
+ require './spec/support/samples/decorator_with_application_helper'
15
+ require './spec/support/samples/decorator_with_denies'
16
+ require './spec/support/samples/namespaced_product'
17
+ require './spec/support/samples/namespaced_product_decorator'
18
+ require './spec/support/samples/non_active_model_product'
19
+ require './spec/support/samples/product'
20
+ require './spec/support/samples/product_decorator'
21
+ require './spec/support/samples/specific_product_decorator'
22
+ require './spec/support/samples/some_thing'
23
+ require './spec/support/samples/some_thing_decorator'
24
+ require './spec/support/samples/widget'
25
+ require './spec/support/samples/widget_decorator'
@@ -0,0 +1,9 @@
1
+ module ActiveModel
2
+ module Conversion; end
3
+ module Validations; end
4
+
5
+ class Errors
6
+ def initialize(params = nil)
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,13 @@
1
1
  module ActiveRecord
2
2
  class Base
3
+ include ActiveModel::Validations
4
+ include ActiveModel::Conversion
5
+
6
+ attr_reader :errors, :to_model
7
+
8
+ def initialize
9
+ @errors = ActiveModel::Errors.new(self)
10
+ end
3
11
 
4
12
  def self.limit
5
13
  self