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.
- data/.gitignore +3 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.markdown +49 -0
- data/Gemfile +4 -0
- data/Rakefile +50 -31
- data/Readme.markdown +63 -92
- data/draper.gemspec +6 -4
- data/lib/draper/collection_decorator.rb +88 -0
- data/lib/draper/decoratable.rb +44 -0
- data/lib/draper/decorated_association.rb +55 -0
- data/lib/draper/decorator.rb +260 -0
- data/lib/draper/finders.rb +34 -0
- data/lib/draper/helper_proxy.rb +16 -0
- data/lib/draper/railtie.rb +17 -21
- data/lib/draper/security.rb +48 -0
- data/lib/draper/tasks/tu.rake +5 -0
- data/lib/draper/test/minitest_integration.rb +5 -24
- data/lib/draper/test/rspec_integration.rb +0 -6
- data/lib/draper/test/test_unit_integration.rb +9 -0
- data/lib/draper/version.rb +1 -1
- data/lib/draper/view_context.rb +24 -6
- data/lib/draper/view_helpers.rb +36 -0
- data/lib/draper.rb +48 -7
- data/lib/generators/decorator/decorator_generator.rb +1 -1
- data/lib/generators/decorator/templates/decorator.rb +0 -1
- data/lib/generators/rspec/decorator_generator.rb +1 -1
- data/lib/generators/test_unit/decorator_generator.rb +1 -1
- data/lib/generators/test_unit/templates/decorator_test.rb +0 -3
- data/spec/draper/collection_decorator_spec.rb +240 -0
- data/spec/draper/decoratable_spec.rb +164 -0
- data/spec/draper/decorated_association_spec.rb +130 -0
- data/spec/draper/decorator_spec.rb +622 -0
- data/spec/draper/finders_spec.rb +145 -0
- data/spec/draper/helper_proxy_spec.rb +12 -0
- data/spec/draper/security_spec.rb +158 -0
- data/spec/draper/view_helpers_spec.rb +41 -0
- data/spec/dummy/.rspec +2 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/dummy/app/controllers/localized_urls.rb +5 -0
- data/spec/dummy/app/controllers/posts_controller.rb +17 -0
- data/spec/dummy/app/decorators/post_decorator.rb +29 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/application_mailer.rb +3 -0
- data/spec/dummy/app/mailers/post_mailer.rb +9 -0
- data/spec/dummy/app/models/post.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +11 -0
- data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +1 -0
- data/spec/dummy/app/views/posts/_post.html.erb +19 -0
- data/spec/dummy/app/views/posts/show.html.erb +1 -0
- data/spec/dummy/config/application.rb +64 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +34 -0
- data/spec/dummy/config/environments/production.rb +55 -0
- data/spec/dummy/config/environments/test.rb +32 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +7 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
- data/spec/dummy/db/schema.rb +21 -0
- data/spec/dummy/db/seeds.rb +2 -0
- data/spec/dummy/lib/tasks/spec.rake +5 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/spec/decorators/post_decorator_spec.rb +26 -0
- data/spec/dummy/spec/mailers/post_mailer_spec.rb +29 -0
- data/spec/dummy/spec/spec_helper.rb +9 -0
- data/spec/generators/decorator/decorator_generator_spec.rb +25 -6
- data/spec/integration/integration_spec.rb +33 -0
- data/spec/minitest-rails/spec_type_spec.rb +63 -0
- data/{performance → spec/performance}/decorators.rb +2 -4
- data/spec/spec_helper.rb +23 -26
- data/spec/support/action_controller.rb +12 -0
- data/spec/support/active_model.rb +7 -0
- data/spec/support/{samples/active_record.rb → active_record.rb} +5 -0
- data/spec/support/{samples → decorators}/decorator_with_application_helper.rb +1 -1
- data/spec/support/decorators/namespaced_product_decorator.rb +5 -0
- data/spec/support/decorators/non_active_model_product_decorator.rb +2 -0
- data/spec/support/decorators/product_decorator.rb +23 -0
- data/spec/support/decorators/products_decorator.rb +10 -0
- data/spec/support/decorators/some_thing_decorator.rb +2 -0
- data/spec/support/{samples → decorators}/specific_product_decorator.rb +0 -2
- data/spec/support/dummy_app.rb +84 -0
- data/spec/support/matchers/have_text.rb +50 -0
- data/spec/support/{samples → models}/namespaced_product.rb +1 -3
- data/spec/support/{samples → models}/non_active_model_product.rb +1 -0
- data/spec/support/{samples → models}/product.rb +17 -2
- data/spec/support/models/some_thing.rb +5 -0
- data/spec/support/models/uninferrable_decorator_model.rb +3 -0
- metadata +211 -102
- data/doc/ApplicationDecorator.html +0 -147
- data/doc/Draper/AllHelpers.html +0 -256
- data/doc/Draper/Base.html +0 -1222
- data/doc/Draper/DecoratorGenerator.html +0 -216
- data/doc/Draper/LazyHelpers.html +0 -174
- data/doc/Draper/ModelSupport.html +0 -164
- data/doc/Draper/System.html +0 -179
- data/doc/Draper.html +0 -123
- data/doc/_index.html +0 -172
- data/doc/class_list.html +0 -47
- data/doc/css/common.css +0 -1
- data/doc/css/full_list.css +0 -53
- data/doc/css/style.css +0 -320
- data/doc/file_list.html +0 -46
- data/doc/frames.html +0 -13
- data/doc/index.html +0 -172
- data/doc/js/app.js +0 -205
- data/doc/js/full_list.js +0 -150
- data/doc/js/jquery.js +0 -16
- data/doc/method_list.html +0 -174
- data/doc/top-level-namespace.html +0 -103
- data/lib/draper/active_model_support.rb +0 -27
- data/lib/draper/base.rb +0 -312
- data/lib/draper/decorated_enumerable_proxy.rb +0 -57
- data/lib/draper/model_support.rb +0 -17
- data/lib/draper/rspec_integration.rb +0 -2
- data/lib/draper/system.rb +0 -10
- data/lib/draper/test/view_context.rb +0 -12
- data/spec/draper/base_spec.rb +0 -863
- data/spec/draper/helper_support_spec.rb +0 -18
- data/spec/draper/model_support_spec.rb +0 -48
- data/spec/draper/view_context_spec.rb +0 -30
- data/spec/support/samples/active_model.rb +0 -9
- data/spec/support/samples/application_controller.rb +0 -42
- data/spec/support/samples/application_helper.rb +0 -8
- data/spec/support/samples/decorator.rb +0 -5
- data/spec/support/samples/decorator_with_allows.rb +0 -3
- data/spec/support/samples/decorator_with_denies.rb +0 -3
- data/spec/support/samples/decorator_with_denies_all.rb +0 -3
- data/spec/support/samples/decorator_with_multiple_allows.rb +0 -4
- data/spec/support/samples/decorator_with_special_methods.rb +0 -13
- data/spec/support/samples/namespaced_product_decorator.rb +0 -7
- data/spec/support/samples/product_decorator.rb +0 -7
- data/spec/support/samples/sequel_product.rb +0 -13
- data/spec/support/samples/some_thing.rb +0 -2
- data/spec/support/samples/some_thing_decorator.rb +0 -3
- /data/{performance → spec/performance}/active_record.rb +0 -0
- /data/{performance/bechmark.rb → spec/performance/benchmark.rb} +0 -0
- /data/{performance → spec/performance}/models.rb +0 -0
- /data/spec/support/{samples → decorators}/widget_decorator.rb +0 -0
- /data/spec/support/{samples → models}/widget.rb +0 -0
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
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
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
|
-
|
|
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
|
-
|
|
15
|
+
desc "Run all specs"
|
|
16
|
+
task "spec" => "spec:all"
|
|
8
17
|
|
|
9
|
-
|
|
10
|
-
|
|
18
|
+
namespace "spec" do
|
|
19
|
+
task "all" => ["draper", "generators", "minitest-rails", "integration"]
|
|
11
20
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
end
|
|
28
|
+
spec_task "draper"
|
|
29
|
+
spec_task "generators"
|
|
30
|
+
spec_task "minitest-rails"
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
31
|
-
task
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
50
|
+
raise "Integration specs failed in #{environment}" unless success
|
|
51
|
+
end
|
|
46
52
|
|
|
47
|
-
task "
|
|
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
|
-
[](http://travis-ci.org/drapergem/draper)
|
|
4
|
+
[](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.
|
|
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. `.
|
|
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
|
-
|
|
17
|
+
2. `.new` method with single object
|
|
19
18
|
ex: `ArticleDecorator.new(Article.first)`
|
|
20
|
-
6.
|
|
21
|
-
ex:
|
|
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::
|
|
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
|
-
[](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::
|
|
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
|
|
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::
|
|
173
|
-
decorates :article
|
|
174
|
-
|
|
153
|
+
class ArticleDecorator < Draper::Decorator
|
|
175
154
|
def author_name
|
|
176
|
-
|
|
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::
|
|
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::
|
|
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
|
-
##
|
|
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 <
|
|
275
|
+
class ArticlesController < ApplicationController
|
|
300
276
|
def show
|
|
301
|
-
@article = ArticleDecorator.find
|
|
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::
|
|
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::
|
|
332
|
-
|
|
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::
|
|
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
|
-
##
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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/
|
|
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', '
|
|
20
|
-
s.add_dependency 'actionpack', '
|
|
19
|
+
s.add_dependency 'activesupport', '>= 3.2'
|
|
20
|
+
s.add_dependency 'actionpack', '>= 3.2'
|
|
21
21
|
|
|
22
|
-
s.add_development_dependency 'ammeter'
|
|
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
|