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.
Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +27 -0
  3. data/.gitignore +18 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1159 -0
  6. data/.travis.yml +14 -0
  7. data/.yardopts +1 -0
  8. data/CHANGELOG.md +230 -0
  9. data/CONTRIBUTING.md +20 -0
  10. data/Gemfile +16 -0
  11. data/Guardfile +26 -0
  12. data/LICENSE +7 -0
  13. data/README.md +592 -0
  14. data/Rakefile +76 -0
  15. data/drape.gemspec +31 -0
  16. data/gemfiles/5.0.gemfile +8 -0
  17. data/lib/drape.rb +63 -0
  18. data/lib/drape/automatic_delegation.rb +55 -0
  19. data/lib/drape/collection_decorator.rb +102 -0
  20. data/lib/drape/decoratable.rb +93 -0
  21. data/lib/drape/decoratable/equality.rb +26 -0
  22. data/lib/drape/decorated_association.rb +33 -0
  23. data/lib/drape/decorates_assigned.rb +44 -0
  24. data/lib/drape/decorator.rb +282 -0
  25. data/lib/drape/delegation.rb +13 -0
  26. data/lib/drape/factory.rb +91 -0
  27. data/lib/drape/finders.rb +36 -0
  28. data/lib/drape/helper_proxy.rb +43 -0
  29. data/lib/drape/helper_support.rb +5 -0
  30. data/lib/drape/lazy_helpers.rb +13 -0
  31. data/lib/drape/railtie.rb +69 -0
  32. data/lib/drape/tasks/test.rake +9 -0
  33. data/lib/drape/test/devise_helper.rb +30 -0
  34. data/lib/drape/test/minitest_integration.rb +6 -0
  35. data/lib/drape/test/rspec_integration.rb +20 -0
  36. data/lib/drape/test_case.rb +42 -0
  37. data/lib/drape/undecorate.rb +9 -0
  38. data/lib/drape/version.rb +3 -0
  39. data/lib/drape/view_context.rb +104 -0
  40. data/lib/drape/view_context/build_strategy.rb +46 -0
  41. data/lib/drape/view_helpers.rb +34 -0
  42. data/lib/generators/controller_override.rb +17 -0
  43. data/lib/generators/mini_test/decorator_generator.rb +20 -0
  44. data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
  45. data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
  46. data/lib/generators/rails/decorator_generator.rb +36 -0
  47. data/lib/generators/rails/templates/decorator.rb +19 -0
  48. data/lib/generators/rspec/decorator_generator.rb +9 -0
  49. data/lib/generators/rspec/templates/decorator_spec.rb +4 -0
  50. data/lib/generators/test_unit/decorator_generator.rb +9 -0
  51. data/lib/generators/test_unit/templates/decorator_test.rb +4 -0
  52. data/spec/draper/collection_decorator_spec.rb +305 -0
  53. data/spec/draper/decoratable/equality_spec.rb +10 -0
  54. data/spec/draper/decoratable_spec.rb +203 -0
  55. data/spec/draper/decorated_association_spec.rb +85 -0
  56. data/spec/draper/decorates_assigned_spec.rb +70 -0
  57. data/spec/draper/decorator_spec.rb +828 -0
  58. data/spec/draper/factory_spec.rb +250 -0
  59. data/spec/draper/finders_spec.rb +165 -0
  60. data/spec/draper/helper_proxy_spec.rb +61 -0
  61. data/spec/draper/lazy_helpers_spec.rb +21 -0
  62. data/spec/draper/undecorate_spec.rb +19 -0
  63. data/spec/draper/view_context/build_strategy_spec.rb +116 -0
  64. data/spec/draper/view_context_spec.rb +160 -0
  65. data/spec/draper/view_helpers_spec.rb +8 -0
  66. data/spec/dummy/.gitignore +21 -0
  67. data/spec/dummy/Rakefile +6 -0
  68. data/spec/dummy/app/assets/config/manifest.js +3 -0
  69. data/spec/dummy/app/assets/images/.keep +0 -0
  70. data/spec/dummy/app/assets/javascripts/application.js +16 -0
  71. data/spec/dummy/app/assets/javascripts/cable.js +13 -0
  72. data/spec/dummy/app/assets/javascripts/channels/.keep +0 -0
  73. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  74. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  75. data/spec/dummy/app/channels/application_cable/connection.rb +5 -0
  76. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  77. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  78. data/spec/dummy/app/controllers/posts_controller.rb +21 -0
  79. data/spec/dummy/app/decorators/mongoid_post_decorator.rb +4 -0
  80. data/spec/dummy/app/decorators/post_decorator.rb +59 -0
  81. data/spec/dummy/app/helpers/application_helper.rb +5 -0
  82. data/spec/dummy/app/jobs/application_job.rb +2 -0
  83. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  84. data/spec/dummy/app/mailers/post_mailer.rb +18 -0
  85. data/spec/dummy/app/models/admin.rb +5 -0
  86. data/spec/dummy/app/models/application_record.rb +3 -0
  87. data/spec/dummy/app/models/concerns/.keep +0 -0
  88. data/spec/dummy/app/models/mongoid_post.rb +5 -0
  89. data/spec/dummy/app/models/post.rb +3 -0
  90. data/spec/dummy/app/models/user.rb +5 -0
  91. data/spec/dummy/app/views/layouts/application.html.erb +9 -0
  92. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  93. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  94. data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +31 -0
  95. data/spec/dummy/app/views/posts/_post.html.erb +40 -0
  96. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  97. data/spec/dummy/bin/bundle +3 -0
  98. data/spec/dummy/bin/rails +9 -0
  99. data/spec/dummy/bin/rake +9 -0
  100. data/spec/dummy/bin/setup +34 -0
  101. data/spec/dummy/bin/spring +15 -0
  102. data/spec/dummy/bin/update +29 -0
  103. data/spec/dummy/config.ru +5 -0
  104. data/spec/dummy/config/application.rb +21 -0
  105. data/spec/dummy/config/boot.rb +2 -0
  106. data/spec/dummy/config/cable.yml +10 -0
  107. data/spec/dummy/config/database.yml +25 -0
  108. data/spec/dummy/config/environment.rb +5 -0
  109. data/spec/dummy/config/environments/development.rb +51 -0
  110. data/spec/dummy/config/environments/production.rb +91 -0
  111. data/spec/dummy/config/environments/test.rb +42 -0
  112. data/spec/dummy/config/initializers/active_record_belongs_to_required_by_default.rb +6 -0
  113. data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -0
  114. data/spec/dummy/config/initializers/assets.rb +11 -0
  115. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  116. data/spec/dummy/config/initializers/callback_terminator.rb +6 -0
  117. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  118. data/spec/dummy/config/initializers/devise.rb +3 -0
  119. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  120. data/spec/dummy/config/initializers/inflections.rb +16 -0
  121. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  122. data/spec/dummy/config/initializers/per_form_csrf_tokens.rb +4 -0
  123. data/spec/dummy/config/initializers/request_forgery_protection.rb +4 -0
  124. data/spec/dummy/config/initializers/session_store.rb +3 -0
  125. data/spec/dummy/config/initializers/ssl_options.rb +4 -0
  126. data/spec/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  127. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  128. data/spec/dummy/config/locales/en.yml +23 -0
  129. data/spec/dummy/config/mongoid.yml +16 -0
  130. data/spec/dummy/config/puma.rb +47 -0
  131. data/spec/dummy/config/routes.rb +8 -0
  132. data/spec/dummy/config/secrets.yml +22 -0
  133. data/spec/dummy/config/spring.rb +6 -0
  134. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +7 -0
  135. data/spec/dummy/db/schema.rb +19 -0
  136. data/spec/dummy/db/seeds.rb +2 -0
  137. data/spec/dummy/lib/assets/.keep +0 -0
  138. data/spec/dummy/lib/tasks/.keep +0 -0
  139. data/spec/dummy/lib/tasks/test.rake +16 -0
  140. data/spec/dummy/log/.keep +0 -0
  141. data/spec/dummy/public/404.html +67 -0
  142. data/spec/dummy/public/422.html +67 -0
  143. data/spec/dummy/public/500.html +66 -0
  144. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  145. data/spec/dummy/public/apple-touch-icon.png +0 -0
  146. data/spec/dummy/public/favicon.ico +0 -0
  147. data/spec/dummy/public/robots.txt +5 -0
  148. data/spec/dummy/spec/decorators/active_model_serializers_spec.rb +12 -0
  149. data/spec/dummy/spec/decorators/devise_spec.rb +64 -0
  150. data/spec/dummy/spec/decorators/helpers_spec.rb +21 -0
  151. data/spec/dummy/spec/decorators/post_decorator_spec.rb +66 -0
  152. data/spec/dummy/spec/decorators/spec_type_spec.rb +7 -0
  153. data/spec/dummy/spec/decorators/view_context_spec.rb +22 -0
  154. data/spec/dummy/spec/mailers/post_mailer_spec.rb +33 -0
  155. data/spec/dummy/spec/models/mongoid_post_spec.rb +8 -0
  156. data/spec/dummy/spec/models/post_spec.rb +6 -0
  157. data/spec/dummy/spec/shared_examples/decoratable.rb +26 -0
  158. data/spec/dummy/spec/spec_helper.rb +8 -0
  159. data/spec/dummy/test/controllers/.keep +0 -0
  160. data/spec/dummy/test/fixtures/.keep +0 -0
  161. data/spec/dummy/test/fixtures/files/.keep +0 -0
  162. data/spec/dummy/test/helpers/.keep +0 -0
  163. data/spec/dummy/test/integration/.keep +0 -0
  164. data/spec/dummy/test/mailers/.keep +0 -0
  165. data/spec/dummy/test/models/.keep +0 -0
  166. data/spec/dummy/test/test_helper.rb +10 -0
  167. data/spec/dummy/vendor/assets/javascripts/.keep +0 -0
  168. data/spec/dummy/vendor/assets/stylesheets/.keep +0 -0
  169. data/spec/generators/controller/controller_generator_spec.rb +24 -0
  170. data/spec/generators/decorator/decorator_generator_spec.rb +94 -0
  171. data/spec/integration/integration_spec.rb +68 -0
  172. data/spec/performance/active_record.rb +4 -0
  173. data/spec/performance/benchmark.rb +57 -0
  174. data/spec/performance/decorators.rb +41 -0
  175. data/spec/performance/models.rb +20 -0
  176. data/spec/spec_helper.rb +41 -0
  177. data/spec/support/dummy_app.rb +88 -0
  178. data/spec/support/matchers/have_text.rb +50 -0
  179. data/spec/support/shared_examples/decoratable_equality.rb +40 -0
  180. data/spec/support/shared_examples/view_helpers.rb +39 -0
  181. metadata +497 -0
@@ -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
@@ -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
@@ -0,0 +1,8 @@
1
+ gem "rails", ">= 5.0.rc1"
2
+ gem "mongoid", github: "mongodb/mongoid"
3
+ gem "devise", github: "plataformatec/devise"
4
+ gem "minitest-rails", github: "blowmage/minitest-rails", branch: "rails5"
5
+
6
+ group :development, :test do
7
+ gem 'listen'
8
+ end
@@ -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