draper 0.18.0 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +5 -0
  3. data/CHANGELOG.markdown +8 -0
  4. data/Gemfile +4 -0
  5. data/Rakefile +50 -31
  6. data/Readme.markdown +42 -48
  7. data/draper.gemspec +2 -1
  8. data/lib/draper.rb +42 -7
  9. data/lib/draper/collection_decorator.rb +88 -0
  10. data/lib/draper/decoratable.rb +44 -0
  11. data/lib/draper/decorated_association.rb +55 -0
  12. data/lib/draper/decorator.rb +208 -0
  13. data/lib/draper/finders.rb +44 -0
  14. data/lib/draper/helper_proxy.rb +16 -0
  15. data/lib/draper/railtie.rb +17 -21
  16. data/lib/draper/security.rb +48 -0
  17. data/lib/draper/tasks/tu.rake +5 -0
  18. data/lib/draper/test/minitest_integration.rb +1 -1
  19. data/lib/draper/test/test_unit_integration.rb +9 -0
  20. data/lib/draper/version.rb +1 -1
  21. data/lib/draper/view_context.rb +6 -13
  22. data/lib/draper/view_helpers.rb +36 -0
  23. data/lib/generators/decorator/decorator_generator.rb +1 -1
  24. data/lib/generators/decorator/templates/decorator.rb +0 -1
  25. data/spec/draper/collection_decorator_spec.rb +240 -0
  26. data/spec/draper/decoratable_spec.rb +164 -0
  27. data/spec/draper/decorated_association_spec.rb +130 -0
  28. data/spec/draper/decorator_spec.rb +497 -0
  29. data/spec/draper/finders_spec.rb +156 -0
  30. data/spec/draper/helper_proxy_spec.rb +12 -0
  31. data/spec/draper/security_spec.rb +158 -0
  32. data/spec/draper/view_helpers_spec.rb +41 -0
  33. data/spec/dummy/.rspec +2 -0
  34. data/spec/dummy/README.rdoc +261 -0
  35. data/spec/dummy/Rakefile +7 -0
  36. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  37. data/spec/dummy/app/controllers/localized_urls.rb +5 -0
  38. data/spec/dummy/app/controllers/posts_controller.rb +17 -0
  39. data/spec/dummy/app/decorators/post_decorator.rb +25 -0
  40. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  41. data/spec/dummy/app/mailers/application_mailer.rb +3 -0
  42. data/spec/dummy/app/mailers/post_mailer.rb +9 -0
  43. data/spec/dummy/app/models/post.rb +3 -0
  44. data/spec/dummy/app/views/layouts/application.html.erb +11 -0
  45. data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +1 -0
  46. data/spec/dummy/app/views/posts/_post.html.erb +19 -0
  47. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  48. data/spec/dummy/config.ru +4 -0
  49. data/spec/dummy/config/application.rb +64 -0
  50. data/spec/dummy/config/boot.rb +10 -0
  51. data/spec/dummy/config/database.yml +25 -0
  52. data/spec/dummy/config/environment.rb +5 -0
  53. data/spec/dummy/config/environments/development.rb +34 -0
  54. data/spec/dummy/config/environments/production.rb +55 -0
  55. data/spec/dummy/config/environments/test.rb +32 -0
  56. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  57. data/spec/dummy/config/initializers/inflections.rb +15 -0
  58. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  59. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  60. data/spec/dummy/config/initializers/session_store.rb +8 -0
  61. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  62. data/spec/dummy/config/locales/en.yml +5 -0
  63. data/spec/dummy/config/routes.rb +7 -0
  64. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
  65. data/spec/dummy/db/schema.rb +21 -0
  66. data/spec/dummy/db/seeds.rb +2 -0
  67. data/spec/dummy/lib/tasks/spec.rake +5 -0
  68. data/spec/dummy/public/404.html +26 -0
  69. data/spec/dummy/public/422.html +26 -0
  70. data/spec/dummy/public/500.html +25 -0
  71. data/spec/dummy/public/favicon.ico +0 -0
  72. data/spec/dummy/script/rails +6 -0
  73. data/spec/dummy/spec/decorators/post_decorator_spec.rb +23 -0
  74. data/spec/dummy/spec/mailers/post_mailer_spec.rb +29 -0
  75. data/spec/dummy/spec/spec_helper.rb +9 -0
  76. data/spec/generators/decorator/decorator_generator_spec.rb +3 -4
  77. data/spec/integration/integration_spec.rb +33 -0
  78. data/{performance → spec/performance}/active_record.rb +0 -0
  79. data/{performance/bechmark.rb → spec/performance/benchmark.rb} +0 -0
  80. data/{performance → spec/performance}/decorators.rb +2 -4
  81. data/{performance → spec/performance}/models.rb +0 -0
  82. data/spec/spec_helper.rb +20 -41
  83. data/spec/support/action_controller.rb +12 -0
  84. data/spec/support/active_model.rb +7 -0
  85. data/spec/support/{samples/active_record.rb → active_record.rb} +5 -0
  86. data/spec/support/{samples → decorators}/decorator_with_application_helper.rb +1 -1
  87. data/spec/support/decorators/namespaced_product_decorator.rb +5 -0
  88. data/spec/support/decorators/non_active_model_product_decorator.rb +2 -0
  89. data/spec/support/decorators/product_decorator.rb +19 -0
  90. data/spec/support/{samples → decorators}/products_decorator.rb +1 -1
  91. data/spec/support/decorators/some_thing_decorator.rb +2 -0
  92. data/spec/support/{samples → decorators}/specific_product_decorator.rb +0 -2
  93. data/spec/support/{samples → decorators}/widget_decorator.rb +0 -0
  94. data/spec/support/dummy_app.rb +84 -0
  95. data/spec/support/matchers/have_text.rb +50 -0
  96. data/spec/support/{samples → models}/namespaced_product.rb +1 -3
  97. data/spec/support/{samples → models}/non_active_model_product.rb +1 -0
  98. data/spec/support/{samples → models}/product.rb +13 -2
  99. data/spec/support/models/some_thing.rb +5 -0
  100. data/spec/support/models/uninferrable_decorator_model.rb +3 -0
  101. data/spec/support/{samples → models}/widget.rb +0 -0
  102. metadata +185 -68
  103. data/lib/draper/active_model_support.rb +0 -27
  104. data/lib/draper/base.rb +0 -312
  105. data/lib/draper/decorated_enumerable_proxy.rb +0 -90
  106. data/lib/draper/model_support.rb +0 -25
  107. data/lib/draper/rspec_integration.rb +0 -2
  108. data/lib/draper/system.rb +0 -18
  109. data/spec/draper/base_spec.rb +0 -873
  110. data/spec/draper/decorated_enumerable_proxy_spec.rb +0 -45
  111. data/spec/draper/model_support_spec.rb +0 -48
  112. data/spec/support/samples/decorator.rb +0 -5
  113. data/spec/support/samples/decorator_with_allows.rb +0 -3
  114. data/spec/support/samples/decorator_with_denies.rb +0 -3
  115. data/spec/support/samples/decorator_with_denies_all.rb +0 -3
  116. data/spec/support/samples/decorator_with_multiple_allows.rb +0 -4
  117. data/spec/support/samples/decorator_with_special_methods.rb +0 -13
  118. data/spec/support/samples/enumerable_proxy.rb +0 -3
  119. data/spec/support/samples/namespaced_product_decorator.rb +0 -7
  120. data/spec/support/samples/product_decorator.rb +0 -7
  121. data/spec/support/samples/sequel_product.rb +0 -13
  122. data/spec/support/samples/some_thing.rb +0 -2
  123. data/spec/support/samples/some_thing_decorator.rb +0 -3
