draper 0.17.0 → 1.0.0.beta2

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 (154) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +5 -0
  3. data/CHANGELOG.markdown +49 -0
  4. data/Gemfile +4 -0
  5. data/Rakefile +50 -31
  6. data/Readme.markdown +63 -92
  7. data/draper.gemspec +6 -4
  8. data/lib/draper/collection_decorator.rb +88 -0
  9. data/lib/draper/decoratable.rb +44 -0
  10. data/lib/draper/decorated_association.rb +55 -0
  11. data/lib/draper/decorator.rb +260 -0
  12. data/lib/draper/finders.rb +34 -0
  13. data/lib/draper/helper_proxy.rb +16 -0
  14. data/lib/draper/railtie.rb +17 -21
  15. data/lib/draper/security.rb +48 -0
  16. data/lib/draper/tasks/tu.rake +5 -0
  17. data/lib/draper/test/minitest_integration.rb +5 -24
  18. data/lib/draper/test/rspec_integration.rb +0 -6
  19. data/lib/draper/test/test_unit_integration.rb +9 -0
  20. data/lib/draper/version.rb +1 -1
  21. data/lib/draper/view_context.rb +24 -6
  22. data/lib/draper/view_helpers.rb +36 -0
  23. data/lib/draper.rb +48 -7
  24. data/lib/generators/decorator/decorator_generator.rb +1 -1
  25. data/lib/generators/decorator/templates/decorator.rb +0 -1
  26. data/lib/generators/rspec/decorator_generator.rb +1 -1
  27. data/lib/generators/test_unit/decorator_generator.rb +1 -1
  28. data/lib/generators/test_unit/templates/decorator_test.rb +0 -3
  29. data/spec/draper/collection_decorator_spec.rb +240 -0
  30. data/spec/draper/decoratable_spec.rb +164 -0
  31. data/spec/draper/decorated_association_spec.rb +130 -0
  32. data/spec/draper/decorator_spec.rb +622 -0
  33. data/spec/draper/finders_spec.rb +145 -0
  34. data/spec/draper/helper_proxy_spec.rb +12 -0
  35. data/spec/draper/security_spec.rb +158 -0
  36. data/spec/draper/view_helpers_spec.rb +41 -0
  37. data/spec/dummy/.rspec +2 -0
  38. data/spec/dummy/README.rdoc +261 -0
  39. data/spec/dummy/Rakefile +7 -0
  40. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  41. data/spec/dummy/app/controllers/localized_urls.rb +5 -0
  42. data/spec/dummy/app/controllers/posts_controller.rb +17 -0
  43. data/spec/dummy/app/decorators/post_decorator.rb +29 -0
  44. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  45. data/spec/dummy/app/mailers/application_mailer.rb +3 -0
  46. data/spec/dummy/app/mailers/post_mailer.rb +9 -0
  47. data/spec/dummy/app/models/post.rb +3 -0
  48. data/spec/dummy/app/views/layouts/application.html.erb +11 -0
  49. data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +1 -0
  50. data/spec/dummy/app/views/posts/_post.html.erb +19 -0
  51. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  52. data/spec/dummy/config/application.rb +64 -0
  53. data/spec/dummy/config/boot.rb +10 -0
  54. data/spec/dummy/config/database.yml +25 -0
  55. data/spec/dummy/config/environment.rb +5 -0
  56. data/spec/dummy/config/environments/development.rb +34 -0
  57. data/spec/dummy/config/environments/production.rb +55 -0
  58. data/spec/dummy/config/environments/test.rb +32 -0
  59. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/spec/dummy/config/initializers/inflections.rb +15 -0
  61. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  62. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  63. data/spec/dummy/config/initializers/session_store.rb +8 -0
  64. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  65. data/spec/dummy/config/locales/en.yml +5 -0
  66. data/spec/dummy/config/routes.rb +7 -0
  67. data/spec/dummy/config.ru +4 -0
  68. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
  69. data/spec/dummy/db/schema.rb +21 -0
  70. data/spec/dummy/db/seeds.rb +2 -0
  71. data/spec/dummy/lib/tasks/spec.rake +5 -0
  72. data/spec/dummy/log/.gitkeep +0 -0
  73. data/spec/dummy/public/404.html +26 -0
  74. data/spec/dummy/public/422.html +26 -0
  75. data/spec/dummy/public/500.html +25 -0
  76. data/spec/dummy/public/favicon.ico +0 -0
  77. data/spec/dummy/script/rails +6 -0
  78. data/spec/dummy/spec/decorators/post_decorator_spec.rb +26 -0
  79. data/spec/dummy/spec/mailers/post_mailer_spec.rb +29 -0
  80. data/spec/dummy/spec/spec_helper.rb +9 -0
  81. data/spec/generators/decorator/decorator_generator_spec.rb +25 -6
  82. data/spec/integration/integration_spec.rb +33 -0
  83. data/spec/minitest-rails/spec_type_spec.rb +63 -0
  84. data/{performance → spec/performance}/decorators.rb +2 -4
  85. data/spec/spec_helper.rb +23 -26
  86. data/spec/support/action_controller.rb +12 -0
  87. data/spec/support/active_model.rb +7 -0
  88. data/spec/support/{samples/active_record.rb → active_record.rb} +5 -0
  89. data/spec/support/{samples → decorators}/decorator_with_application_helper.rb +1 -1
  90. data/spec/support/decorators/namespaced_product_decorator.rb +5 -0
  91. data/spec/support/decorators/non_active_model_product_decorator.rb +2 -0
  92. data/spec/support/decorators/product_decorator.rb +23 -0
  93. data/spec/support/decorators/products_decorator.rb +10 -0
  94. data/spec/support/decorators/some_thing_decorator.rb +2 -0
  95. data/spec/support/{samples → decorators}/specific_product_decorator.rb +0 -2
  96. data/spec/support/dummy_app.rb +84 -0
  97. data/spec/support/matchers/have_text.rb +50 -0
  98. data/spec/support/{samples → models}/namespaced_product.rb +1 -3
  99. data/spec/support/{samples → models}/non_active_model_product.rb +1 -0
  100. data/spec/support/{samples → models}/product.rb +17 -2
  101. data/spec/support/models/some_thing.rb +5 -0
  102. data/spec/support/models/uninferrable_decorator_model.rb +3 -0
  103. metadata +211 -102
  104. data/doc/ApplicationDecorator.html +0 -147
  105. data/doc/Draper/AllHelpers.html +0 -256
  106. data/doc/Draper/Base.html +0 -1222
  107. data/doc/Draper/DecoratorGenerator.html +0 -216
  108. data/doc/Draper/LazyHelpers.html +0 -174
  109. data/doc/Draper/ModelSupport.html +0 -164
  110. data/doc/Draper/System.html +0 -179
  111. data/doc/Draper.html +0 -123
  112. data/doc/_index.html +0 -172
  113. data/doc/class_list.html +0 -47
  114. data/doc/css/common.css +0 -1
  115. data/doc/css/full_list.css +0 -53
  116. data/doc/css/style.css +0 -320
  117. data/doc/file_list.html +0 -46
  118. data/doc/frames.html +0 -13
  119. data/doc/index.html +0 -172
  120. data/doc/js/app.js +0 -205
  121. data/doc/js/full_list.js +0 -150
  122. data/doc/js/jquery.js +0 -16
  123. data/doc/method_list.html +0 -174
  124. data/doc/top-level-namespace.html +0 -103
  125. data/lib/draper/active_model_support.rb +0 -27
  126. data/lib/draper/base.rb +0 -312
  127. data/lib/draper/decorated_enumerable_proxy.rb +0 -57
  128. data/lib/draper/model_support.rb +0 -17
  129. data/lib/draper/rspec_integration.rb +0 -2
  130. data/lib/draper/system.rb +0 -10
  131. data/lib/draper/test/view_context.rb +0 -12
  132. data/spec/draper/base_spec.rb +0 -863
  133. data/spec/draper/helper_support_spec.rb +0 -18
  134. data/spec/draper/model_support_spec.rb +0 -48
  135. data/spec/draper/view_context_spec.rb +0 -30
  136. data/spec/support/samples/active_model.rb +0 -9
  137. data/spec/support/samples/application_controller.rb +0 -42
  138. data/spec/support/samples/application_helper.rb +0 -8
  139. data/spec/support/samples/decorator.rb +0 -5
  140. data/spec/support/samples/decorator_with_allows.rb +0 -3
  141. data/spec/support/samples/decorator_with_denies.rb +0 -3
  142. data/spec/support/samples/decorator_with_denies_all.rb +0 -3
  143. data/spec/support/samples/decorator_with_multiple_allows.rb +0 -4
  144. data/spec/support/samples/decorator_with_special_methods.rb +0 -13
  145. data/spec/support/samples/namespaced_product_decorator.rb +0 -7
  146. data/spec/support/samples/product_decorator.rb +0 -7
  147. data/spec/support/samples/sequel_product.rb +0 -13
  148. data/spec/support/samples/some_thing.rb +0 -2
  149. data/spec/support/samples/some_thing_decorator.rb +0 -3
  150. /data/{performance → spec/performance}/active_record.rb +0 -0
  151. /data/{performance/bechmark.rb → spec/performance/benchmark.rb} +0 -0
  152. /data/{performance → spec/performance}/models.rb +0 -0
  153. /data/spec/support/{samples → decorators}/widget_decorator.rb +0 -0
  154. /data/spec/support/{samples → models}/widget.rb +0 -0
