draper 0.11.1 → 0.12.0

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