draper 1.0.0.beta3 → 1.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
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.