data/.gitignore CHANGED
@@ -6,8 +6,11 @@ pkg/*
6
6
  coverage.data
7
7
  coverage/*
8
8
  .yardoc
9
+ doc/*
9
10
  tmp
10
11
  vendor/bundle
11
12
  *.swp
12
13
  *.swo
13
14
  *.DS_Store
15
+ spec/dummy/log/*
16
+ spec/dummy/db/*.sqlite3
data/.travis.yml CHANGED
@@ -1,4 +1,9 @@
1
+ language: ruby
1
2
  rvm:
2
3
  - 1.9.3
3
4
  - rbx-19mode
4
5
  - jruby-19mode
6
+ - ruby-head
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: ruby-head
data/CHANGELOG.markdown CHANGED
@@ -1,5 +1,54 @@
1
1
  # Draper Changelog
2
2
 
3
+ ## 1.0.0.beta2
4
+
5
+ * `has_finders` is now `decorates_finders`. [https://github.com/haines/draper/commit/33f18aa062e0d3848443dbd81047f20d5665579f](https://github.com/haines/draper/commit/33f18aa062e0d3848443dbd81047f20d5665579f)
6
+
7
+ * If a finder method is used, and the source class is not set and cannot be inferred, an `UninferrableSourceError` is raised. [https://github.com/haines/draper/commit/8ef5bf2f02f7033e3cd4f1f5de7397b02c984fe3](https://github.com/haines/draper/commit/8ef5bf2f02f7033e3cd4f1f5de7397b02c984fe3)
8
+
9
+ * Class methods are now properly delegated again. [https://github.com/haines/draper/commit/731995a5feac4cd06cf9328d2892c0eca9992db6](https://github.com/haines/draper/commit/731995a5feac4cd06cf9328d2892c0eca9992db6)
10
+
11
+ * We no longer `respond_to?` private methods on the source. [https://github.com/haines/draper/commit/18ebac81533a6413aa20a3c26f23e91d0b12b031](https://github.com/haines/draper/commit/18ebac81533a6413aa20a3c26f23e91d0b12b031)
12
+
13
+ * Rails versioning relaxed to support Rails 4 [https://github.com/drapergem/draper/commit/8bfd393b5baa7aa1488076a5e2cb88648efaa815](https://github.com/drapergem/draper/commit/8bfd393b5baa7aa1488076a5e2cb88648efaa815)
14
+
15
+ ## 1.0.0.beta1
16
+
17
+ * Renaming `Draper::Base` to `Draper::Decorator`. This is the most significant
18
+ change you'll need to upgrade your application. [https://github.com/drapergem/draper/commit/025742cb3b295d259cf0ecf3669c24817d6f2df1](https://github.com/drapergem/draper/commit/025742cb3b295d259cf0ecf3669c24817d6f2df1)
19
+ * Added an internal Rails application for integration tests. This won't affect
20
+ your application, but we're now running a set of Cucumber tests inside of a
21
+ Rails app in both development and production mode to help ensure that we
22
+ don't make changes that break Draper. [https://github.com/drapergem/draper/commit/90a4859085cab158658d23d77cd3108b6037e36f](https://github.com/drapergem/draper/commit/90a4859085cab158658d23d77cd3108b6037e36f)
23
+ * Add `#decorated?` method. This gives us a free RSpec matcher,
24
+ `is_decorated?`. [https://github.com/drapergem/draper/commit/834a6fd1f24b5646c333a04a99fe9846a58965d6](https://github.com/drapergem/draper/commit/834a6fd1f24b5646c333a04a99fe9846a58965d6)
25
+ * `#decorates` is no longer needed inside your models, and should be removed.
26
+ Decorators automatically infer the class they decorate. [https://github.com/drapergem/draper/commit/e1214d97b62f2cab45227cc650029734160dcdfe](https://github.com/drapergem/draper/commit/e1214d97b62f2cab45227cc650029734160dcdfe)
27
+ * Decorators do not automatically come with 'finders' by default. If you'd like
28
+ to use `SomeDecorator.find(1)`, for example, simply add `#has_finders` to
29
+ the decorator to include them. [https://github.com/drapergem/draper/commit/42b6f78fda4f51845dab4d35da68880f1989d178](https://github.com/drapergem/draper/commit/42b6f78fda4f51845dab4d35da68880f1989d178)
30
+ * To refer to the object being decorated, `#source` is now the preferred
31
+ method. [https://github.com/drapergem/draper/commit/1e84fcb4a0eab0d12f5feda6886ce1caa239cb16](https://github.com/drapergem/draper/commit/1e84fcb4a0eab0d12f5feda6886ce1caa239cb16)
32
+ * `ActiveModel::Serialization` is included in Decorators if you've requred
33
+ `ActiveModel::Serializers`, so that decorators can be serialized. [https://github.com/drapergem/draper/commit/c4b352799067506849abcbf14963ea36abda301c](https://github.com/drapergem/draper/commit/c4b352799067506849abcbf14963ea36abda301c)
34
+ * Properly support Test::Unit [https://github.com/drapergem/draper/commit/087e134ed0885ec11325ffabe8ab2bebef77a33a](https://github.com/drapergem/draper/commit/087e134ed0885ec11325ffabe8ab2bebef77a33a)
35
+
36
+ And many small bug fixes and refactorings.
37
+
38
+ ## 0.18.0
39
+
40
+ * [Adds the ability to decorate an enumerable proxy](https://github.com/drapergem/draper/commit/67c7125192740a7586a3a635acd735ae01b97837)
41
+
42
+ * Many bug fixes.
43
+
44
+ * Last version of Draper in the 0.x series.
45
+
46
+ ## 0.17.0
47
+
48
+ * [Fix earlier fix of `view_context` priming](https://github.com/jcasimir/draper/commit/5da44336)
49
+ * [Add `denies_all`](https://github.com/jcasimir/draper/commit/148e732)
50
+ * [Properly proxy associations with regard to `find`](https://github.com/jcasimir/draper/commit/d46d19205e)
51
+
3
52
  ## 0.16.0
4
53
 
5
54
  * [Automatically prime `view_context`](https://github.com/jcasimir/draper/commit/057ab4e8)
data/Gemfile CHANGED
@@ -1,3 +1,7 @@
1
1
  source :rubygems
2
2
 
3
3
  gemspec
4
+
5
+ gem "sqlite3", :platforms => :ruby
6
+
7
+ gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
data/Rakefile CHANGED
@@ -1,47 +1,66 @@
1
- require 'bundler/gem_tasks'
2
1
  require 'rake'
2
+ require 'bundler/gem_tasks'
3
3
  require 'rspec/core/rake_task'
4
4
 
5
- RCOV = RUBY_VERSION.to_f == 1.8
5
+ def run_in_dummy_app(command)
6
+ success = system("cd spec/dummy && #{command}")
7
+ raise "#{command} failed" unless success
8
+ end
9
+
10
+ task "default" => "ci"
11
+
12
+ desc "Run all tests for CI"
13
+ task "ci" => "spec"
6
14
 
7
- namespace :spec do
15
+ desc "Run all specs"
16
+ task "spec" => "spec:all"
8
17
 
9
- RSpec::Core::RakeTask.new(:coverage) do |t|
10
- t.pattern = 'spec/**/*_spec.rb'
18
+ namespace "spec" do
19
+ task "all" => ["draper", "generators", "minitest-rails", "integration"]
11
20
 
