draper 1.0.0.beta3 → 1.0.0.beta4

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.markdown CHANGED
@@ -1,5 +1,23 @@
1
1
  # Draper Changelog
2
2
 
3
+ ## 1.0.0.beta4
4
+
5
+ * Fixed a race condition with capybara integration. [https://github.com/drapergem/draper/commit/e79464931e7b98c85ed5d78ed9ca38d51f43006e](https://github.com/drapergem/draper/commit/e79464931e7b98c85ed5d78ed9ca38d51f43006e)
6
+
7
+ * `[]` can be decorated again. [https://github.com/drapergem/draper/commit/597fbdf0c80583f5ea6df9f7350fefeaa0cca989](https://github.com/drapergem/draper/commit/597fbdf0c80583f5ea6df9f7350fefeaa0cca989)
8
+
9
+ * `model == decorator` as well as `decorator == model`. [https://github.com/drapergem/draper/commit/46f8a6823c50c13e5c9ab3c07723f335c4e291bc](https://github.com/drapergem/draper/commit/46f8a6823c50c13e5c9ab3c07723f335c4e291bc)
10
+
11
+ * Preliminary Mongoid integration. [https://github.com/drapergem/draper/commit/892d1954202c61fd082a07213c8d4a23560687bc](https://github.com/drapergem/draper/commit/892d1954202c61fd082a07213c8d4a23560687bc)
12
+
13
+ * Add a helper method `sign_in` for devise in decorator specs. [https://github.com/drapergem/draper/commit/66a30093ed4207d02d8fa60bda4df2da091d85a3](https://github.com/drapergem/draper/commit/66a30093ed4207d02d8fa60bda4df2da091d85a3)
14
+
15
+ * Brought back `context`. [https://github.com/drapergem/draper/commit/9609156b997b3a469386eef3a5f043b24d8a2fba](https://github.com/drapergem/draper/commit/9609156b997b3a469386eef3a5f043b24d8a2fba)
16
+
17
+ * Fixed issue where classes were incorrectly being looked up. [https://github.com/drapergem/draper/commit/ee2a015514ff87dfd2158926457e988c2fc3fd79](https://github.com/drapergem/draper/commit/ee2a015514ff87dfd2158926457e988c2fc3fd79)
18
+
19
+ * Integrate RequestStore for per-request storage. [https://github.com/drapergem/draper/commit/fde1cde9adfb856750c1f616d8b62d221ef97fc6](https://github.com/drapergem/draper/commit/fde1cde9adfb856750c1f616d8b62d221ef97fc6)
20
+
3
21
  ## 1.0.0.beta3
4
22
 
5
23
  * Relaxed Rails version requirement to 3.0. Support for < 3.2 should be
@@ -7,13 +25,13 @@
7
25
 
8
26
  ## 1.0.0.beta2
9
27
 
10
- * `has_finders` is now `decorates_finders`. [https://github.com/haines/draper/commit/33f18aa062e0d3848443dbd81047f20d5665579f](https://github.com/haines/draper/commit/33f18aa062e0d3848443dbd81047f20d5665579f)
28
+ * `has_finders` is now `decorates_finders`. [https://github.com/drapergem/draper/commit/33f18aa062e0d3848443dbd81047f20d5665579f](https://github.com/drapergem/draper/commit/33f18aa062e0d3848443dbd81047f20d5665579f)
11
29
 
12
- * 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)
30
+ * If a finder method is used, and the source class is not set and cannot be inferred, an `UninferrableSourceError` is raised. [https://github.com/drapergem/draper/commit/8ef5bf2f02f7033e3cd4f1f5de7397b02c984fe3](https://github.com/drapergem/draper/commit/8ef5bf2f02f7033e3cd4f1f5de7397b02c984fe3)
13
31
 
14
- * Class methods are now properly delegated again. [https://github.com/haines/draper/commit/731995a5feac4cd06cf9328d2892c0eca9992db6](https://github.com/haines/draper/commit/731995a5feac4cd06cf9328d2892c0eca9992db6)
32
+ * Class methods are now properly delegated again. [https://github.com/drapergem/draper/commit/731995a5feac4cd06cf9328d2892c0eca9992db6](https://github.com/drapergem/draper/commit/731995a5feac4cd06cf9328d2892c0eca9992db6)
15
33
 
16
- * We no longer `respond_to?` private methods on the source. [https://github.com/haines/draper/commit/18ebac81533a6413aa20a3c26f23e91d0b12b031](https://github.com/haines/draper/commit/18ebac81533a6413aa20a3c26f23e91d0b12b031)
34
+ * We no longer `respond_to?` private methods on the source. [https://github.com/drapergem/draper/commit/18ebac81533a6413aa20a3c26f23e91d0b12b031](https://github.com/drapergem/draper/commit/18ebac81533a6413aa20a3c26f23e91d0b12b031)
17
35
 
18
36
  * Rails versioning relaxed to support Rails 4 [https://github.com/drapergem/draper/commit/8bfd393b5baa7aa1488076a5e2cb88648efaa815](https://github.com/drapergem/draper/commit/8bfd393b5baa7aa1488076a5e2cb88648efaa815)
19
37
 
@@ -50,27 +68,27 @@ And many small bug fixes and refactorings.
50
68
 
51
69
  ## 0.17.0
52
70
 
53
- * [Fix earlier fix of `view_context` priming](https://github.com/jcasimir/draper/commit/5da44336)
54
- * [Add `denies_all`](https://github.com/jcasimir/draper/commit/148e732)
55
- * [Properly proxy associations with regard to `find`](https://github.com/jcasimir/draper/commit/d46d19205e)
71
+ * [Fix earlier fix of `view_context` priming](https://github.com/drapergem/draper/commit/5da44336)
72
+ * [Add `denies_all`](https://github.com/drapergem/draper/commit/148e732)
73
+ * [Properly proxy associations with regard to `find`](https://github.com/drapergem/draper/commit/d46d19205e)
56
74
 
57
75
  ## 0.16.0
58
76
 
59
- * [Automatically prime `view_context`](https://github.com/jcasimir/draper/commit/057ab4e8)
60
- * [Fixed bug where rspec eq matchers didn't work]((https://github.com/jcasimir/draper/commit/57617b)
61
- * [Sequel ORM support](https://github.com/jcasimir/draper/commit/7d4942)
77
+ * [Automatically prime `view_context`](https://github.com/drapergem/draper/commit/057ab4e8)
78
+ * [Fixed bug where rspec eq matchers didn't work]((https://github.com/drapergem/draper/commit/57617b)
79
+ * [Sequel ORM support](https://github.com/drapergem/draper/commit/7d4942)
62
80
  * Fixed issues with newer minitest
63
- * [Changed the way the `view_context` gets set](https://github.com/jcasimir/draper/commit/0b03d9c)
81
+ * [Changed the way the `view_context` gets set](https://github.com/drapergem/draper/commit/0b03d9c)
64
82
 
65
83
  ## 0.15.0
66
84
 
67
85
  * Proper minitest integration
68
- * [We can properly decorate scoped associations](https://github.com/jcasimir/draper/issues/223)
69
- * [Fixed awkward eager loading](https://github.com/jcasimir/draper/commit/7dc3510b)
86
+ * [We can properly decorate scoped associations](https://github.com/drapergem/draper/issues/223)
87
+ * [Fixed awkward eager loading](https://github.com/drapergem/draper/commit/7dc3510b)
70
88
 
71
89
  ## 0.14.0
72
90
 
73
- * [Properly prime the view context in Rails Console](https://github.com/jcasimir/draper/commit/738074f)
91
+ * [Properly prime the view context in Rails Console](https://github.com/drapergem/draper/commit/738074f)
74
92
  * Make more gems development requirements only
75
93
 
76
94
  ## 0.13.0
@@ -82,7 +100,7 @@ And many small bug fixes and refactorings.
82
100
 
83
101
  ## 0.12.3
84
102
 
85
- * [Fix i18n issue](https://github.com/jcasimir/draper/issues/202)
103
+ * [Fix i18n issue](https://github.com/drapergem/draper/issues/202)
86
104
 
87
105
  ## 0.12.2
88
106
 
@@ -93,13 +111,13 @@ And many small bug fixes and refactorings.
93
111
  ## 0.12.0
94
112
 
95
113
  * Added Changelog
96
- * [Prevented double decoration](https://github.com/jcasimir/draper/issues/173)
97
- * [`ActiveModel::Errors` support](https://github.com/jcasimir/draper/commit/19496f0c)
98
- * [Fixed autoloading issue](https://github.com/jcasimir/draper/issues/188)
99
- * [Re-did generators](https://github.com/jcasimir/draper/commit/9155e58f)
100
- * [Added capybara integration](https://github.com/jcasimir/draper/commit/57c8678e)
114
+ * [Prevented double decoration](https://github.com/drapergem/draper/issues/173)
115
+ * [`ActiveModel::Errors` support](https://github.com/drapergem/draper/commit/19496f0c)
116
+ * [Fixed autoloading issue](https://github.com/drapergem/draper/issues/188)
117
+ * [Re-did generators](https://github.com/drapergem/draper/commit/9155e58f)
118
+ * [Added capybara integration](https://github.com/drapergem/draper/commit/57c8678e)
101
119
  * Fixed a few bugs with the `DecoratedEnumerableProxy`
102
120
 
103
121
  ## 0.11.1
104
122
 
105
- * [Fixed regression, we don't want to introduce a hard dependency on Rails](https://github.com/jcasimir/draper/issues/107)
123
+ * [Fixed regression, we don't want to introduce a hard dependency on Rails](https://github.com/drapergem/draper/issues/107)
data/Readme.markdown CHANGED
@@ -12,10 +12,12 @@
12
12
  1. `h` to proxy to Rails/application helpers like `h.current_user`
13
13
  2. the name of your decorated model to access the wrapped object like `article.created_at`
14
14
  5. Wrap models in your controller with the decorator using:
15
- 1. `.decorate` method with a single object or collection,
16
- ex: `ArticleDecorator.decorate(Article.all)`
15
+ 1. `.decorate` method with a single object,
16
+ ex: `ArticleDecorator.decorate(Article.first)`
17
17
  2. `.new` method with single object
18
18
  ex: `ArticleDecorator.new(Article.first)`
19
+ 3. `.decorate_collection` method with a collection,
20
+ ex: `ArticleDecorator.decorate_collection(Article.all)`
19
21
  6. Call decorator methods from your view templates
20
22
  ex: `<%= @article_decorator.created_at %>`
21
23
 
@@ -201,7 +203,7 @@ ArticleDecorator.new(Article.find(params[:id]))
201
203
 
202
204
  ```ruby
203
205
  ArticleDecorator.decorate(Article.first) # Returns one instance of ArticleDecorator
204
- ArticleDecorator.decorate(Article.all) # Returns an enumeration proxy of ArticleDecorator instances
206
+ ArticleDecorator.decorate_collection(Article.all) # Returns CollectionDecorator of ArticleDecorator
205
207
  ```
206
208
 
207
209
  ### In Your Views
data/draper.gemspec CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
18
18
 
19
19
  s.add_dependency 'activesupport', '>= 3.0'
20
20
  s.add_dependency 'actionpack', '>= 3.0'
21
+ s.add_dependency 'request_store', '~> 1.0.0'
21
22
 
22
23
  s.add_development_dependency 'ammeter'
23
24
  s.add_development_dependency 'rake', '~> 0.9.2'
data/lib/draper.rb CHANGED
@@ -14,10 +14,7 @@ require 'draper/view_context'
14
14
  require 'draper/collection_decorator'
15
15
  require 'draper/railtie' if defined?(Rails)
16
16
 
17
- # Test Support
18
- require 'draper/test/rspec_integration' if defined?(RSpec) and RSpec.respond_to?(:configure)
19
- require 'draper/test/minitest_integration' if defined?(MiniTest::Rails)
20
- require 'draper/test/test_unit_integration'
17
+ require 'active_support/core_ext/hash/keys'
21
18
 
22
19
  module Draper
23
20
  def self.setup_action_controller(base)
@@ -37,7 +34,7 @@ module Draper
37
34
  end
38
35
  end
39
36
 
40
- def self.setup_active_record(base)
37
+ def self.setup_orm(base)
41
38
  base.class_eval do
42
39
  include Draper::Decoratable
43
40
  end
@@ -3,20 +3,21 @@ module Draper
3
3
  include Enumerable
4
4
  include ViewHelpers
5
5
 
6
- attr_accessor :source, :options, :decorator_class
6
+ attr_accessor :source, :context, :decorator_class
7
7
  alias_method :to_source, :source
8
8
 
9
9
  delegate :as_json, *(Array.instance_methods - Object.instance_methods), to: :decorated_collection
10
10
 
11
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
12
+ # @param [Hash] options (optional)
13
+ # @option options [Class, Symbol] :with the class used to decorate
15
14
  # items, or `:infer` to call each item's `decorate` method instead
15
+ # @option options [Hash] :context context available to each item's decorator
16
16
  def initialize(source, options = {})
17
+ options.assert_valid_keys(:with, :context)
17
18
  @source = source
18
- @decorator_class = options.delete(:with) || self.class.inferred_decorator_class
19
- @options = options
19
+ @decorator_class = options.fetch(:with) { self.class.inferred_decorator_class }
20
+ @context = options.fetch(:context, {})
20
21
  end
21
22
 
22
23
  class << self
@@ -56,18 +57,18 @@ module Draper
56
57
  "#<CollectionDecorator of #{decorator_class} for #{source.inspect}>"
57
58
  end
58
59
 
59
- def options=(options)
60
- each {|item| item.options = options }
61
- @options = options
60
+ def context=(value)
61
+ @context = value
62
+ each {|item| item.context = value } if @decorated_collection
62
63
  end
63
64
 
64
65
  protected
65
66
 
66
67
  def decorate_item(item)
67
68
  if decorator_class == :infer
68
- item.decorate(options)
69
+ item.decorate(context: context)
69
70
  else
70
- decorator_class.decorate(item, options)
71
+ decorator_class.decorate(item, context: context)
71
72
  end
72
73
  end
73
74
 
@@ -21,7 +21,7 @@ module Draper::Decoratable
21
21
  false
22
22
  end
23
23
 
24
- def ===(other)
24
+ def ==(other)
25
25
  super || (other.respond_to?(:source) && super(other.source))
26
26
  end
27
27
 
@@ -1,19 +1,24 @@
1
1
  module Draper
2
2
  class DecoratedAssociation
3
3
 
4
- attr_reader :source, :association, :options
4
+ attr_reader :base, :association, :options
5
5
 
6
- def initialize(source, association, options)
7
- @source = source
6
+ def initialize(base, association, options)
7
+ @base = base
8
8
  @association = association
9
+ options.assert_valid_keys(:with, :scope, :context)
9
10
  @options = options
10
11
  end
11
12
 
12
13
  def call
13
- return undecorated if undecorated.nil? || undecorated == []
14
+ return undecorated if undecorated.nil?
14
15
  decorate
15
16
  end
16
17
 
18
+ def source
19
+ base.source
20
+ end
21
+
17
22
  private
18
23
 
19
24
  def undecorated
@@ -25,7 +30,7 @@ module Draper
25
30
  end
26
31
 
27
32
  def decorate
28
- @decorated ||= decorator_class.send(decorate_method, undecorated, options)
33
+ @decorated ||= decorator_class.send(decorate_method, undecorated, decorator_options)
29
34
  end
30
35
 
31
36
  def decorate_method
@@ -51,5 +56,15 @@ module Draper
51
56
  end
52
57
  end
53
58
 
59
+ def decorator_options
60
+ decorator_class # Ensures options[:with] = :infer for unspecified collections
61
+
62
+ dec_options = collection? ? options.slice(:with, :context) : options.slice(:context)
63
+ dec_options[:context] = base.context unless dec_options.key?(:context)
64
+ if dec_options[:context].respond_to?(:call)
65
+ dec_options[:context] = dec_options[:context].call(base.context)
66
+ end
67
+ dec_options
68
+ end
54
69
  end
55
70
  end
@@ -5,14 +5,15 @@ module Draper
5
5
  include Draper::ViewHelpers
6
6
  include ActiveModel::Serialization if defined?(ActiveModel::Serialization)
7
7
 
8
- attr_accessor :source, :options
8
+ attr_accessor :source, :context
9
9
 
10
10
  alias_method :model, :source
11
11
  alias_method :to_source, :source
12
12
 
13
13
  # Initialize a new decorator instance by passing in
14
14
  # an instance of the source class. Pass in an optional
15
- # context inside the options hash is stored for later use.
15
+ # :context inside the options hash which is available
16
+ # for later use.
16
17
  #
17
18
  # A decorator cannot be applied to other instances of the
18
19
  # same decorator and will instead result in a decorator
@@ -23,11 +24,13 @@ module Draper
23
24
  #
24
25
  # @param [Object] source object to decorate
25
26
  # @param [Hash] options (optional)
27
+ # @option options [Hash] :context context available to the decorator
26
28
  def initialize(source, options = {})
29
+ options.assert_valid_keys(:context)
27
30
  source.to_a if source.respond_to?(:to_a) # forces evaluation of a lazy query from AR
28
31
  @source = source
29
- @options = options
30
- handle_multiple_decoration if source.is_a?(Draper::Decorator)
32
+ @context = options.fetch(:context, {})
33
+ handle_multiple_decoration(options) if source.is_a?(Draper::Decorator)
31
34
  end
32
35
 
33
36
  class << self
@@ -38,7 +41,7 @@ module Draper
38
41
  #
39
42
  # @param [String, Symbol, Class] Class or name of class to decorate.
40
43
  def self.decorates(klass)
41
- @source_class = klass.to_s.classify.constantize
44
+ @source_class = klass.to_s.camelize.constantize
42
45
  end
43
46
 
44
47
  # @return [Class] The source class corresponding to this
@@ -72,9 +75,15 @@ module Draper
72
75
  # @param [Symbol] association name of association to decorate, like `:products`
73
76
  # @option options [Class] :with the decorator to apply to the association
74
77
  # @option options [Symbol] :scope a scope to apply when fetching the association
78
+ # @option options [Hash, #call] :context context available to decorated
79
+ # objects in collection. Passing a `lambda` or similar will result in that
80
+ # block being called when the association is evaluated. The block will be
81
+ # passed the base decorator's `context` Hash and should return the desired
82
+ # context Hash for the decorated items.
75
83
  def self.decorates_association(association, options = {})
84
+ options.assert_valid_keys(:with, :scope, :context)
76
85
  define_method(association) do
77
- decorated_associations[association] ||= Draper::DecoratedAssociation.new(source, association, options)
86
+ decorated_associations[association] ||= Draper::DecoratedAssociation.new(self, association, options)
78
87
  decorated_associations[association].call
79
88
  end
80
89
  end
@@ -82,7 +91,8 @@ module Draper
82
91
  # A convenience method for decorating multiple associations. Calls
83
92
  # decorates_association on each of the given symbols.
84
93
  #
85
- # @param [Symbols*] associations name of associations to decorate
94
+ # @param [Symbols*] associations names of associations to decorate
95
+ # @param [Hash] options passed to `decorate_association`
86
96
  def self.decorates_associations(*associations)
87
97
  options = associations.extract_options!
88
98
  associations.each do |association|
@@ -128,7 +138,9 @@ module Draper
128
138
  # for the keys listed below)
129
139
  # @option options [Class,Symbol] :with (self) the class used to decorate
130
140
  # items, or `:infer` to call each item's `decorate` method instead
141
+ # @option options [Hash] :context context available to decorated items
131
142
  def self.decorate_collection(source, options = {})
143
+ options.assert_valid_keys(:with, :context)
132
144
  Draper::CollectionDecorator.new(source, options.reverse_merge(with: self))
133
145
  end
134
146
 
@@ -244,9 +256,9 @@ module Draper
244
256
  self.class.security.allow?(method)
245
257
  end
246
258
 
247
- def handle_multiple_decoration
259
+ def handle_multiple_decoration(options)
248
260
  if source.instance_of?(self.class)
249
- self.options = source.options if options.empty?
261
+ self.context = source.context unless options.has_key?(:context)
250
262
  self.source = source.source
251
263
  elsif source.decorated_with?(self.class)
252
264
  warn "Reapplying #{self.class} decorator to target that is already decorated with it. Call stack:\n#{caller(1).join("\n")}"
@@ -16,6 +16,11 @@ module Draper
16
16
 
17
17
  config.after_initialize do |app|
18
18
  app.config.paths.add 'app/decorators', eager_load: true
19
+
20
+ # Test Support
21
+ require 'draper/test/rspec_integration' if defined?(RSpec) and RSpec.respond_to?(:configure)
22
+ require 'draper/test/minitest_integration' if defined?(MiniTest::Rails)
23
+ require 'draper/test/test_unit_integration'
19
24
  end
20
25
 
21
26
  initializer "draper.setup_action_controller" do |app|
@@ -30,9 +35,11 @@ module Draper
30
35
  end
31
36
  end
32
37
 
33
- initializer "draper.setup_active_record" do |app|
34
- ActiveSupport.on_load :active_record do
35
- Draper.setup_active_record self
38
+ initializer "draper.setup_orm" do |app|
39
+ [:active_record, :mongoid].each do |orm|
40
+ ActiveSupport.on_load orm do
41
+ Draper.setup_orm self
42
+ end
36
43
  end
37
44
  end
38
45
 
@@ -3,6 +3,39 @@ module Draper
3
3
  extend ActiveSupport::Concern
4
4
  included { metadata[:type] = :decorator }
5
5
  end
6
+
7
+ module DeviseHelper
8
+ def sign_in(user)
9
+ warden.stub :authenticate! => user
10
+ controller.stub :current_user => user
11
+ user
12
+ end
13
+
14
+ private
15
+
16
+ def request
17
+ @request ||= ::ActionDispatch::TestRequest.new
18
+ end
19
+
20
+ def controller
21
+ return @controller if @controller
22
+ @controller = ApplicationController.new
23
+ @controller.request = request
24
+ ::Draper::ViewContext.current = @controller.view_context
25
+ @controller
26
+ end
27
+
28
+ # taken from Devise's helper but uses the request method instead of @request
29
+ # and we don't really need the rest of their helper
30
+ def warden
31
+ @warden ||= begin
32
+ manager = Warden::Manager.new(nil) do |config|
33
+ config.merge! Devise.warden_config
34
+ end
35
+ request.env['warden'] = Warden::Proxy.new(request.env, manager)
36
+ end
37
+ end
38
+ end
6
39
  end
7
40
 
8
41
  RSpec.configure do |config|
@@ -11,12 +44,24 @@ RSpec.configure do |config|
11
44
  :file_path => /spec[\\\/]decorators/
12
45
  }
13
46
 
47
+ if defined?(Devise)
48
+ config.include Draper::DeviseHelper, :type => :decorator
49
+ end
14
50
  end
15
51
 
16
- if defined?(Capybara)
17
- require 'capybara/rspec/matchers'
52
+ module Draper
53
+ module RSpec
54
+ class Railtie < Rails::Railtie
55
+ config.after_initialize do |app|
56
+ if defined?(Capybara)
57
+ require 'capybara/rspec/matchers'
18
58
 
19
- RSpec.configure do |config|
20
- config.include Capybara::RSpecMatchers, :type => :decorator
59
+ ::RSpec.configure do |config|
60
+ config.include Capybara::RSpecMatchers, :type => :decorator
61
+ end
62
+ end
63
+ end
64
+ end
21
65
  end
22
66
  end
67
+
@@ -1,3 +1,3 @@
1
1
  module Draper
2
- VERSION = "1.0.0.beta3"
2
+ VERSION = "1.0.0.beta4"
3
3
  end
@@ -1,3 +1,5 @@
1
+ require 'request_store'
2
+
1
3
  module Draper
2
4
  module ViewContext
3
5
  def view_context
@@ -7,19 +9,19 @@ module Draper
7
9
  end
8
10
 
9
11
  def self.current_controller
10
- Thread.current[:current_controller] || ApplicationController.new
12
+ RequestStore.store[:current_controller] || ApplicationController.new
11
13
  end
12
14
 
13
15
  def self.current_controller=(controller)
14
- Thread.current[:current_controller] = controller
16
+ RequestStore.store[:current_controller] = controller
15
17
  end
16
18
 
17
19
  def self.current
18
- Thread.current[:current_view_context] ||= build_view_context
20
+ RequestStore.store[:current_view_context] ||= build_view_context
19
21
  end
20
22
 
21
23
  def self.current=(context)
22
- Thread.current[:current_view_context] = context
24
+ RequestStore.store[:current_view_context] = context
23
25
  end
24
26
 
25
27
  def self.build_view_context
@@ -16,33 +16,70 @@ describe Draper::CollectionDecorator do
16
16
  subject.map{|item| item.source}.should == source
17
17
  end
18
18
 
19
- context "with options" do
20
- subject { Draper::CollectionDecorator.new(source, with: ProductDecorator, some: "options") }
19
+ context "with context" do
20
+ subject { Draper::CollectionDecorator.new(source, with: ProductDecorator, context: {some: 'context'}) }
21
21
 
22
- its(:options) { should == {some: "options"} }
22
+ its(:context) { should == {some: 'context'} }
23
23
 
24
- it "passes options to the individual decorators" do
24
+ it "passes context to the individual decorators" do
25
25
  subject.each do |item|
26
- item.options.should == {some: "options"}
26
+ item.context.should == {some: 'context'}
27
27
  end
28
28
  end
29
29
 
30
- describe "#options=" do
31
- it "updates the options on the collection decorator" do
32
- subject.options = {other: "options"}
33
- subject.options.should == {other: "options"}
30
+ it "does not tie the individual decorators' contexts together" do
31
+ subject.each do |item|
32
+ item.context.should == {some: 'context'}
33
+ item.context = {alt: 'context'}
34
+ item.context.should == {alt: 'context'}
35
+ end
36
+ end
37
+
38
+ describe "#context=" do
39
+ it "updates the collection decorator's context" do
40
+ subject.context = {other: 'context'}
41
+ subject.context.should == {other: 'context'}
34
42
  end
35
43
 
36
- it "updates the options on the individual decorators" do
37
- subject.options = {other: "options"}
38
- subject.each do |item|
39
- item.options.should == {other: "options"}
44
+ context "when the collection is already decorated" do
45
+ it "updates the items' context" do
46
+ subject.decorated_collection
47
+ subject.context = {other: 'context'}
48
+ subject.each do |item|
49
+ item.context.should == {other: 'context'}
50
+ end
51
+ end
52
+ end
53
+
54
+ context "when the collection has not yet been decorated" do
55
+ it "does not trigger decoration" do
56
+ subject.should_not_receive(:decorated_collection)
57
+ subject.context = {other: 'context'}
58
+ end
59
+
60
+ it "sets context after decoration is triggered" do
61
+ subject.context = {other: 'context'}
62
+ subject.each do |item|
63
+ item.context.should == {other: 'context'}
64
+ end
40
65
  end
41
66
  end
42
67
  end
43
68
  end
44
69
 
45
70
  describe "#initialize" do
71
+ describe "options validation" do
72
+ let(:valid_options) { {with: ProductDecorator, context: {}} }
73
+
74
+ it "does not raise error on valid options" do
75
+ expect { Draper::CollectionDecorator.new(source, valid_options) }.to_not raise_error
76
+ end
77
+
78
+ it "raises error on invalid options" do
79
+ expect { Draper::CollectionDecorator.new(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
80
+ end
81
+ end
82
+
46
83
  context "when the :with option is given" do
47
84
  context "and the decorator can't be inferred from the class" do
48
85
  subject { Draper::CollectionDecorator.new(source, with: ProductDecorator) }
@@ -123,14 +160,14 @@ describe Draper::CollectionDecorator do
123
160
  end
124
161
 
125
162
  describe "#localize" do
126
- before { subject.helpers.should_receive(:localize).with(:an_object, {some: "options"}) }
163
+ before { subject.helpers.should_receive(:localize).with(:an_object, {some: 'parameter'}) }
127
164
 
128
165
  it "delegates to helpers" do
129
- subject.localize(:an_object, some: "options")
166
+ subject.localize(:an_object, some: 'parameter')
130
167
  end
131
168
 
132
169
  it "is aliased to #l" do
133
- subject.l(:an_object, some: "options")
170
+ subject.l(:an_object, some: 'parameter')
134
171
  end
135
172
  end
136
173
 
@@ -9,9 +9,9 @@ describe Draper::Decoratable do
9
9
  subject.decorate.source.should be subject
10
10
  end
11
11
 
12
- it "accepts options" do
13
- decorator = subject.decorate(some: "options")
14
- decorator.options.should == {some: "options"}
12
+ it "accepts context" do
13
+ decorator = subject.decorate(context: {some: 'context'})
14
+ decorator.context.should == {some: 'context'}
15
15
  end
16
16
 
17
17
  it "is not memoized" do
@@ -45,6 +45,34 @@ describe Draper::Decoratable do
45
45
  end
46
46
  end
47
47
 
48
+ describe "#==" do
49
+ context "with itself" do
50
+ it "returns true" do
51
+ (subject == subject).should be_true
52
+ end
53
+ end
54
+
55
+ context "with another instance" do
56
+ it "returns false" do
57
+ (subject == Product.new).should be_false
58
+ end
59
+ end
60
+
61
+ context "with a decorated version of itself" do
62
+ it "returns true" do
63
+ decorator = double(source: subject)
64
+ (subject == decorator).should be_true
65
+ end
66
+ end
67
+
68
+ context "with a decorated other instance" do
69
+ it "returns false" do
70
+ decorator = double(source: Product.new)
71
+ (subject == decorator).should be_false
72
+ end
73
+ end
74
+ end
75
+
48
76
  describe "#===" do
49
77
  context "with itself" do
50
78
  it "returns true" do
@@ -125,9 +153,9 @@ describe Draper::Decoratable do
125
153
  decorator.source.should be Product.scoped
126
154
  end
127
155
 
128
- it "accepts options" do
129
- decorator = Product.decorate(some: "options")
130
- decorator.options.should == {some: "options"}
156
+ it "accepts context" do
157
+ decorator = Product.decorate(context: {some: 'context'})
158
+ decorator.context.should == {some: 'context'}
131
159
  end
132
160
 
133
161
  it "is not memoized" do
@@ -1,10 +1,52 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Draper::DecoratedAssociation do
4
- let(:decorated_association) { Draper::DecoratedAssociation.new(source, association, options) }
4
+ let(:decorated_association) { Draper::DecoratedAssociation.new(base, association, options) }
5
5
  let(:source) { Product.new }
6
+ let(:base) { source.decorate }
6
7
  let(:options) { {} }
7
8
 
9
+ describe "#initialize" do
10
+ describe "options validation" do
11
+ let(:association) { :similar_products }
12
+ let(:valid_options) { {with: ProductDecorator, scope: :foo, context: {}} }
13
+
14
+ it "does not raise error on valid options" do
15
+ expect { Draper::DecoratedAssociation.new(base, association, valid_options) }.to_not raise_error
16
+ end
17
+
18
+ it "raises error on invalid options" do
19
+ expect { Draper::DecoratedAssociation.new(base, association, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
20
+ end
21
+ end
22
+ end
23
+
24
+ describe "#base" do
25
+ subject { decorated_association.base }
26
+ let(:association) { :similar_products }
27
+
28
+ it "returns the base decorator" do
29
+ should be base
30
+ end
31
+
32
+ it "returns a Decorator" do
33
+ subject.class.should == ProductDecorator
34
+ end
35
+ end
36
+
37
+ describe "#source" do
38
+ subject { decorated_association.source }
39
+ let(:association) { :similar_products }
40
+
41
+ it "returns the base decorator's source" do
42
+ should be base.source
43
+ end
44
+
45
+ it "returns a Model" do
46
+ subject.class.should == Product
47
+ end
48
+ end
49
+
8
50
  describe "#call" do
9
51
  subject { decorated_association.call }
10
52
 
@@ -22,10 +64,11 @@ describe Draper::DecoratedAssociation do
22
64
  end
23
65
 
24
66
  context "when the association is empty" do
25
- it "doesn't decorate the collection" do
67
+ it "returns an empty collection decorator" do
26
68
  source.stub(:similar_products).and_return([])
27
- subject.should_not be_a Draper::CollectionDecorator
69
+ subject.should be_a Draper::CollectionDecorator
28
70
  subject.should be_empty
71
+ subject.first.should be_nil
29
72
  end
30
73
  end
31
74
  end
@@ -44,10 +87,11 @@ describe Draper::DecoratedAssociation do
44
87
  end
45
88
 
46
89
  context "when the association is empty" do
47
- it "doesn't decorate the collection" do
90
+ it "returns an empty collection decorator" do
48
91
  source.stub(:poro_similar_products).and_return([])
49
- subject.should_not be_a Draper::CollectionDecorator
92
+ subject.should be_a Draper::CollectionDecorator
50
93
  subject.should be_empty
94
+ subject.first.should be_nil
51
95
  end
52
96
  end
53
97
  end
@@ -126,5 +170,144 @@ describe Draper::DecoratedAssociation do
126
170
  subject.source.should be scoped
127
171
  end
128
172
  end
173
+
174
+ context "base has context" do
175
+ let(:association) { :similar_products }
176
+ let(:base) { source.decorate(context: {some: 'context'}) }
177
+
178
+ context "when no context is specified" do
179
+ it "it should inherit context from base" do
180
+ subject.context.should == {some: 'context'}
181
+ end
182
+
183
+ it "it should share context hash with base" do
184
+ subject.context.should be base.context
185
+ end
186
+ end
187
+
188
+ context "when static context is specified" do
189
+ let(:options) { {context: {other: 'context'}} }
190
+
191
+ it "it should get context from static option" do
192
+ subject.context.should == {other: 'context'}
193
+ end
194
+ end
195
+
196
+ context "when lambda context is specified" do
197
+ let(:options) { {context: lambda {|context| context.merge(other: 'protext')}} }
198
+
199
+ it "it should get generated context" do
200
+ subject.context.should == {some: 'context', other: 'protext'}
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ describe "#decorator_options" do
207
+ subject { decorated_association.send(:decorator_options) }
208
+
209
+ context "collection association" do
210
+ let(:association) { :similar_products }
211
+
212
+ context "no options" do
213
+ it "should return default options" do
214
+ should == {with: :infer, context: {}}
215
+ end
216
+
217
+ it "should set with: to :infer" do
218
+ decorated_association.send(:options).should == options
219
+ subject
220
+ decorated_association.send(:options).should == {with: :infer}
221
+ end
222
+ end
223
+
224
+ context "option with: ProductDecorator" do
225
+ let(:options) { {with: ProductDecorator} }
226
+ it "should pass with: from options" do
227
+ should == {with: ProductDecorator, context: {}}
228
+ end
229
+ end
230
+
231
+ context "option scope: :to_a" do
232
+ let(:options) { {scope: :to_a} }
233
+ it "should strip scope: from options" do
234
+ decorated_association.send(:options).should == options
235
+ should == {with: :infer, context: {}}
236
+ end
237
+ end
238
+
239
+ context "base has context" do
240
+ let(:base) { source.decorate(context: {some: 'context'}) }
241
+
242
+ context "no options" do
243
+ it "should return context from base" do
244
+ should == {with: :infer, context: {some: 'context'}}
245
+ end
246
+ end
247
+
248
+ context "option context: {other: 'context'}" do
249
+ let(:options) { {context: {other: 'context'}} }
250
+ it "should return specified context" do
251
+ should == {with: :infer, context: {other: 'context'}}
252
+ end
253
+ end
254
+
255
+ context "option context: lambda" do
256
+ let(:options) { {context: lambda {|context| context.merge(other: 'protext')}} }
257
+ it "should return specified context" do
258
+ should == {with: :infer, context: {some: 'context', other: 'protext'}}
259
+ end
260
+ end
261
+ end
262
+ end
263
+
264
+ context "singular association" do
265
+ let(:association) { :previous_version }
266
+
267
+ context "no options" do
268
+ it "should return default options" do
269
+ should == {context: {}}
270
+ end
271
+ end
272
+
273
+ context "option with: ProductDecorator" do
274
+ let(:options) { {with: ProductDecorator} }
275
+ it "should strip with: from options" do
276
+ should == {context: {}}
277
+ end
278
+ end
279
+
280
+ context "option scope: :decorate" do
281
+ let(:options) { {scope: :decorate} }
282
+ it "should strip scope: from options" do
283
+ decorated_association.send(:options).should == options
284
+ should == {context: {}}
285
+ end
286
+ end
287
+
288
+ context "base has context" do
289
+ let(:base) { source.decorate(context: {some: 'context'}) }
290
+
291
+ context "no options" do
292
+ it "should return context from base" do
293
+ should == {context: {some: 'context'}}
294
+ end
295
+ end
296
+
297
+ context "option context: {other: 'context'}" do
298
+ let(:options) { {context: {other: 'context'}} }
299
+ it "should return specified context" do
300
+ should == {context: {other: 'context'}}
301
+ end
302
+ end
303
+
304
+ context "option context: lambda" do
305
+ let(:options) { {context: lambda {|context| context.merge(other: 'protext')}} }
306
+ it "should return specified context" do
307
+ should == {context: {some: 'context', other: 'protext'}}
308
+ end
309
+ end
310
+ end
311
+ end
129
312
  end
130
313
  end
@@ -7,13 +7,25 @@ describe Draper::Decorator do
7
7
  let(:source) { Product.new }
8
8
 
9
9
  describe "#initialize" do
10
+ describe "options validation" do
11
+ let(:valid_options) { {context: {}} }
12
+
13
+ it "does not raise error on valid options" do
14
+ expect { decorator_class.new(source, valid_options) }.to_not raise_error
15
+ end
16
+
17
+ it "raises error on invalid options" do
18
+ expect { decorator_class.new(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
19
+ end
20
+ end
21
+
10
22
  it "sets the source" do
11
23
  subject.source.should be source
12
24
  end
13
25
 
14
- it "stores options" do
15
- decorator = decorator_class.new(source, some: "options")
16
- decorator.options.should == {some: "options"}
26
+ it "stores context" do
27
+ decorator = decorator_class.new(source, context: {some: 'context'})
28
+ decorator.context.should == {some: 'context'}
17
29
  end
18
30
 
19
31
  context "when decorating an instance of itself" do
@@ -23,16 +35,16 @@ describe Draper::Decorator do
23
35
  end
24
36
 
25
37
  context "when options are supplied" do
26
- it "overwrites existing options" do
27
- decorator = ProductDecorator.new(source, role: :admin)
28
- ProductDecorator.new(decorator, role: :user).options.should == {role: :user}
38
+ it "overwrites existing context" do
39
+ decorator = ProductDecorator.new(source, context: {role: :admin})
40
+ ProductDecorator.new(decorator, context: {role: :user}).context.should == {role: :user}
29
41
  end
30
42
  end
31
43
 
32
44
  context "when no options are supplied" do
33
- it "preserves existing options" do
34
- decorator = ProductDecorator.new(source, role: :admin)
35
- ProductDecorator.new(decorator).options.should == {role: :admin}
45
+ it "preserves existing context" do
46
+ decorator = ProductDecorator.new(source, context: {role: :admin})
47
+ ProductDecorator.new(decorator).context.should == {role: :admin}
36
48
  end
37
49
  end
38
50
  end
@@ -55,10 +67,31 @@ describe Draper::Decorator do
55
67
  end
56
68
  end
57
69
 
70
+ describe "#context=" do
71
+ it "modifies the context" do
72
+ decorator = decorator_class.new(source, context: {some: 'context'})
73
+ decorator.context = {some: 'other_context'}
74
+ decorator.context.should == {some: 'other_context'}
75
+ end
76
+ end
77
+
58
78
  describe ".decorate_collection" do
59
79
  subject { ProductDecorator.decorate_collection(source) }
60
80
  let(:source) { [Product.new, Widget.new] }
61
81
 
82
+ describe "options validation" do
83
+ let(:valid_options) { {with: :infer, context: {}} }
84
+ before(:each) { Draper::CollectionDecorator.stub(:new) }
85
+
86
+ it "does not raise error on valid options" do
87
+ expect { ProductDecorator.decorate_collection(source, valid_options) }.to_not raise_error
88
+ end
89
+
90
+ it "raises error on invalid options" do
91
+ expect { ProductDecorator.decorate_collection(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
92
+ end
93
+ end
94
+
62
95
  it "returns a collection decorator" do
63
96
  subject.should be_a Draper::CollectionDecorator
64
97
  subject.source.should be source
@@ -77,11 +110,11 @@ describe Draper::Decorator do
77
110
  end
78
111
  end
79
112
 
80
- context "with options" do
81
- subject { ProductDecorator.decorate_collection(source, with: :infer, some: "options") }
113
+ context "with context" do
114
+ subject { ProductDecorator.decorate_collection(source, with: :infer, context: {some: 'context'}) }
82
115
 
83
- it "passes the options to the collection decorator" do
84
- subject.options.should == {some: "options"}
116
+ it "passes the context to the collection decorator" do
117
+ subject.context.should == {some: 'context'}
85
118
  end
86
119
  end
87
120
  end
@@ -104,14 +137,14 @@ describe Draper::Decorator do
104
137
  end
105
138
 
106
139
  describe "#localize" do
107
- before { subject.helpers.should_receive(:localize).with(:an_object, {some: "options"}) }
140
+ before { subject.helpers.should_receive(:localize).with(:an_object, {some: 'parameter'}) }
108
141
 
109
142
  it "delegates to #helpers" do
110
- subject.localize(:an_object, some: "options")
143
+ subject.localize(:an_object, some: 'parameter')
111
144
  end
112
145
 
113
146
  it "is aliased to #l" do
114
- subject.l(:an_object, some: "options")
147
+ subject.l(:an_object, some: 'parameter')
115
148
  end
116
149
  end
117
150
 
@@ -223,8 +256,26 @@ describe Draper::Decorator do
223
256
  describe "overridden association method" do
224
257
  let(:decorated_association) { ->{} }
225
258
 
259
+ describe "options validation" do
260
+ let(:valid_options) { {with: ProductDecorator, scope: :foo, context: {}} }
261
+ before(:each) { Draper::DecoratedAssociation.stub(:new).and_return(decorated_association) }
262
+
263
+ it "does not raise error on valid options" do
264
+ expect { decorator_class.decorates_association :similar_products, valid_options }.to_not raise_error
265
+ end
266
+
267
+ it "raises error on invalid options" do
268
+ expect { decorator_class.decorates_association :similar_products, valid_options.merge(foo: 'bar') }.to raise_error(ArgumentError, 'Unknown key: foo')
269
+ end
270
+ end
271
+
226
272
  it "creates a DecoratedAssociation" do
227
- Draper::DecoratedAssociation.should_receive(:new).with(source, :similar_products, {with: ProductDecorator}).and_return(decorated_association)
273
+ Draper::DecoratedAssociation.should_receive(:new).with(subject, :similar_products, {with: ProductDecorator}).and_return(decorated_association)
274
+ subject.similar_products
275
+ end
276
+
277
+ it "receives the Decorator" do
278
+ Draper::DecoratedAssociation.should_receive(:new).with(kind_of(decorator_class), :similar_products, {with: ProductDecorator}).and_return(decorated_association)
228
279
  subject.similar_products
229
280
  end
230
281
 
@@ -15,9 +15,9 @@ describe Draper::Finders do
15
15
  decorator.source.should be found
16
16
  end
17
17
 
18
- it "passes options to the decorator" do
19
- decorator = ProductDecorator.find(1, some: "options")
20
- decorator.options.should == {some: "options"}
18
+ it "passes context to the decorator" do
19
+ decorator = ProductDecorator.find(1, context: {some: 'context'})
20
+ decorator.context.should == {some: 'context'}
21
21
  end
22
22
  end
23
23
 
@@ -55,10 +55,10 @@ describe Draper::Finders do
55
55
  ProductDecorator.find_or_create_by_name_and_size("apples", "large")
56
56
  end
57
57
 
58
- it "passes options to the decorator" do
59
- Product.should_receive(:find_by_name_and_size).with("apples", "large", {some: "options"})
60
- decorator = ProductDecorator.find_by_name_and_size("apples", "large", some: "options")
61
- decorator.options.should == {some: "options"}
58
+ it "passes context to the decorator" do
59
+ Product.should_receive(:find_by_name_and_size).with("apples", "large", context: {some: 'context'})
60
+ decorator = ProductDecorator.find_by_name_and_size("apples", "large", context: {some: 'context'})
61
+ decorator.context.should == {some: 'context'}
62
62
  end
63
63
  end
64
64
 
@@ -84,9 +84,9 @@ describe Draper::Finders do
84
84
  collection.first.should be_a ProductDecorator
85
85
  end
86
86
 
87
- it "passes options to the collection decorator" do
88
- collection = ProductDecorator.all(some: "options")
89
- collection.options.should == {some: "options"}
87
+ it "passes context to the collection decorator" do
88
+ collection = ProductDecorator.all(context: {some: 'context'})
89
+ collection.context.should == {some: 'context'}
90
90
  end
91
91
  end
92
92
 
@@ -104,9 +104,9 @@ describe Draper::Finders do
104
104
  decorator.source.should be first
105
105
  end
106
106
 
107
- it "passes options to the decorator" do
108
- decorator = ProductDecorator.first(some: "options")
109
- decorator.options.should == {some: "options"}
107
+ it "passes context to the decorator" do
108
+ decorator = ProductDecorator.first(context: {some: 'context'})
109
+ decorator.context.should == {some: 'context'}
110
110
  end
111
111
  end
112
112
 
@@ -124,9 +124,9 @@ describe Draper::Finders do
124
124
  decorator.source.should be last
125
125
  end
126
126
 
127
- it "passes options to the decorator" do
128
- decorator = ProductDecorator.last(some: "options")
129
- decorator.options.should == {some: "options"}
127
+ it "passes context to the decorator" do
128
+ decorator = ProductDecorator.last(context: {some: 'context'})
129
+ decorator.context.should == {some: 'context'}
130
130
  end
131
131
  end
132
132
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: draper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta3
4
+ version: 1.0.0.beta4
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-12-03 00:00:00.000000000 Z
13
+ date: 2012-12-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -44,6 +44,22 @@ dependencies:
44
44
  - - ! '>='
45
45
  - !ruby/object:Gem::Version
46
46
  version: '3.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: request_store
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: 1.0.0
47
63
  - !ruby/object:Gem::Dependency
48
64
  name: ammeter
49
65
  requirement: !ruby/object:Gem::Requirement
@@ -274,9 +290,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
274
290
  - - ! '>='
275
291
  - !ruby/object:Gem::Version
276
292
  version: '0'
277
- segments:
278
- - 0
279
- hash: -1515210213471723462
280
293
  required_rubygems_version: !ruby/object:Gem::Requirement
281
294
  none: false
282
295
  requirements:
@@ -285,7 +298,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
285
298
  version: 1.3.1
286
299
  requirements: []
287
300
  rubyforge_project: draper
288
- rubygems_version: 1.8.24
301
+ rubygems_version: 1.8.23
289
302
  signing_key:
290
303
  specification_version: 3
291
304
  summary: Decorator pattern implementation for Rails.