drape 1.0.0.beta1
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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +27 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1159 -0
- data/.travis.yml +14 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +230 -0
- data/CONTRIBUTING.md +20 -0
- data/Gemfile +16 -0
- data/Guardfile +26 -0
- data/LICENSE +7 -0
- data/README.md +592 -0
- data/Rakefile +76 -0
- data/drape.gemspec +31 -0
- data/gemfiles/5.0.gemfile +8 -0
- data/lib/drape.rb +63 -0
- data/lib/drape/automatic_delegation.rb +55 -0
- data/lib/drape/collection_decorator.rb +102 -0
- data/lib/drape/decoratable.rb +93 -0
- data/lib/drape/decoratable/equality.rb +26 -0
- data/lib/drape/decorated_association.rb +33 -0
- data/lib/drape/decorates_assigned.rb +44 -0
- data/lib/drape/decorator.rb +282 -0
- data/lib/drape/delegation.rb +13 -0
- data/lib/drape/factory.rb +91 -0
- data/lib/drape/finders.rb +36 -0
- data/lib/drape/helper_proxy.rb +43 -0
- data/lib/drape/helper_support.rb +5 -0
- data/lib/drape/lazy_helpers.rb +13 -0
- data/lib/drape/railtie.rb +69 -0
- data/lib/drape/tasks/test.rake +9 -0
- data/lib/drape/test/devise_helper.rb +30 -0
- data/lib/drape/test/minitest_integration.rb +6 -0
- data/lib/drape/test/rspec_integration.rb +20 -0
- data/lib/drape/test_case.rb +42 -0
- data/lib/drape/undecorate.rb +9 -0
- data/lib/drape/version.rb +3 -0
- data/lib/drape/view_context.rb +104 -0
- data/lib/drape/view_context/build_strategy.rb +46 -0
- data/lib/drape/view_helpers.rb +34 -0
- data/lib/generators/controller_override.rb +17 -0
- data/lib/generators/mini_test/decorator_generator.rb +20 -0
- data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
- data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
- data/lib/generators/rails/decorator_generator.rb +36 -0
- data/lib/generators/rails/templates/decorator.rb +19 -0
- data/lib/generators/rspec/decorator_generator.rb +9 -0
- data/lib/generators/rspec/templates/decorator_spec.rb +4 -0
- data/lib/generators/test_unit/decorator_generator.rb +9 -0
- data/lib/generators/test_unit/templates/decorator_test.rb +4 -0
- data/spec/draper/collection_decorator_spec.rb +305 -0
- data/spec/draper/decoratable/equality_spec.rb +10 -0
- data/spec/draper/decoratable_spec.rb +203 -0
- data/spec/draper/decorated_association_spec.rb +85 -0
- data/spec/draper/decorates_assigned_spec.rb +70 -0
- data/spec/draper/decorator_spec.rb +828 -0
- data/spec/draper/factory_spec.rb +250 -0
- data/spec/draper/finders_spec.rb +165 -0
- data/spec/draper/helper_proxy_spec.rb +61 -0
- data/spec/draper/lazy_helpers_spec.rb +21 -0
- data/spec/draper/undecorate_spec.rb +19 -0
- data/spec/draper/view_context/build_strategy_spec.rb +116 -0
- data/spec/draper/view_context_spec.rb +160 -0
- data/spec/draper/view_helpers_spec.rb +8 -0
- data/spec/dummy/.gitignore +21 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +16 -0
- data/spec/dummy/app/assets/javascripts/cable.js +13 -0
- data/spec/dummy/app/assets/javascripts/channels/.keep +0 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +5 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/controllers/posts_controller.rb +21 -0
- data/spec/dummy/app/decorators/mongoid_post_decorator.rb +4 -0
- data/spec/dummy/app/decorators/post_decorator.rb +59 -0
- data/spec/dummy/app/helpers/application_helper.rb +5 -0
- data/spec/dummy/app/jobs/application_job.rb +2 -0
- data/spec/dummy/app/mailers/application_mailer.rb +4 -0
- data/spec/dummy/app/mailers/post_mailer.rb +18 -0
- data/spec/dummy/app/models/admin.rb +5 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/models/mongoid_post.rb +5 -0
- data/spec/dummy/app/models/post.rb +3 -0
- data/spec/dummy/app/models/user.rb +5 -0
- data/spec/dummy/app/views/layouts/application.html.erb +9 -0
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +31 -0
- data/spec/dummy/app/views/posts/_post.html.erb +40 -0
- data/spec/dummy/app/views/posts/show.html.erb +1 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +9 -0
- data/spec/dummy/bin/rake +9 -0
- data/spec/dummy/bin/setup +34 -0
- data/spec/dummy/bin/spring +15 -0
- data/spec/dummy/bin/update +29 -0
- data/spec/dummy/config.ru +5 -0
- data/spec/dummy/config/application.rb +21 -0
- data/spec/dummy/config/boot.rb +2 -0
- data/spec/dummy/config/cable.yml +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 +51 -0
- data/spec/dummy/config/environments/production.rb +91 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/active_record_belongs_to_required_by_default.rb +6 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/callback_terminator.rb +6 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/devise.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/per_form_csrf_tokens.rb +4 -0
- data/spec/dummy/config/initializers/request_forgery_protection.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/ssl_options.rb +4 -0
- data/spec/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/mongoid.yml +16 -0
- data/spec/dummy/config/puma.rb +47 -0
- data/spec/dummy/config/routes.rb +8 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config/spring.rb +6 -0
- data/spec/dummy/db/migrate/20121019115657_create_posts.rb +7 -0
- data/spec/dummy/db/schema.rb +19 -0
- data/spec/dummy/db/seeds.rb +2 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/lib/tasks/.keep +0 -0
- data/spec/dummy/lib/tasks/test.rake +16 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/robots.txt +5 -0
- data/spec/dummy/spec/decorators/active_model_serializers_spec.rb +12 -0
- data/spec/dummy/spec/decorators/devise_spec.rb +64 -0
- data/spec/dummy/spec/decorators/helpers_spec.rb +21 -0
- data/spec/dummy/spec/decorators/post_decorator_spec.rb +66 -0
- data/spec/dummy/spec/decorators/spec_type_spec.rb +7 -0
- data/spec/dummy/spec/decorators/view_context_spec.rb +22 -0
- data/spec/dummy/spec/mailers/post_mailer_spec.rb +33 -0
- data/spec/dummy/spec/models/mongoid_post_spec.rb +8 -0
- data/spec/dummy/spec/models/post_spec.rb +6 -0
- data/spec/dummy/spec/shared_examples/decoratable.rb +26 -0
- data/spec/dummy/spec/spec_helper.rb +8 -0
- data/spec/dummy/test/controllers/.keep +0 -0
- data/spec/dummy/test/fixtures/.keep +0 -0
- data/spec/dummy/test/fixtures/files/.keep +0 -0
- data/spec/dummy/test/helpers/.keep +0 -0
- data/spec/dummy/test/integration/.keep +0 -0
- data/spec/dummy/test/mailers/.keep +0 -0
- data/spec/dummy/test/models/.keep +0 -0
- data/spec/dummy/test/test_helper.rb +10 -0
- data/spec/dummy/vendor/assets/javascripts/.keep +0 -0
- data/spec/dummy/vendor/assets/stylesheets/.keep +0 -0
- data/spec/generators/controller/controller_generator_spec.rb +24 -0
- data/spec/generators/decorator/decorator_generator_spec.rb +94 -0
- data/spec/integration/integration_spec.rb +68 -0
- data/spec/performance/active_record.rb +4 -0
- data/spec/performance/benchmark.rb +57 -0
- data/spec/performance/decorators.rb +41 -0
- data/spec/performance/models.rb +20 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/support/dummy_app.rb +88 -0
- data/spec/support/matchers/have_text.rb +50 -0
- data/spec/support/shared_examples/decoratable_equality.rb +40 -0
- data/spec/support/shared_examples/view_helpers.rb +39 -0
- metadata +497 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'bundler/gem_tasks'
|
|
3
|
+
require 'rspec/core/rake_task'
|
|
4
|
+
|
|
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'
|
|
14
|
+
|
|
15
|
+
desc 'Run all specs'
|
|
16
|
+
task 'spec' => 'spec:all'
|
|
17
|
+
|
|
18
|
+
namespace 'spec' do
|
|
19
|
+
task 'all' => ['drape', 'generators', 'integration']
|
|
20
|
+
|
|
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"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
spec_task 'drape'
|
|
29
|
+
spec_task 'generators'
|
|
30
|
+
|
|
31
|
+
desc 'Run integration specs'
|
|
32
|
+
task 'integration' => ['db:setup', 'integration:all']
|
|
33
|
+
|
|
34
|
+
namespace 'integration' do
|
|
35
|
+
task 'all' => ['development', 'production', 'test']
|
|
36
|
+
|
|
37
|
+
['development', 'production'].each do |environment|
|
|
38
|
+
task environment do
|
|
39
|
+
Rake::Task['spec:integration:run'].execute environment
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
task 'run' do |_t, environment|
|
|
44
|
+
puts "Running integration specs in #{environment}"
|
|
45
|
+
|
|
46
|
+
ENV['RAILS_ENV'] = environment
|
|
47
|
+
success = system('bundle exec rspec spec/integration')
|
|
48
|
+
|
|
49
|
+
raise "Integration specs failed in #{environment}" unless success
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
task 'test' do
|
|
53
|
+
puts 'Running rake in dummy app'
|
|
54
|
+
ENV['RAILS_ENV'] = 'test'
|
|
55
|
+
run_in_dummy_app 'bundle exec rake'
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
namespace 'db' do
|
|
61
|
+
desc 'Set up databases for integration testing'
|
|
62
|
+
task 'setup' do
|
|
63
|
+
puts 'Setting up databases'
|
|
64
|
+
|
|
65
|
+
run_in_dummy_app 'rm -f db/*.sqlite3'
|
|
66
|
+
|
|
67
|
+
run_in_dummy_app 'RAILS_ENV=development rails db:environment:set'
|
|
68
|
+
run_in_dummy_app 'RAILS_ENV=development rails db:schema:load db:seed'
|
|
69
|
+
|
|
70
|
+
run_in_dummy_app 'RAILS_ENV=production rails db:environment:set'
|
|
71
|
+
run_in_dummy_app 'RAILS_ENV=production rails db:schema:load db:seed'
|
|
72
|
+
|
|
73
|
+
run_in_dummy_app 'RAILS_ENV=test rails db:environment:set'
|
|
74
|
+
run_in_dummy_app 'RAILS_ENV=test rails db:schema:load db:seed'
|
|
75
|
+
end
|
|
76
|
+
end
|
data/drape.gemspec
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
|
3
|
+
require 'drape/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = 'drape'
|
|
7
|
+
s.version = Drape::VERSION
|
|
8
|
+
s.authors = ['Andrew Emelianenko', 'Jeff Casimir', 'Steve Klabnik']
|
|
9
|
+
s.email = ['andrew@soften-it.com', 'jeff@casimircreative.com', 'steve@steveklabnik.com']
|
|
10
|
+
s.homepage = 'http://github.com/MrEmelianenko/drape'
|
|
11
|
+
s.summary = 'View Models for Rails'
|
|
12
|
+
s.description = 'Drape adds an object-oriented layer of presentation logic to your Rails apps.'
|
|
13
|
+
s.license = 'MIT'
|
|
14
|
+
|
|
15
|
+
s.files = `git ls-files`.split("\n")
|
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
|
18
|
+
s.require_paths = ['lib']
|
|
19
|
+
|
|
20
|
+
s.add_dependency 'activesupport', '>= 5.0.0.beta1'
|
|
21
|
+
s.add_dependency 'actionpack', '>= 5.0.0.beta1'
|
|
22
|
+
s.add_dependency 'request_store', '~> 1.0'
|
|
23
|
+
s.add_dependency 'activemodel', '>= 5.0.0.beta1'
|
|
24
|
+
s.add_dependency 'activemodel-serializers-xml', '>= 1.0'
|
|
25
|
+
|
|
26
|
+
s.add_development_dependency 'rake', '>= 0.9.2'
|
|
27
|
+
s.add_development_dependency 'rspec-rails', '>= 3.5.0.beta1'
|
|
28
|
+
s.add_development_dependency 'minitest-rails', '>= 2.2.0'
|
|
29
|
+
s.add_development_dependency 'capybara'
|
|
30
|
+
s.add_development_dependency 'active_model_serializers'
|
|
31
|
+
end
|
data/lib/drape.rb
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'action_view'
|
|
2
|
+
require 'active_model/naming'
|
|
3
|
+
require 'active_model/serialization'
|
|
4
|
+
require 'active_model/serializers/json'
|
|
5
|
+
require 'active_model/serializers/xml'
|
|
6
|
+
require 'active_support/inflector'
|
|
7
|
+
require 'active_support/core_ext/hash/keys'
|
|
8
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
|
9
|
+
require 'active_support/core_ext/name_error'
|
|
10
|
+
|
|
11
|
+
require 'drape/version'
|
|
12
|
+
require 'drape/view_helpers'
|
|
13
|
+
require 'drape/delegation'
|
|
14
|
+
require 'drape/automatic_delegation'
|
|
15
|
+
require 'drape/finders'
|
|
16
|
+
require 'drape/decorator'
|
|
17
|
+
require 'drape/helper_proxy'
|
|
18
|
+
require 'drape/lazy_helpers'
|
|
19
|
+
require 'drape/decoratable'
|
|
20
|
+
require 'drape/factory'
|
|
21
|
+
require 'drape/decorated_association'
|
|
22
|
+
require 'drape/helper_support'
|
|
23
|
+
require 'drape/view_context'
|
|
24
|
+
require 'drape/collection_decorator'
|
|
25
|
+
require 'drape/undecorate'
|
|
26
|
+
require 'drape/decorates_assigned'
|
|
27
|
+
require 'drape/railtie' if defined?(Rails)
|
|
28
|
+
|
|
29
|
+
module Drape
|
|
30
|
+
def self.setup_action_controller(base)
|
|
31
|
+
base.class_eval do
|
|
32
|
+
include Drape::ViewContext
|
|
33
|
+
extend Drape::HelperSupport
|
|
34
|
+
extend Drape::DecoratesAssigned
|
|
35
|
+
|
|
36
|
+
before_action :activate_drape
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.setup_action_mailer(base)
|
|
41
|
+
base.class_eval do
|
|
42
|
+
include Drape::ViewContext
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.setup_orm(base)
|
|
47
|
+
base.class_eval do
|
|
48
|
+
include Drape::Decoratable
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class UninferrableDecoratorError < NameError
|
|
53
|
+
def initialize(klass)
|
|
54
|
+
super("Could not infer a decorator for #{klass}.")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class UninferrableSourceError < NameError
|
|
59
|
+
def initialize(klass)
|
|
60
|
+
super("Could not infer a source for #{klass}.")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Drape
|
|
2
|
+
module AutomaticDelegation
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
# Delegates missing instance methods to the source object.
|
|
6
|
+
def method_missing(method, *args, &block)
|
|
7
|
+
return super unless delegatable?(method)
|
|
8
|
+
|
|
9
|
+
self.class.delegate method
|
|
10
|
+
send(method, *args, &block)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Checks if the decorator responds to an instance method, or is able to
|
|
14
|
+
# proxy it to the source object.
|
|
15
|
+
def respond_to_missing?(method, include_private = false)
|
|
16
|
+
super || delegatable?(method)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @private
|
|
20
|
+
def delegatable?(method)
|
|
21
|
+
object.respond_to?(method)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module ClassMethods
|
|
25
|
+
# Proxies missing class methods to the source class.
|
|
26
|
+
def method_missing(method, *args, &block)
|
|
27
|
+
return super unless delegatable?(method)
|
|
28
|
+
|
|
29
|
+
object_class.send(method, *args, &block)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Checks if the decorator responds to a class method, or is able to proxy
|
|
33
|
+
# it to the source class.
|
|
34
|
+
def respond_to_missing?(method, include_private = false)
|
|
35
|
+
super || delegatable?(method)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @private
|
|
39
|
+
def delegatable?(method)
|
|
40
|
+
object_class? && object_class.respond_to?(method)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @private
|
|
44
|
+
# Avoids reloading the model class when ActiveSupport clears autoloaded
|
|
45
|
+
# dependencies in development mode.
|
|
46
|
+
def before_remove_const
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
included do
|
|
51
|
+
private :delegatable?
|
|
52
|
+
private_class_method :delegatable?
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module Drape
|
|
2
|
+
class CollectionDecorator
|
|
3
|
+
include Enumerable
|
|
4
|
+
include Drape::ViewHelpers
|
|
5
|
+
extend Drape::Delegation
|
|
6
|
+
|
|
7
|
+
# @return the collection being decorated.
|
|
8
|
+
attr_reader :object
|
|
9
|
+
|
|
10
|
+
# @return [Class] the decorator class used to decorate each item, as set by
|
|
11
|
+
# {#initialize}.
|
|
12
|
+
attr_reader :decorator_class
|
|
13
|
+
|
|
14
|
+
# @return [Hash] extra data to be used in user-defined methods, and passed
|
|
15
|
+
# to each item's decorator.
|
|
16
|
+
attr_accessor :context
|
|
17
|
+
|
|
18
|
+
array_methods = Array.instance_methods - Object.instance_methods
|
|
19
|
+
delegate :==, :as_json, *array_methods, to: :decorated_collection
|
|
20
|
+
|
|
21
|
+
# @param [Enumerable] object
|
|
22
|
+
# collection to decorate.
|
|
23
|
+
# @option options [Class, nil] :with (nil)
|
|
24
|
+
# the decorator class used to decorate each item. When `nil`, each item's
|
|
25
|
+
# {Decoratable#decorate decorate} method will be used.
|
|
26
|
+
# @option options [Hash] :context ({})
|
|
27
|
+
# extra data to be stored in the collection decorator and used in
|
|
28
|
+
# user-defined methods, and passed to each item's decorator.
|
|
29
|
+
def initialize(object, options = {})
|
|
30
|
+
options.assert_valid_keys(:with, :context)
|
|
31
|
+
@object = object
|
|
32
|
+
@decorator_class = options[:with]
|
|
33
|
+
@context = options.fetch(:context, {})
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
alias decorate new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [Array] the decorated items.
|
|
41
|
+
def decorated_collection
|
|
42
|
+
@decorated_collection ||= object.map { |item| decorate_item(item) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Delegated to the decorated collection when using the block form
|
|
46
|
+
# (`Enumerable#find`) or to the decorator class if not
|
|
47
|
+
# (`ActiveRecord::FinderMethods#find`)
|
|
48
|
+
def find(*args, &block)
|
|
49
|
+
if block_given?
|
|
50
|
+
decorated_collection.find(*args, &block)
|
|
51
|
+
else
|
|
52
|
+
ActiveSupport::Deprecation.warn('Using ActiveRecord\'s `find` on a CollectionDecorator is deprecated. ' +
|
|
53
|
+
'Call `find` on a model, and then decorate the result', caller)
|
|
54
|
+
|
|
55
|
+
decorate_item(object.find(*args))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_s
|
|
60
|
+
"#<#{self.class.name} of #{decorator_class || 'inferred decorators'} for #{object.inspect}>"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def context=(value)
|
|
64
|
+
@context = value
|
|
65
|
+
each { |item| item.context = value } if @decorated_collection
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @return [true]
|
|
69
|
+
def decorated?
|
|
70
|
+
true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
alias decorated_with? instance_of?
|
|
74
|
+
|
|
75
|
+
def kind_of?(klass)
|
|
76
|
+
decorated_collection.is_a?(klass) || super
|
|
77
|
+
end
|
|
78
|
+
alias is_a? kind_of?
|
|
79
|
+
|
|
80
|
+
def replace(other)
|
|
81
|
+
decorated_collection.replace(other)
|
|
82
|
+
self
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
protected
|
|
86
|
+
|
|
87
|
+
# Decorates the given item.
|
|
88
|
+
def decorate_item(item)
|
|
89
|
+
item_decorator.call(item, context: context)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def item_decorator
|
|
95
|
+
if decorator_class
|
|
96
|
+
decorator_class.method(:decorate)
|
|
97
|
+
else
|
|
98
|
+
->(item, options) { item.decorate(options) }
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require 'drape/decoratable/equality'
|
|
2
|
+
|
|
3
|
+
module Drape
|
|
4
|
+
# Provides shortcuts to decorate objects directly, so you can do
|
|
5
|
+
# `@product.decorate` instead of `ProductDecorator.new(@product)`.
|
|
6
|
+
#
|
|
7
|
+
# This module is included by default into `ActiveRecord::Base` and
|
|
8
|
+
# `Mongoid::Document`, but you're using another ORM, or want to decorate
|
|
9
|
+
# plain old Ruby objects, you can include it manually.
|
|
10
|
+
module Decoratable
|
|
11
|
+
extend ActiveSupport::Concern
|
|
12
|
+
include Drape::Decoratable::Equality
|
|
13
|
+
|
|
14
|
+
# Decorates the object using the inferred {#decorator_class}.
|
|
15
|
+
# @param [Hash] options
|
|
16
|
+
# see {Decorator#initialize}
|
|
17
|
+
def decorate(options = {})
|
|
18
|
+
decorator_class.decorate(self, options)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# (see ClassMethods#decorator_class)
|
|
22
|
+
def decorator_class
|
|
23
|
+
self.class.decorator_class
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def decorator_class?
|
|
27
|
+
self.class.decorator_class?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The list of decorators that have been applied to the object.
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<Class>] `[]`
|
|
33
|
+
def applied_decorators
|
|
34
|
+
[]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# (see Decorator#decorated_with?)
|
|
38
|
+
# @return [false]
|
|
39
|
+
def decorated_with?(_decorator_class)
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Checks if this object is decorated.
|
|
44
|
+
#
|
|
45
|
+
# @return [false]
|
|
46
|
+
def decorated?
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
module ClassMethods
|
|
51
|
+
# Decorates a collection of objects. Used at the end of a scope chain.
|
|
52
|
+
#
|
|
53
|
+
# @example
|
|
54
|
+
# Product.popular.decorate
|
|
55
|
+
# @param [Hash] options
|
|
56
|
+
# see {Decorator.decorate_collection}.
|
|
57
|
+
def decorate(options = {})
|
|
58
|
+
collection = Rails::VERSION::MAJOR >= 4 ? all : scoped
|
|
59
|
+
decorator_class.decorate_collection(collection, options.reverse_merge(with: nil))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def decorator_class?
|
|
63
|
+
decorator_class
|
|
64
|
+
rescue Drape::UninferrableDecoratorError
|
|
65
|
+
false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Infers the decorator class to be used by {Decoratable#decorate} (e.g.
|
|
69
|
+
# `Product` maps to `ProductDecorator`).
|
|
70
|
+
#
|
|
71
|
+
# @return [Class] the inferred decorator class.
|
|
72
|
+
def decorator_class
|
|
73
|
+
prefix = respond_to?(:model_name) ? model_name : name
|
|
74
|
+
decorator_name = "#{prefix}Decorator"
|
|
75
|
+
decorator_name.constantize
|
|
76
|
+
rescue NameError => error
|
|
77
|
+
if superclass.respond_to?(:decorator_class)
|
|
78
|
+
superclass.decorator_class
|
|
79
|
+
else
|
|
80
|
+
raise unless error.missing_name?(decorator_name)
|
|
81
|
+
raise Drape::UninferrableDecoratorError, self
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Compares with possibly-decorated objects.
|
|
86
|
+
#
|
|
87
|
+
# @return [Boolean]
|
|
88
|
+
def ===(other)
|
|
89
|
+
super || (other.respond_to?(:object) && super(other.object))
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Drape
|
|
2
|
+
module Decoratable
|
|
3
|
+
module Equality
|
|
4
|
+
# Compares self with a possibly-decorated object.
|
|
5
|
+
#
|
|
6
|
+
# @return [Boolean]
|
|
7
|
+
def ==(other)
|
|
8
|
+
super || Equality.test_for_decorator(self, other)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Compares an object to a possibly-decorated object.
|
|
12
|
+
#
|
|
13
|
+
# @return [Boolean]
|
|
14
|
+
def self.test(object, other)
|
|
15
|
+
return object == other if object.is_a?(Decoratable)
|
|
16
|
+
object == other || test_for_decorator(object, other)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @private
|
|
20
|
+
def self.test_for_decorator(object, other)
|
|
21
|
+
other.respond_to?(:decorated?) && other.decorated? &&
|
|
22
|
+
other.respond_to?(:object) && test(object, other.object)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|