12
- if RCOV
13
- t.rcov = true
14
- t.rcov_opts = '--exclude osx\/objc,spec,gems\/'
21
+ def spec_task(name)
22
+ desc "Run #{name} specs"
23
+ RSpec::Core::RakeTask.new(name) do |t|
24
+ t.pattern = "spec/#{name}/**/*_spec.rb"
15
25
  end
16
26
  end
17
27
 
18
- RSpec::Core::RakeTask.new(:normal) do |t|
19
- t.pattern ='spec/**/*_spec.rb'
20
- t.rcov = false
21
- end
28
+ spec_task "draper"
29
+ spec_task "generators"
30
+ spec_task "minitest-rails"
22
31
 
23
- namespace :coverage do
24
- desc "Cleanup coverage data"
25
- task :cleanup do
26
- rm_rf 'coverage.data'
27
- rm_rf 'coverage'
28
- end
32
+ desc "Run integration specs"
33
+ task "integration" => ["db:setup", "integration:all"]
29
34
 
30
- desc "Browse the code coverage report."
31
- task :report => ["spec:coverage:cleanup", "spec:coverage"] do
32
- if RCOV
33
- require "launchy"
34
- Launchy.open("coverage/index.html")
35
- else
36
- require 'cover_me'
37
- CoverMe.complete!
35
+ namespace "integration" do
36
+ task "all" => ["development", "production", "test"]
37
+
38
+ ["development", "production"].each do |environment|
39
+ task environment do
40
+ Rake::Task["spec:integration:run"].execute environment
38
41
  end