@@ -1,27 +0,0 @@
1
- module Draper::ActiveModelSupport
2
- module Proxies
3
- def self.extended(base)
4
- # These methods (as keys) will be created only if the correspondent
5
- # model responds to the method
6
- proxies = [:to_param, :errors, :id]
7
-
8
- proxies.each do |method_name|
9
- if base.model.respond_to?(method_name)
10
- base.singleton_class.class_eval do
11
- if !base.class.instance_methods.include?(method_name) || base.class.instance_method(method_name).owner === Draper::Base
12
- define_method(method_name) do |*args, &block|
13
- model.send(method_name, *args, &block)
14
- end
15
- end
16
- end
17
- end
18
- end
19
-
20
- base.class_eval do
21
- def to_model
22
- self
23
- end
24
- end
25
- end
26
- end
27
- end
data/lib/draper/base.rb DELETED
@@ -1,312 +0,0 @@
1
- module Draper
2
- class Base
3
- require 'active_support/core_ext/class/attribute'
4
- require 'active_support/core_ext/array/extract_options'
5
-
6
- class_attribute :denied, :allowed, :model_class
7
- attr_accessor :model, :options
8
-
9
- DEFAULT_DENIED = Object.instance_methods << :method_missing
10
- DEFAULT_ALLOWED = []
11
- self.denied = DEFAULT_DENIED
12
- self.allowed = DEFAULT_ALLOWED
13
-
14
- # Initialize a new decorator instance by passing in
15
- # an instance of the source class. Pass in an optional
16
- # context inside the options hash is stored for later use.
17
- #
18
- # @param [Object] instance to wrap
19
- # @param [Hash] options (optional)
20
- def initialize(input, options = {})
21
- input.to_a if input.respond_to?(:to_a) # forces evaluation of a lazy query from AR
22
- self.class.model_class = input.class if model_class.nil?
23
- @model = input.kind_of?(Draper::Base) ? input.model : input
24
- self.options = options
25
- self.extend Draper::ActiveModelSupport::Proxies
26
- end
27
-
28
- # Proxies to the class specified by `decorates` to automatically
29
- # lookup an object in the database and decorate it.
30
- #
31
- # @param [Symbol or String] id to lookup
32
- # @return [Object] instance of this decorator class
33
- def self.find(input, options = {})
34
- self.new(model_class.find(input), options)
35
- end
36
-
37
- # Typically called within a decorator definition, this method
38
- # specifies the name of the wrapped object class.
39
- #
40
- # For instance, a `ProductDecorator` class might call `decorates :product`
41
- #
42
- # But they don't have to match in name, so a `EmployeeDecorator`
43
- # class could call `decorates :person` to wrap instances of `Person`
44
- #
45
- # This is primarilly set so the `.find` method knows which class
46
- # to query.
47
- #
48
- # @param [Symbol] class_name snakecase name of the decorated class, like `:product`
49
- def self.decorates(input, options = {})
50
- self.model_class = options[:class] || options[:class_name] || input.to_s.camelize
51
- self.model_class = model_class.constantize if model_class.respond_to?(:constantize)
52
- model_class.send :include, Draper::ModelSupport
53
- define_method(input){ @model }
54
- end
55
-
56
- # Typically called within a decorator definition, this method causes
57
- # the assocation to be decorated when it is retrieved.
58
- #
59
- # @param [Symbol] name of association to decorate, like `:products`
60
- # @option options [Hash] :with The decorator to decorate the association with
61
- # :scope The scope to apply to the association
62
- def self.decorates_association(association_symbol, options = {})
63
- define_method(association_symbol) do
64
- orig_association = model.send(association_symbol)
65
-
66
- return orig_association if orig_association.nil?
67
- return decorated_associations[association_symbol] if decorated_associations[association_symbol]
68
-
69
- orig_association = orig_association.send(options[:scope]) if options[:scope]
70
-
71
- return options[:with].decorate(orig_association) if options[:with]
72
-
73
- klass = if options[:polymorphic]
74
- orig_association.class
75
- elsif association_reflection = find_association_reflection(association_symbol)
76
- association_reflection.klass
77
- elsif orig_association.respond_to?(:first)
78
- orig_association.first.class
79
- else
80
- orig_association.class
81
- end
82
-
83
- decorated_associations[association_symbol] = "#{klass}Decorator".constantize.decorate(orig_association, options)
84
- end
85
- end
86
-
87
- # A convenience method for decorating multiple associations. Calls
88
- # decorates_association on each of the given symbols.
89
- #
90
- # @param [Symbols*] name of associations to decorate
91
- def self.decorates_associations(*association_symbols)
92
- options = association_symbols.extract_options!
93
- association_symbols.each{ |sym| decorates_association(sym, options) }
94
- end
95
-
96
- # Specifies a black list of methods which may *not* be proxied to
97
- # to the wrapped object.
98
- #
99
- # Do not use both `.allows` and `.denies` together, either write
100
- # a whitelist with `.allows` or a blacklist with `.denies`
101
- #
102
- # @param [Symbols*] methods to deny like `:find, :find_by_name`
103
- def self.denies(*input_denied)
104
- raise ArgumentError, "Specify at least one method (as a symbol) to exclude when using denies" if input_denied.empty?
105
- raise ArgumentError, "Use either 'allows' or 'denies', but not both." unless (self.allowed == DEFAULT_ALLOWED)
106
- self.denied += input_denied
107
- end
108
-
109
- # Specifies that all methods may *not* be proxied to
110
- # to the wrapped object.
111
- #
112
- # Do not use `.allows` and `.denies` in combination with '.denies_all'
113
- def self.denies_all
114
- raise ArgumentError, "Use either 'allows' or 'denies', but not both." unless ((self.allowed == DEFAULT_ALLOWED && self.denied == DEFAULT_DENIED) || (self.allowed != DEFAULT_ALLOWED && self.denied != DEFAULT_DENIED))
115
- self.denied += [nil] # Add dummy value to denied to prevent calls to #allows. Hacky???
116
- self.allowed += [nil] # Add dummy value to allowed to prevent calls to #denies
117
- end
118
-
119
- # Specifies a white list of methods which *may* be proxied to
120
- # to the wrapped object. When `allows` is used, only the listed
121
- # methods and methods defined in the decorator itself will be
122
- # available.
123
- #
124
- # Do not use both `.allows` and `.denies` together, either write
125
- # a whitelist with `.allows` or a blacklist with `.denies`
126
- #
127
- # @param [Symbols*] methods to allow like `:find, :find_by_name`
128
- def self.allows(*input_allows)
129
- raise ArgumentError, "Specify at least one method (as a symbol) to allow when using allows" if input_allows.empty?
130
- raise ArgumentError, "Use either 'allows' or 'denies', but not both." unless (self.denied == DEFAULT_DENIED)
131
- self.allowed += input_allows
132
- end
133
-
134
- # Initialize a new decorator instance by passing in
135
- # an instance of the source class. Pass in an optional
136
- # context into the options hash is stored for later use.
137
- #
138
- # When passing in a single object, using `.decorate` is
139
- # identical to calling `.new`. However, `.decorate` can
140
- # also accept a collection and return a collection of
141
- # individually decorated objects.
142
- #
143
- # @param [Object] instance(s) to wrap
144
- # @param [Hash] options (optional)
145
- # @option options [Boolean] :infer If true, each model will be
146
- # wrapped by its inferred decorator.
147
- def self.decorate(input, options = {})
148
- if input.instance_of?(self)
149
- input.options = options unless options.empty?
150
- return input
151
- elsif input.respond_to?(:each) && !input.is_a?(Struct) && (!defined?(Sequel) || !input.is_a?(Sequel::Model))
152
- Draper::DecoratedEnumerableProxy.new(input, self, options)
153
- elsif options[:infer]
154
- input.decorator(options)
155
- else
156
- new(input, options)
157
- end
158
- end
159
-
160
- # Fetch all instances of the decorated class and decorate them.
161
- #
162
- # @param [Hash] options (optional)
163
- # @return [Draper::DecoratedEnumerableProxy]
164
- def self.all(options = {})
165
- Draper::DecoratedEnumerableProxy.new(model_class.all, self, options)
166
- end
167
-
168
- def self.first(options = {})
169
- decorate(model_class.first, options)
170
- end
171
-
172
- def self.last(options = {})
173
- decorate(model_class.last, options)
174
- end
175
-
176
- # Some helpers are private, for example html_escape... as a workaround
177
- # we are wrapping the helpers in a delegator that passes the methods
178
- # along through a send, which will ignore private/public distinctions
179
- class HelpersWrapper
180
- def initialize(helpers)
181
- @helpers = helpers
182
- end
183
-
184
- def method_missing(method, *args, &block)
185
- @helpers.send(method, *args, &block)
186
- end
187
-
188
- #needed for tests
189
- def ==(other)
190
- other.instance_variable_get(:@helpers) == @helpers
191
- end
192
- end
193
-
194
- # Access the helpers proxy to call built-in and user-defined
195
- # Rails helpers. Aliased to `.h` for convenience.
196
- #
197
- # @return [Object] proxy
198
- def helpers
199
- HelpersWrapper.new self.class.helpers
200
- end
201
- alias :h :helpers
202
-
203
- # Localize is something that's used quite often. Even though
204
- # it's available through helpers, that's annoying. Aliased
205
- # to `.l` for convenience.
206
- def localize(object, options = {})
207
- self.class.helpers.localize(object, options)
208
- end
209
- alias :l :localize
210
-
211
- # Access the helpers proxy to call built-in and user-defined
212
- # Rails helpers from a class context.
213
- #
214
- # @return [Object] proxy
215
- class << self
216
- def helpers
217
- Draper::ViewContext.current
218
- end
219
- alias :h :helpers
220
- end
221
-
222
- # Fetch the original wrapped model.
223
- #
224
- # @return [Object] original_model
225
- def wrapped_object
226
- @model
227
- end
228
-
229
- # Delegates == to the decorated models
230
- #
231
- # @return [Boolean] true if other's model == self's model
232
- def ==(other)
233
- @model == (other.respond_to?(:model) ? other.model : other)
234
- end
235
-
236
- def kind_of?(klass)
237
- super || model.kind_of?(klass)
238
- end
239
- alias :is_a? :kind_of?
240
-
241
- def respond_to?(method, include_private = false)
242
- super || (allow?(method) && model.respond_to?(method, include_private))
243
- end
244
-
245
- # We always want to delegate present, in case we decorate a nil object.
246
- #
247
- # I don't like the idea of decorating a nil object, but we'll deal with
248
- # that later.
249
- def present?
250
- model.present?
251
- end
252
-
253
- def method_missing(method, *args, &block)
254
- super unless allow?(method)
255
-
256
- if model.respond_to?(method)
257
- self.class.send :define_method, method do |*args, &blokk|
258
- model.send method, *args, &blokk
259
- end
260
-
261
- send method, *args, &block
262
- else
263
- super
264
- end
265
-
266
- rescue NoMethodError => no_method_error
267
- super if no_method_error.name == method
268
- raise no_method_error
269
- end
270
-
271
- def self.method_missing(method, *args, &block)
272
- if method.to_s.match(/^find_((all_|last_)?by_|or_(initialize|create)_by_).*/)
273
- self.decorate(model_class.send(method, *args, &block), :context => args.dup.extract_options!)
274
- else
275
- model_class.send(method, *args, &block)
276
- end
277
- end
278
-
279
- def self.respond_to?(method, include_private = false)
280
- super || model_class.respond_to?(method)
281
- end
282
-
283
- def context
284
- options.fetch(:context, {})
285
- end
286
-
287
- def context=(input)
288
- options[:context] = input
289
- end
290
-
291
- def source
292
- model
293
- end
294
- alias_method :to_source, :model
295
-
296
- private
297
-
298
- def allow?(method)
299
- (allowed.empty? || allowed.include?(method)) && !denied.include?(method)
300
- end
301
-
302
- def find_association_reflection(association)
303
- if model.class.respond_to?(:reflect_on_association)
304
- model.class.reflect_on_association(association)
305
- end
306
- end
307
-
308
- def decorated_associations
309
- @decorated_associations ||= {}
310
- end
311
- end
312
- end
@@ -1,90 +0,0 @@
1
- require 'active_support/core_ext/object/blank'
2
- module Draper
3
- class DecoratedEnumerableProxy
4
- include Enumerable
5
-
6
- delegate :as_json, :collect, :map, :each, :[], :all?, :include?, :first, :last, :shift, :in_groups_of, :to => :decorated_collection
7
-
8
- # Initialize a new collection decorator instance by passing in
9
- # an instance of a collection. Pass in an optional
10
- # context into the options hash is stored for later use.
11
- #
12
- #
13
- # @param [Object] instances to wrap
14
- # @param [Hash] options (optional)
15
- # @option options [Class] :class The decorator class to use
16
- # for each item in the collection.
17
- # @option options all other options are passed to Decorator
18
- # class for each item.
19
-
20
- def self.decorate(collection, options = {})
21
- new( collection, discern_class_from_my_class(options.delete(:class)), options)
22
- end
23
- class << self
24
- alias_method :decorates, :decorate
25
- end
26
-
27
- def initialize(collection, klass, options = {})
28
- @wrapped_collection, @klass, @options = collection, klass, options
29
- end
30
-
31
- def decorated_collection
32
- @decorated_collection ||= @wrapped_collection.collect { |member| @klass.decorate(member, @options) }
33
- end
34
- alias_method :to_ary, :decorated_collection
35
-
36
- def find(ifnone_or_id = nil, &blk)
37
- if block_given?
38
- source.find(ifnone_or_id, &blk)
39
- else
40
- obj = decorated_collection.first
41
- return nil if obj.blank?
42
- obj.class.find(ifnone_or_id)
43
- end
44
- end
45
-
46
- def method_missing (method, *args, &block)
47
- @wrapped_collection.send(method, *args, &block)
48
- end
49
-
50
- def respond_to?(method, include_private = false)
51
- super || @wrapped_collection.respond_to?(method, include_private)
52
- end
53
-
54
- def kind_of?(klass)
55
- @wrapped_collection.kind_of?(klass) || super
56
- end
57
- alias :is_a? :kind_of?
58
-
59
- def ==(other)
60
- @wrapped_collection == (other.respond_to?(:source) ? other.source : other)
61
- end
62
-
63
- def to_s
64
- "#<DecoratedEnumerableProxy of #{@klass} for #{@wrapped_collection.inspect}>"
65
- end
66
-
67
- def context=(input)
68
- self.map { |member| member.context = input }
69
- end
70
-
71
- def source
72
- @wrapped_collection
73
- end
74
- alias_method :to_source, :source
75
-
76
- def helpers
77
- Draper::ViewContext.current
78
- end
79
- alias_method :h, :helpers
80
-
81
- private
82
- def self.discern_class_from_my_class default_class
83
- return default_class if default_class
84
- name = self.to_s.gsub("Decorator", "")
85
- "#{name.singularize}Decorator".constantize
86
- rescue NameError
87
- raise NameError("You must supply a class (as the klass option) for the members of your collection or the class must be inferable from the name of this class ('#{new.class}')")
88
- end
89
- end
90
- end
@@ -1,25 +0,0 @@
1
- module Draper::ModelSupport
2
- extend ActiveSupport::Concern
3
-
4
- def decorator(options = {})
5
- @decorator ||= decorator_class.decorate(self, options.merge(:infer => false))
6
- block_given? ? yield(@decorator) : @decorator
7
- end
8
-
9
- def decorator_class
10
- "#{self.class.name}Decorator".constantize
11
- end
12
-
13
- alias :decorate :decorator
14
-
15
- module ClassMethods
16
- def decorate(options = {})
17
- decorator_proxy = decorator_class.decorate(self.scoped, options)
18
- block_given? ? yield(decorator_proxy) : decorator_proxy
19
- end
20
-
21
- def decorator_class
22
- "#{model_name}Decorator".constantize
23
- end
24
- end
25
- end