draper 0.11.1 → 0.12.0
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/CHANGELOG.txt +23 -0
- data/Gemfile +4 -0
- data/Readme.markdown +32 -8
- data/draper.gemspec +1 -0
- data/lib/draper.rb +1 -0
- data/lib/draper/active_model_support.rb +24 -0
- data/lib/draper/base.rb +30 -22
- data/lib/draper/decorated_enumerable_proxy.rb +12 -9
- data/lib/draper/helper_support.rb +1 -1
- data/lib/draper/lazy_helpers.rb +1 -1
- data/lib/draper/model_support.rb +2 -4
- data/lib/draper/railtie.rb +31 -0
- data/lib/draper/rspec_integration.rb +22 -11
- data/lib/draper/system.rb +9 -0
- data/lib/draper/version.rb +1 -1
- data/lib/generators/decorator/decorator_generator.rb +28 -0
- data/lib/generators/{draper/decorator → decorator}/templates/decorator.rb +5 -3
- data/lib/generators/resource_override.rb +12 -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 +7 -0
- data/performance/active_record.rb +1 -1
- data/performance/models.rb +1 -1
- data/spec/draper/base_spec.rb +74 -15
- data/spec/draper/helper_support_spec.rb +4 -4
- data/spec/draper/model_support_spec.rb +1 -1
- data/spec/draper/view_context_spec.rb +2 -7
- data/spec/generators/decorator/decorator_generator_spec.rb +52 -0
- data/spec/spec_helper.rb +21 -17
- data/spec/support/samples/active_model.rb +9 -0
- data/spec/support/samples/active_record.rb +8 -0
- data/spec/support/samples/application_helper.rb +1 -1
- data/spec/support/samples/decorator.rb +1 -1
- data/spec/support/samples/decorator_with_allows.rb +1 -1
- data/spec/support/samples/non_active_model_product.rb +2 -0
- data/spec/support/samples/specific_product_decorator.rb +1 -1
- metadata +30 -18
- data/lib/generators/draper/decorator/USAGE +0 -7
- data/lib/generators/draper/decorator/decorator_generator.rb +0 -43
- data/lib/generators/draper/decorator/templates/decorator_spec.rb +0 -5
- data/lib/generators/draper/decorator/templates/decorator_test.rb +0 -12
- data/lib/generators/draper/install/install_generator.rb +0 -39
- data/lib/generators/draper/install/templates/application_decorator.rb +0 -28
- data/lib/generators/draper/install/templates/application_decorator_spec.rb +0 -4
- data/lib/generators/draper/install/templates/application_decorator_test.rb +0 -11
- data/lib/generators/rails/decorator_generator.rb +0 -15
- data/spec/generators/draper/decorator/decorator_generator_spec.rb +0 -102
- data/spec/generators/draper/install/install_generator_spec.rb +0 -46
data/CHANGELOG.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
= Draper Changelog
|
2
|
+
|
3
|
+
= Master
|
4
|
+
|
5
|
+
== 0.12.1
|
6
|
+
|
7
|
+
* Added Changelog
|
8
|
+
|
9
|
+
* Prevented double decoration, see #173
|
10
|
+
|
11
|
+
* ActiveModel::Errors support, 19496f0c
|
12
|
+
|
13
|
+
* Fixed autoloading issue, see #188
|
14
|
+
|
15
|
+
* Re-did generators, see 9155e58f
|
16
|
+
|
17
|
+
* Added capybara integration, see 57c8678e
|
18
|
+
|
19
|
+
* Fixed a few bugs with the DecoratedEnumerableProxy
|
20
|
+
|
21
|
+
== 0.11.1
|
22
|
+
|
23
|
+
* Fixed regression, we don't want to introduce a hard dependency on Rails. #107
|
data/Gemfile
CHANGED
data/Readme.markdown
CHANGED
@@ -6,22 +6,31 @@
|
|
6
6
|
## Quick Start
|
7
7
|
|
8
8
|
1. Add `gem 'draper'` to your `Gemfile` and `bundle`
|
9
|
-
2.
|
10
|
-
3.
|
9
|
+
2. When you generate a resource with `rails g resource YourModel`, you get a decorator automatically!
|
10
|
+
3. If YourModel exists, run `rails g decorator YourModel`
|
11
11
|
4. Edit `app/decorators/[your_model]_decorator.rb` using:
|
12
12
|
1. `h` to proxy to Rails/application helpers like `h.current_user`
|
13
13
|
2. `[your_model]` to access the wrapped object like `article.created_at`
|
14
|
-
5.
|
15
|
-
6. Wrap models in your controller with the decorator using:
|
14
|
+
5. Wrap models in your controller with the decorator using:
|
16
15
|
1. `.find` automatic lookup & wrap
|
17
16
|
ex: `ArticleDecorator.find(1)`
|
18
17
|
2. `.decorate` method with single object or collection,
|
19
18
|
ex: `ArticleDecorator.decorate(Article.all)`
|
20
19
|
3. `.new` method with single object
|
21
20
|
ex: `ArticleDecorator.new(Article.first)`
|
22
|
-
|
21
|
+
6. Output the instance methods in your view templates
|
23
22
|
ex: `@article_decorator.created_at`
|
24
23
|
|
24
|
+
If you need common methods in your decorators, create an `app/decorators/application_decorator.rb`:
|
25
|
+
|
26
|
+
``` ruby
|
27
|
+
class ApplicationDecorator < Draper::Base
|
28
|
+
# your methods go here
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
and make your decorators inherit from it. Newly generated decorators will respect this choice and inherit from `ApplicationDecorator`.
|
33
|
+
|
25
34
|
## Watch the RailsCast
|
26
35
|
|
27
36
|
Ryan Bates has put together an excellent RailsCast on Draper based on the 0.8.0 release:
|
@@ -146,12 +155,20 @@ gem "draper"
|
|
146
155
|
|
147
156
|
Then run `bundle` from the project directory.
|
148
157
|
|
158
|
+
### Run the draper:install command
|
159
|
+
|
160
|
+
This will create the `app/decorators` directory and the `ApplicationDecorator` inside it.
|
161
|
+
|
162
|
+
```
|
163
|
+
rails generate draper:install
|
164
|
+
```
|
165
|
+
|
149
166
|
### Generate the Decorator
|
150
167
|
|
151
168
|
To decorate a model named `Article`:
|
152
169
|
|
153
170
|
```
|
154
|
-
rails generate draper:decorator
|
171
|
+
rails generate draper:decorator article
|
155
172
|
```
|
156
173
|
|
157
174
|
### Writing Methods
|
@@ -239,7 +256,7 @@ ActionMailer class.
|
|
239
256
|
|
240
257
|
```ruby
|
241
258
|
class ArticleMailer < ActionMailer::Base
|
242
|
-
|
259
|
+
default 'init-draper' => Proc.new { set_current_view_context }
|
243
260
|
end
|
244
261
|
```
|
245
262
|
### Integration with RSpec
|
@@ -348,6 +365,14 @@ Now when you call the association it will use a decorator.
|
|
348
365
|
<%= @article.author.fancy_name %>
|
349
366
|
```
|
350
367
|
|
368
|
+
## Contributing
|
369
|
+
|
370
|
+
1. Fork it.
|
371
|
+
2. Create a branch (`git checkout -b my_awesome_branch`)
|
372
|
+
3. Commit your changes (`git commit -am "Added some magic"`)
|
373
|
+
4. Push to the branch (`git push origin my_awesome_branch`)
|
374
|
+
5. Send pull request
|
375
|
+
|
351
376
|
## Issues / Pending
|
352
377
|
|
353
378
|
* Documentation
|
@@ -355,7 +380,6 @@ Now when you call the association it will use a decorator.
|
|
355
380
|
* Add information about the `.decorator` method
|
356
381
|
* Make clear the pattern of overriding accessor methods of the wrapped model
|
357
382
|
* Build sample Rails application(s)
|
358
|
-
* Add a section about contributing
|
359
383
|
* Generators
|
360
384
|
* Implement hook so generating a controller/scaffold generates a decorator
|
361
385
|
* Add generators for...
|
data/draper.gemspec
CHANGED
data/lib/draper.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Draper::ActiveModelSupport
|
2
|
+
module Proxies
|
3
|
+
def create_proxies
|
4
|
+
# These methods (as keys) will be created only if the correspondent
|
5
|
+
# model descends from a specific class (as value)
|
6
|
+
proxies = {}
|
7
|
+
proxies[:to_param] = ActiveModel::Conversion if defined?(ActiveModel::Conversion)
|
8
|
+
proxies[:errors] = ActiveModel::Validations if defined?(ActiveModel::Validations)
|
9
|
+
proxies[:id] = ActiveRecord::Base if defined?(ActiveRecord::Base)
|
10
|
+
|
11
|
+
proxies.each do |method_name, dependency|
|
12
|
+
if model.kind_of?(dependency) || dependency.nil?
|
13
|
+
class << self
|
14
|
+
self
|
15
|
+
end.class_eval do
|
16
|
+
self.send(:define_method, method_name) do |*args, &block|
|
17
|
+
model.send(method_name, *args, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/draper/base.rb
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
module Draper
|
2
2
|
class Base
|
3
3
|
require 'active_support/core_ext/class/attribute'
|
4
|
+
require 'active_support/core_ext/array/extract_options'
|
5
|
+
|
4
6
|
class_attribute :denied, :allowed, :model_class
|
5
7
|
attr_accessor :model, :options
|
6
8
|
|
7
|
-
DEFAULT_DENIED = Object.
|
9
|
+
DEFAULT_DENIED = Object.instance_methods << :method_missing
|
8
10
|
DEFAULT_ALLOWED = []
|
9
|
-
FORCED_PROXY = [:to_param, :id]
|
10
|
-
FORCED_PROXY.each do |method|
|
11
|
-
define_method method do |*args, &block|
|
12
|
-
model.send method, *args, &block
|
13
|
-
end
|
14
|
-
end
|
15
11
|
self.denied = DEFAULT_DENIED
|
16
12
|
self.allowed = DEFAULT_ALLOWED
|
17
13
|
|
14
|
+
include Draper::ActiveModelSupport::Proxies
|
15
|
+
|
18
16
|
# Initialize a new decorator instance by passing in
|
19
17
|
# an instance of the source class. Pass in an optional
|
20
18
|
# context inside the options hash is stored for later use.
|
@@ -24,8 +22,9 @@ module Draper
|
|
24
22
|
def initialize(input, options = {})
|
25
23
|
input.inspect # forces evaluation of a lazy query from AR
|
26
24
|
self.class.model_class = input.class if model_class.nil?
|
27
|
-
@model = input
|
25
|
+
@model = input.kind_of?(Draper::Base) ? input.model : input
|
28
26
|
self.options = options
|
27
|
+
create_proxies
|
29
28
|
end
|
30
29
|
|
31
30
|
# Proxies to the class specified by `decorates` to automatically
|
@@ -60,7 +59,7 @@ module Draper
|
|
60
59
|
# the assocation to be decorated when it is retrieved.
|
61
60
|
#
|
62
61
|
# @param [Symbol] name of association to decorate, like `:products`
|
63
|
-
# @option
|
62
|
+
# @option options [Class] :with The decorator to decorate the association with
|
64
63
|
def self.decorates_association(association_symbol, options = {})
|
65
64
|
define_method(association_symbol) do
|
66
65
|
orig_association = model.send(association_symbol)
|
@@ -69,15 +68,16 @@ module Draper
|
|
69
68
|
|
70
69
|
return options[:with].decorate(orig_association) if options[:with]
|
71
70
|
|
72
|
-
if options[:polymorphic]
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
71
|
+
klass = if options[:polymorphic]
|
72
|
+
orig_association.class
|
73
|
+
elsif association_reflection = find_association_reflection(association_symbol)
|
74
|
+
association_reflection.klass
|
75
|
+
elsif orig_association.respond_to?(:first)
|
76
|
+
orig_association.first.class
|
77
|
+
else
|
78
|
+
orig_association.class
|
79
|
+
end
|
80
|
+
|
81
81
|
"#{klass}Decorator".constantize.decorate(orig_association, options)
|
82
82
|
end
|
83
83
|
end
|
@@ -129,6 +129,8 @@ module Draper
|
|
129
129
|
#
|
130
130
|
# @param [Object] instance(s) to wrap
|
131
131
|
# @param [Hash] options (optional)
|
132
|
+
# @option options [Boolean] :infer If true, each model will be
|
133
|
+
# wrapped by its inferred decorator.
|
132
134
|
def self.decorate(input, options = {})
|
133
135
|
if input.instance_of?(self)
|
134
136
|
input.options = options unless options.empty?
|
@@ -206,7 +208,7 @@ module Draper
|
|
206
208
|
alias :is_a? :kind_of?
|
207
209
|
|
208
210
|
def respond_to?(method, include_private = false)
|
209
|
-
super || (allow?(method) && model.respond_to?(method))
|
211
|
+
super || (allow?(method) && model.respond_to?(method, include_private))
|
210
212
|
end
|
211
213
|
|
212
214
|
def method_missing(method, *args, &block)
|
@@ -228,8 +230,8 @@ module Draper
|
|
228
230
|
end
|
229
231
|
|
230
232
|
def self.method_missing(method, *args, &block)
|
231
|
-
if method.to_s.match(/^
|
232
|
-
self.decorate(model_class.send(method, *args, &block))
|
233
|
+
if method.to_s.match(/^find_((all_|last_)?by_|or_(initialize|create)_by_).*/)
|
234
|
+
self.decorate(model_class.send(method, *args, &block), :context => args.dup.extract_options!)
|
233
235
|
else
|
234
236
|
model_class.send(method, *args, &block)
|
235
237
|
end
|
@@ -255,7 +257,13 @@ module Draper
|
|
255
257
|
private
|
256
258
|
|
257
259
|
def allow?(method)
|
258
|
-
(allowed.empty? || allowed.include?(method)
|
260
|
+
(allowed.empty? || allowed.include?(method)) && !denied.include?(method)
|
261
|
+
end
|
262
|
+
|
263
|
+
def find_association_reflection(association)
|
264
|
+
if model.class.respond_to?(:reflect_on_association)
|
265
|
+
model.class.reflect_on_association(association)
|
266
|
+
end
|
259
267
|
end
|
260
268
|
end
|
261
269
|
end
|
@@ -2,28 +2,27 @@ module Draper
|
|
2
2
|
class DecoratedEnumerableProxy
|
3
3
|
include Enumerable
|
4
4
|
|
5
|
+
delegate :as_json, :collect, :map, :each, :[], :all?, :include?, :first, :last, :shift, :to => :decorated_collection
|
6
|
+
|
5
7
|
def initialize(collection, klass, options = {})
|
6
8
|
@wrapped_collection, @klass, @options = collection, klass, options
|
7
9
|
end
|
8
10
|
|
9
|
-
def
|
10
|
-
@wrapped_collection.
|
11
|
-
end
|
12
|
-
|
13
|
-
def to_ary
|
14
|
-
@wrapped_collection.map { |member| @klass.decorate(member, @options) }
|
11
|
+
def decorated_collection
|
12
|
+
@decorated_collection ||= @wrapped_collection.collect { |member| @klass.decorate(member, @options) }
|
15
13
|
end
|
14
|
+
alias_method :to_ary, :decorated_collection
|
16
15
|
|
17
16
|
def method_missing (method, *args, &block)
|
18
17
|
@wrapped_collection.send(method, *args, &block)
|
19
18
|
end
|
20
19
|
|
21
|
-
def respond_to?(method)
|
22
|
-
super || @wrapped_collection.respond_to?(method)
|
20
|
+
def respond_to?(method, include_private = false)
|
21
|
+
super || @wrapped_collection.respond_to?(method, include_private)
|
23
22
|
end
|
24
23
|
|
25
24
|
def kind_of?(klass)
|
26
|
-
|
25
|
+
@wrapped_collection.kind_of?(klass) || super
|
27
26
|
end
|
28
27
|
alias :is_a? :kind_of?
|
29
28
|
|
@@ -38,6 +37,10 @@ module Draper
|
|
38
37
|
def to_s
|
39
38
|
"#<DecoratedEnumerableProxy of #{@klass} for #{@wrapped_collection.inspect}>"
|
40
39
|
end
|
40
|
+
|
41
|
+
def context=(input)
|
42
|
+
self.map { |member| member.context = input }
|
43
|
+
end
|
41
44
|
|
42
45
|
def source
|
43
46
|
@wrapped_collection
|
data/lib/draper/lazy_helpers.rb
CHANGED
data/lib/draper/model_support.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module Draper::ModelSupport
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
2
4
|
def decorator(options = {})
|
3
5
|
@decorator ||= "#{self.class.name}Decorator".constantize.decorate(self, options.merge(:infer => false))
|
4
6
|
block_given? ? yield(@decorator) : @decorator
|
@@ -12,8 +14,4 @@ module Draper::ModelSupport
|
|
12
14
|
block_given? ? yield(decorator_proxy) : decorator_proxy
|
13
15
|
end
|
14
16
|
end
|
15
|
-
|
16
|
-
def self.included(base)
|
17
|
-
base.extend(ClassMethods)
|
18
|
-
end
|
19
17
|
end
|
data/lib/draper/railtie.rb
CHANGED
@@ -1,8 +1,39 @@
|
|
1
1
|
require 'rails/railtie'
|
2
2
|
|
3
|
+
module ActiveModel
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
generators do |app|
|
6
|
+
Rails::Generators.configure!(app.config.generators)
|
7
|
+
require "generators/resource_override"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
3
12
|
module Draper
|
4
13
|
class Railtie < Rails::Railtie
|
5
14
|
|
15
|
+
##
|
16
|
+
# Decorators are loaded in
|
17
|
+
# => at app boot in non-development environments
|
18
|
+
# => after each request in the development environment
|
19
|
+
#
|
20
|
+
# This is necessary because we might never explicitly reference
|
21
|
+
# Decorator constants.
|
22
|
+
#
|
23
|
+
config.to_prepare do
|
24
|
+
::Draper::System.load_app_local_decorators
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# The `app/decorators` path is eager loaded
|
29
|
+
#
|
30
|
+
# This is the standard "Rails Way" to add paths from which constants
|
31
|
+
# can be loaded.
|
32
|
+
#
|
33
|
+
config.before_initialize do |app|
|
34
|
+
app.config.paths.add 'app/decorators', :eager_load => true
|
35
|
+
end
|
36
|
+
|
6
37
|
initializer "draper.extend_action_controller_base" do |app|
|
7
38
|
ActiveSupport.on_load(:action_controller) do
|
8
39
|
Draper::System.setup(:action_controller)
|
@@ -3,20 +3,31 @@ module Draper
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
included { metadata[:type] = :decorator }
|
5
5
|
end
|
6
|
+
end
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
RSpec.configure do |config|
|
9
|
+
# Automatically tag specs in specs/decorators as type: :decorator
|
10
|
+
config.include Draper::DecoratorExampleGroup, :type => :decorator, :example_group => {
|
11
|
+
:file_path => /spec[\\\/]decorators/
|
12
|
+
}
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
# Specs tagged type: :decorator set the Draper view context
|
15
|
+
config.around do |example|
|
16
|
+
if :decorator == example.metadata[:type]
|
17
|
+
ApplicationController.new.set_current_view_context
|
18
|
+
Draper::ViewContext.current.controller.request ||= ActionController::TestRequest.new
|
19
|
+
Draper::ViewContext.current.request ||= Draper::ViewContext.current.controller.request
|
20
|
+
Draper::ViewContext.current.params ||= {}
|
19
21
|
end
|
22
|
+
example.call
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if defined?(Capybara)
|
27
|
+
require 'capybara/rspec/matchers'
|
28
|
+
|
29
|
+
RSpec.configure do |config|
|
30
|
+
config.include Capybara::RSpecMatchers, :type => :decorator
|
20
31
|
end
|
21
32
|
end
|
22
33
|
|
data/lib/draper/system.rb
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
module Draper
|
2
2
|
class System
|
3
|
+
def self.app_local_decorator_glob
|
4
|
+
'app/decorators/**/*_decorator.rb'
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.load_app_local_decorators
|
8
|
+
decorator_files = Dir[ "#{ Rails.root }/#{ app_local_decorator_glob }" ]
|
9
|
+
decorator_files.each { |d| require_dependency d }
|
10
|
+
end
|
11
|
+
|
3
12
|
def self.setup(component)
|
4
13
|
if component == :action_controller
|
5
14
|
ActionController::Base.send(:include, Draper::ViewContextFilter)
|