39
42
  end
40
- end
41
43
 
42
- end
44
+ task "run" do |t, environment|
45
+ puts "Running integration specs in #{environment}"
46
+
47
+ ENV["RAILS_ENV"] = environment
48
+ success = system("rspec spec/integration")
43
49
 
44
- desc "RSpec tests"
45
- task "spec" => "spec:normal"
50
+ raise "Integration specs failed in #{environment}" unless success
51
+ end
46
52
 
47
- task "default" => "spec"
53
+ task "test" do
54
+ run_in_dummy_app "rake"
55
+ end
56
+ end
57
+ end
58
+
59
+ namespace "db" do
60
+ desc "Set up databases for integration testing"
61
+ task "setup" do
62
+ run_in_dummy_app "rm -f db/*.sqlite3"
63
+ run_in_dummy_app "RAILS_ENV=development rake db:schema:load db:seed db:test:prepare"
64
+ run_in_dummy_app "RAILS_ENV=production rake db:schema:load db:seed"
65
+ end
66
+ end
data/Readme.markdown CHANGED
@@ -1,55 +1,34 @@
1
1
  # Draper: View Models for Rails
2
2
 
3
- [![TravisCI Build Status](https://secure.travis-ci.org/jcasimir/draper.png)](http://travis-ci.org/jcasimir/draper)
3
+ [![TravisCI Build Status](https://secure.travis-ci.org/drapergem/draper.png?branch=master)](http://travis-ci.org/drapergem/draper)
4
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/drapergem/draper)
4
5
 
5
6
  ## Quick Start
6
7
 
7
8
  1. Add `gem 'draper'` to your `Gemfile` and `bundle`
8
9
  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`
10
+ 3. If `YourModel` already exists, run `rails g decorator YourModel` to create `YourModelDecorator`
10
11
  4. Edit `app/decorators/[your_model]_decorator.rb` using:
11
12
  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`
13
+ 2. the name of your decorated model to access the wrapped object like `article.created_at`
13
14
  5. Wrap models in your controller with the decorator using:
14
- 1. `.find` automatic lookup & wrap
15
- ex: `ArticleDecorator.find(1)`
16
- 2. `.decorate` method with single object or collection,
15
+ 1. `.decorate` method with a single object or collection,
17
16
  ex: `ArticleDecorator.decorate(Article.all)`
18
- 3. `.new` method with single object
17
+ 2. `.new` method with single object
19
18
  ex: `ArticleDecorator.new(Article.first)`
20
- 6. Output the instance methods in your view templates
21
- ex: `@article_decorator.created_at`
19
+ 6. Call decorator methods from your view templates
20
+ ex: `<%= @article_decorator.created_at %>`
22
21
 
23
22
  If you need common methods in your decorators, create an `app/decorators/application_decorator.rb`:
24
23
 
25
24
  ``` ruby
26
- class ApplicationDecorator < Draper::Base
25
+ class ApplicationDecorator < Draper::Decorator
27
26
  # your methods go here
28
27
  end
29
28
  ```
30
29
 
31
30
  and make your decorators inherit from it. Newly generated decorators will respect this choice and inherit from `ApplicationDecorator`.
32
31
 
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
32
  ## Goals
54
33
 
55
34
  This gem makes it easy to apply the decorator pattern to domain models in a Rails application. This pattern gives you three wins:
@@ -65,9 +44,7 @@ Why hate normal helpers? In Ruby/Rails we approach everything from an Object-Ori
65
44
  A decorator wraps an object with presentation-related accessor methods. For instance, if you had an `Article` object, then the decorator could override `.published_at` to use formatted output like this:
66
45
 
67
46
  ```ruby
68
- class ArticleDecorator < Draper::Base
69
- decorates :article
70
-
47
+ class ArticleDecorator < Draper::Decorator
71
48
  def published_at
72
49
  date = h.content_tag(:span, article.published_at.strftime("%A, %B %e").squeeze(" "), :class => 'date')
73
50
  time = h.content_tag(:span, article.published_at.strftime("%l:%M%p"), :class => 'time').delete(" ")
@@ -88,8 +65,6 @@ When you use a decorator you have the power of a Ruby object but it's a part of
88
65
 
89
66
  ```ruby
90
67
  class ArticleDecorator < ApplicationDecorator
91
- decorates :article
92
-
93
68
  ADMIN_VISIBLE_ATTRIBUTES = [:title, :body, :author, :status]
94
69
  PUBLIC_VISIBLE_ATTRIBUTES = [:title, :body]
95
70
 
@@ -110,7 +85,6 @@ The `denies` method takes a blacklist approach. For instance:
110
85
 
111
86
  ```ruby
112
87
  class ArticleDecorator < ApplicationDecorator
113
- decorates :article
114
88
  denies :title
115
89
  end
116
90
  ```
@@ -118,11 +92,19 @@ end
118
92
  Then, to test it:
119
93
 
120
94
  ```irb
121
- > ad = ArticleDecorator.find(1)
122
- => #<ArticleDecorator:0x000001020d7728 @model=#<Article id: 1, title: "Hello, World">>
95
+ > ad = ArticleDecorator.new(Article.find(1))
96
+ => #<ArticleDecorator:0x000001020d7728 @model=#<Article id: 1, title: "Hello, World">>
123
97
  > ad.title
124
98
  NoMethodError: undefined method `title' for #<ArticleDecorator:0x000001020d7728>
125
- ```
99
+ ```
100
+
101
+ You may also blacklist all methods by using `denies_all`:
102
+
103
+ ```ruby
104
+ class ArticleDecorate < ApplicationDecorator
105
+ denies_all
106
+ end
107
+ ```
126
108
 
127
109
  #### Using `allows`
128
110
 
@@ -130,14 +112,13 @@ A better approach is to define a whitelist using `allows`:
130
112
 
131
113
  ```ruby
132
114
  class ArticleDecorator < ApplicationDecorator
133
- decorates :article
134
115
  allows :title, :description
135
116
  end
136
117
  ```
137
118
 
138
119
  ```irb
139
- > ad = ArticleDecorator.find(1)
140
- => #<ArticleDecorator:0x000001020d7728 @model=#<Article id: 1, title: "Hello, World">>
120
+ > ad = ArticleDecorator.new(Article.find(1))
121
+ => #<ArticleDecorator:0x000001020d7728 @model=#<Article id: 1, title: "Hello, World">>
141
122
  > ad.title
142
123
  => "Hello, World"
143
124
  > ad.created_at
@@ -166,14 +147,12 @@ rails generate decorator article
166
147
 
167
148
  ### Writing Methods
168
149
 
169
- Open the decorator model (ex: `app/decorators/article_decorator.rb`) and add normal instance methods. To access the wrapped source object, use a method named after the `decorates` argument:
150
+ Open the decorator model (ex: `app/decorators/article_decorator.rb`) and add normal instance methods. To access the wrapped source object, use the `model` method:
170
151
 
171
152
  ```ruby
172
- class ArticleDecorator < Draper::Base
173
- decorates :article
174
-
153
+ class ArticleDecorator < Draper::Decorator
175
154
  def author_name
176
- article.author.first_name + " " + article.author.last_name
155
+ model.author.first_name + " " + model.author.last_name
177
156
  end
178
157
  end
179
158
  ```
@@ -183,9 +162,7 @@ end
183
162
  You probably want to make use of Rails helpers and those defined in your application. Use the `helpers` or `h` method proxy:
184
163
 
185
164
  ```ruby
186
- class ArticleDecorator < Draper::Base
187
- decorates :article
188
-
165
+ class ArticleDecorator < Draper::Decorator
189
166
  def published_at
190
167
  date = h.content_tag(:span, article.published_at.strftime("%A, %B %e").squeeze(" "), :class => 'date')
191
168
  time = h.content_tag(:span, article.published_at.strftime("%l:%M%p"), :class => 'time').delete(" ")
@@ -199,10 +176,9 @@ end
199
176
  Hate seeing that `h.` proxy all over? Willing to mix a bazillion methods into your decorator? Then try lazy helpers:
200
177
 
201
178
  ```ruby
202
- class ArticleDecorator < Draper::Base
203
- decorates :article
179
+ class ArticleDecorator < Draper::Decorator
204
180
  include Draper::LazyHelpers
205
-
181
+
206
182
  def published_at
207
183
  date = content_tag(:span, article.published_at.strftime("%A, %B %e").squeeze(" "), :class => 'date')
208
184
  time = content_tag(:span, article.published_at.strftime("%l:%M%p"), :class => 'time').delete(" ")
@@ -227,12 +203,6 @@ ArticleDecorator.new(Article.find(params[:id]))
227
203
  ArticleDecorator.decorate(Article.first) # Returns one instance of ArticleDecorator
228
204
  ArticleDecorator.decorate(Article.all) # Returns an enumeration proxy of ArticleDecorator instances
229
205
  ```
230
-
231
- * Call `.find` to automatically do a lookup on the `decorates` class:
232
-
233
- ```ruby
234
- ArticleDecorator.find(1)
235
- ```
236
206
 
237
207
  ### In Your Views
238
208
 
@@ -244,11 +214,11 @@ Use the new methods in your views like any other model method (ex: `@article.pub
244
214
 
245
215
  ### Integration with RSpec
246
216
 
247
- Using the provided generator, Draper will place specs for your new decorator in `spec/decorators/`.
217
+ Using the provided generator, Draper will place specs for your new decorator in `spec/decorators/`.
248
218
 
249
219
  By default, specs in `spec/decorators` will be tagged as `type => :decorator`. Any spec tagged as `decorator` will make helpers available to the decorator.
250
220
 
251
- If your decorator specs live somewhere else, which they shouldn't, make sure to tag them with `type => :decorator`. If you don't tag them, Draper's helpers won't be available to your decorator while testing.
221
+ If your decorator specs live somewhere else, which they shouldn't, make sure to tag them with `type => :decorator`. If you don't tag them, Draper's helpers won't be available to your decorator while testing.
252
222
 
253
223
  Note: If you're using Spork, you need to `require 'draper/test/rspec_integration'` in your Spork.prefork block.
254
224
 
@@ -260,7 +230,13 @@ Here are some ideas of what you might do in decorator methods:
260
230
  * Format dates and times using `strftime`
261
231
  * Implement a commonly used representation of the data object like a `.name` method that combines `first_name` and `last_name` attributes
262
232
 
263
- ## Example Using a Decorator
233
+ ## Learning Resources
234
+
235
+ ### RailsCast
236
+
237
+ Ryan Bates has put together an excellent RailsCast on Draper based on the 0.8.0 release: http://railscasts.com/episodes/286-draper
238
+
239
+ ### Example Using a Decorator
264
240
 
265
241
  For a brief tutorial with sample project, check this out: http://tutorials.jumpstartlab.com/topics/decorators.html
266
242
 
@@ -296,9 +272,9 @@ end
296
272
  Then you need to perform the wrapping in your controller. Here's the simplest method:
297
273
 
298
274
  ```ruby
299
- class ArticlesController < Draper::Decorator
275
+ class ArticlesController < ApplicationController
300
276
  def show
301
- @article = ArticleDecorator.find params[:id]
277
+ @article = ArticleDecorator.new(Article.find(params[:id]))
302
278
  end
303
279
  end
304
280
  ```
@@ -312,9 +288,7 @@ Then within your views you can utilize both the normal data methods and your new
312
288
  Ta-da! Object-oriented data formatting for your view layer. Below is the complete decorator with extra comments removed:
313
289
 
314
290
  ```ruby
315
- class ArticleDecorator < Draper::Base
316
- decorates :article
317
-
291
+ class ArticleDecorator < Draper::Decorator
318
292
  def published_at
319
293
  date = h.content_tag(:span, article.published_at.strftime("%A, %B %e").squeeze(" "), :class => 'date')
320
294
  time = h.content_tag(:span, article.published_at.strftime("%l:%M%p"), :class => 'time').delete(" ")
@@ -328,14 +302,11 @@ end
328
302
  Add a `decorates_association :association_name` to gain access to a decorated version of your target association.
329
303
 
330
304
  ```ruby
331
- class ArticleDecorator < Draper::Base
332
- decorates :article
333
- decorates_association :author # belongs_to :author association
305
+ class ArticleDecorator < Draper::Decorator
306
+ decorates_association :author # belongs_to :author association
334
307
  end
335
308
 
336
- class AuthorDecorator < Draper::Base
337
- decorates :author
338
-
309
+ class AuthorDecorator < Draper::Decorator
339
310
  def fancy_name
340
311
  "#{model.title}. #{model.first_name} #{model.middle_name[0]}. #{model.last_name}"
341
312
  end
@@ -360,7 +331,6 @@ end
360
331
 
361
332
  # app/decorators/user_decorator.rb
362
333
  class UserDecorator < ApplicationDecorator
363
- decorates :user
364
334
  end
365
335
  ```
366
336
 
@@ -380,29 +350,30 @@ For automatic decoration, check out [decorates_before_rendering](https://github.
380
350
  4. Push to the branch (`git push origin my_awesome_branch`)
381
351
  5. Send pull request
382
352
 
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
353
+ ## Running tests
354
+
355
+ If it's the first time you want to run the tests, start by creating the dummy database:
356
+
357
+ ```
358
+ $ rake db:migrate
359
+ ```
360
+
361
+ You can then run tests by executing `rake`.
362
+
363
+ ## Contributors
364
+
365
+ Draper was conceived by Jeff Casimir and heavily refined by Steve Klabnik and a great community of open source contributors.
366
+
367
+ ### Core Team
368
+
369
+ * Jeff Casimir (jeff@jumpstartlab.com)
370
+ * Steve Klabnik (steve@jumpstartlab.com)
371
+ * Vasiliy Ermolovich
399
372
 
400
373
  ## License
401
374
 
402
375
  (The MIT License)
403
376
 
404
- Copyright © 2011 Jeff Casimir
405
-
406
377
  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
378
 
408
379
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
data/draper.gemspec CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.version = Draper::VERSION
8
8
  s.authors = ["Jeff Casimir", "Steve Klabnik"]
9
9
  s.email = ["jeff@casimircreative.com", "steve@steveklabnik.com"]
10
- s.homepage = "http://github.com/jcasimir/draper"
10
+ s.homepage = "http://github.com/drapergem/draper"
11
11
  s.summary = "Decorator pattern implementation for Rails."
12
12
  s.description = "Draper implements a decorator or presenter pattern for Rails applications."
13
13
  s.rubyforge_project = "draper"
@@ -16,12 +16,14 @@ Gem::Specification.new do |s|
16
16
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
17
  s.require_paths = ["lib"]
18
18
 
19
- s.add_dependency 'activesupport', '~> 3.2'
20
- s.add_dependency 'actionpack', '~> 3.2'
19
+ s.add_dependency 'activesupport', '>= 3.2'
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"
28
+ s.add_development_dependency 'capybara'
27
29
  end
@@ -0,0 +1,88 @@
1
+ module Draper
2
+ class CollectionDecorator
3
+ include Enumerable
4
+ include ViewHelpers
5
+
6
+ attr_accessor :source, :options, :decorator_class
7
+ alias_method :to_source, :source
8
+
9
+ delegate :as_json, *(Array.instance_methods - Object.instance_methods), to: :decorated_collection
10
+
11
+ # @param source collection to decorate
12
+ # @param options [Hash] passed to each item's decorator (except
13
+ # for the keys listed below)
14
+ # @option options [Class,Symbol] :with the class used to decorate
15
+ # items, or `:infer` to call each item's `decorate` method instead
16
+ def initialize(source, options = {})
17
+ @source = source
18
+ @decorator_class = options.delete(:with) || self.class.inferred_decorator_class
19
+ @options = options
20
+ end
21
+
22
+ class << self
23
+ alias_method :decorate, :new
24
+ end
25
+
26
+ def decorated_collection
27
+ @decorated_collection ||= source.collect {|item| decorate_item(item) }
28
+ end
29
+
30
+ def find(*args, &block)
31
+ if block_given?
32
+ decorated_collection.find(*args, &block)
33
+ else
34
+ decorator_class.find(*args)
35
+ end
36
+ end
37
+
38
+ def method_missing(method, *args, &block)
39
+ source.send(method, *args, &block)
40
+ end
41
+
42
+ def respond_to?(method, include_private = false)
43
+ super || source.respond_to?(method, include_private)
44
+ end
45
+
46
+ def kind_of?(klass)
47
+ super || source.kind_of?(klass)
48
+ end
49
+ alias_method :is_a?, :kind_of?
50
+
51
+ def ==(other)
52
+ source == (other.respond_to?(:source) ? other.source : other)
53
+ end
54
+
55
+ def to_s
56
+ "#<CollectionDecorator of #{decorator_class} for #{source.inspect}>"
57
+ end
58
+
59
+ def options=(options)
60
+ each {|item| item.options = options }
61
+ @options = options
62
+ end
63
+
64
+ protected
65
+
66
+ def decorate_item(item)
67
+ if decorator_class == :infer
68
+ item.decorate(options)
69
+ else
70
+ decorator_class.decorate(item, options)
71
+ end
72
+ end
73
+
74
+ def self.inferred_decorator_class
75
+ decorator_name = "#{name.chomp("Decorator").singularize}Decorator"
76
+ decorator_uninferrable if decorator_name == name
77
+
78
+ decorator_name.constantize
79
+
80
+ rescue NameError
81
+ decorator_uninferrable
82
+ end
83
+
84
+ def self.decorator_uninferrable
85
+ raise Draper::UninferrableDecoratorError.new(self)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,44 @@
1
+ module Draper::Decoratable
2
+ extend ActiveSupport::Concern
3
+
4
+ def decorate(options = {})
5
+ decorator_class.decorate(self, options)
6
+ end
7
+
8
+ def decorator_class
9
+ self.class.decorator_class
10
+ end
11
+
12
+ def applied_decorators
13
+ []
14
+ end
15
+
16
+ def decorated_with?(decorator_class)
17
+ false
18
+ end
19
+
20
+ def decorated?
21
+ false
22
+ end
23
+
24
+ def ===(other)
25
+ super || (other.respond_to?(:source) && super(other.source))
26
+ end
27
+
28
+ module ClassMethods
29
+ def decorate(options = {})
30
+ decorator_class.decorate_collection(self.scoped, options)
31
+ end
32
+
33
+ def decorator_class
34
+ prefix = respond_to?(:model_name) ? model_name : name
35
+ "#{prefix}Decorator".constantize
36
+ rescue NameError
37
+ raise Draper::UninferrableDecoratorError.new(self)
38
+ end
39
+
40
+ def ===(other)
41
+ super || (other.respond_to?(:source) && super(other.source))
42
+ end
43
+ end
44
+ end