strong_presenter 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -2
  3. data/.rspec +2 -0
  4. data/.travis.yml +18 -0
  5. data/.yardopts +1 -0
  6. data/CHANGELOG.md +31 -0
  7. data/Gemfile +22 -2
  8. data/Guardfile +26 -0
  9. data/README.md +210 -52
  10. data/Rakefile +77 -1
  11. data/gemfiles/3.0.gemfile +2 -0
  12. data/gemfiles/3.1.gemfile +2 -0
  13. data/gemfiles/3.2.gemfile +2 -0
  14. data/gemfiles/4.0.gemfile +2 -0
  15. data/gemfiles/4.1.gemfile +2 -0
  16. data/lib/generators/controller_override.rb +15 -0
  17. data/lib/generators/mini_test/presenter_generator.rb +20 -0
  18. data/lib/generators/mini_test/templates/presenter_spec.rb +4 -0
  19. data/lib/generators/mini_test/templates/presenter_test.rb +4 -0
  20. data/lib/generators/rails/presenter_generator.rb +36 -0
  21. data/lib/generators/rails/templates/presenter.rb +19 -0
  22. data/lib/generators/rspec/presenter_generator.rb +9 -0
  23. data/lib/generators/rspec/templates/presenter_spec.rb +4 -0
  24. data/lib/generators/test_unit/presenter_generator.rb +9 -0
  25. data/lib/generators/test_unit/templates/presenter_test.rb +4 -0
  26. data/lib/strong_presenter/associable.rb +78 -0
  27. data/lib/strong_presenter/collection_presenter.rb +90 -0
  28. data/lib/strong_presenter/controller_additions.rb +50 -0
  29. data/lib/strong_presenter/delegation.rb +18 -0
  30. data/lib/strong_presenter/factory.rb +74 -0
  31. data/lib/strong_presenter/helper_proxy.rb +29 -11
  32. data/lib/strong_presenter/inferrer.rb +54 -0
  33. data/lib/strong_presenter/permissible.rb +73 -0
  34. data/lib/strong_presenter/permissions.rb +138 -0
  35. data/lib/strong_presenter/presenter.rb +191 -0
  36. data/lib/strong_presenter/presenter_association.rb +29 -0
  37. data/lib/strong_presenter/presenter_helper_constructor.rb +60 -0
  38. data/lib/strong_presenter/railtie.rb +27 -3
  39. data/lib/strong_presenter/tasks/test.rake +22 -0
  40. data/lib/strong_presenter/test/devise_helper.rb +30 -0
  41. data/lib/strong_presenter/test/minitest_integration.rb +6 -0
  42. data/lib/strong_presenter/test/rspec_integration.rb +16 -0
  43. data/lib/strong_presenter/test_case.rb +53 -0
  44. data/lib/strong_presenter/version.rb +1 -1
  45. data/lib/strong_presenter/view_context/build_strategy.rb +48 -0
  46. data/lib/strong_presenter/view_context.rb +84 -0
  47. data/lib/strong_presenter/view_helpers.rb +39 -0
  48. data/lib/strong_presenter.rb +64 -2
  49. data/spec/dummy/.rspec +2 -0
  50. data/spec/dummy/Rakefile +7 -0
  51. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  52. data/spec/dummy/app/controllers/localized_urls.rb +5 -0
  53. data/spec/dummy/app/controllers/posts_controller.rb +25 -0
  54. data/spec/dummy/app/helpers/application_helper.rb +5 -0
  55. data/spec/dummy/app/mailers/application_mailer.rb +3 -0
  56. data/spec/dummy/app/mailers/post_mailer.rb +19 -0
  57. data/spec/dummy/app/models/admin.rb +5 -0
  58. data/spec/dummy/app/models/post.rb +3 -0
  59. data/spec/dummy/app/models/user.rb +5 -0
  60. data/spec/dummy/app/presenters/post_presenter.rb +69 -0
  61. data/spec/dummy/app/presenters/special_post_presenter.rb +5 -0
  62. data/spec/dummy/app/presenters/special_posts_presenter.rb +5 -0
  63. data/spec/dummy/app/views/layouts/application.html.erb +11 -0
  64. data/spec/dummy/app/views/post_mailer/presented_email.html.erb +1 -0
  65. data/spec/dummy/app/views/posts/_post.html.erb +50 -0
  66. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  67. data/spec/dummy/bin/rails +4 -0
  68. data/spec/dummy/config/application.rb +70 -0
  69. data/spec/dummy/config/boot.rb +5 -0
  70. data/spec/dummy/config/database.yml +25 -0
  71. data/spec/dummy/config/environment.rb +5 -0
  72. data/spec/dummy/config/environments/development.rb +33 -0
  73. data/spec/dummy/config/environments/production.rb +57 -0
  74. data/spec/dummy/config/environments/test.rb +31 -0
  75. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  76. data/spec/dummy/config/initializers/inflections.rb +15 -0
  77. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  78. data/spec/dummy/config/initializers/secret_token.rb +8 -0
  79. data/spec/dummy/config/initializers/session_store.rb +8 -0
  80. data/spec/dummy/config/locales/en.yml +5 -0
  81. data/spec/dummy/config/routes.rb +9 -0
  82. data/spec/dummy/config.ru +4 -0
  83. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
  84. data/spec/dummy/db/schema.rb +21 -0
  85. data/spec/dummy/db/seeds.rb +2 -0
  86. data/spec/dummy/fast_spec/post_presenter_spec.rb +37 -0
  87. data/spec/dummy/lib/tasks/test.rake +16 -0
  88. data/spec/dummy/log/.gitkeep +0 -0
  89. data/spec/dummy/public/404.html +26 -0
  90. data/spec/dummy/public/422.html +26 -0
  91. data/spec/dummy/public/500.html +25 -0
  92. data/spec/dummy/public/favicon.ico +0 -0
  93. data/spec/dummy/script/rails +6 -0
  94. data/spec/dummy/spec/fast_spec_helper.rb +13 -0
  95. data/spec/dummy/spec/mailers/post_mailer_spec.rb +33 -0
  96. data/spec/dummy/spec/models/post_spec.rb +4 -0
  97. data/spec/dummy/spec/presenters/active_model_serializers_spec.rb +11 -0
  98. data/spec/dummy/spec/presenters/devise_spec.rb +64 -0
  99. data/spec/dummy/spec/presenters/helpers_spec.rb +21 -0
  100. data/spec/dummy/spec/presenters/post_presenter_spec.rb +66 -0
  101. data/spec/dummy/spec/presenters/spec_type_spec.rb +7 -0
  102. data/spec/dummy/spec/presenters/special_post_presenter_spec.rb +11 -0
  103. data/spec/dummy/spec/presenters/view_context_spec.rb +22 -0
  104. data/spec/dummy/spec/spec_helper.rb +19 -0
  105. data/spec/dummy/test/minitest_helper.rb +2 -0
  106. data/spec/dummy/test/presenters/minitest/devise_test.rb +64 -0
  107. data/spec/dummy/test/presenters/minitest/helpers_test.rb +21 -0
  108. data/spec/dummy/test/presenters/minitest/spec_type_test.rb +52 -0
  109. data/spec/dummy/test/presenters/minitest/view_context_test.rb +24 -0
  110. data/spec/dummy/test/presenters/test_unit/devise_test.rb +64 -0
  111. data/spec/dummy/test/presenters/test_unit/helpers_test.rb +21 -0
  112. data/spec/dummy/test/presenters/test_unit/view_context_test.rb +24 -0
  113. data/spec/dummy/test/test_helper.rb +13 -0
  114. data/spec/generators/presenters/presenter_generator_spec.rb +131 -0
  115. data/spec/generators/simplecov_spec.rb +5 -0
  116. data/spec/integration/integration_spec.rb +81 -0
  117. data/spec/integration/simplecov_spec.rb +4 -0
  118. data/spec/spec_helper.rb +47 -0
  119. data/spec/strong_presenter/associable_spec.rb +122 -0
  120. data/spec/strong_presenter/collection_presenter_spec.rb +34 -0
  121. data/spec/strong_presenter/delegation_spec.rb +20 -0
  122. data/spec/strong_presenter/permissible_spec.rb +24 -0
  123. data/spec/strong_presenter/permissions_spec.rb +188 -0
  124. data/spec/strong_presenter/presenter_spec.rb +43 -0
  125. data/spec/strong_presenter/simplecov_spec.rb +4 -0
  126. data/spec/support/dummy_app.rb +85 -0
  127. data/spec/support/matchers/have_text.rb +50 -0
  128. data/spec/support/models.rb +14 -0
  129. data/spec/support/schema.rb +12 -0
  130. data/strong_presenter.gemspec +15 -0
  131. metadata +392 -13
  132. data/lib/strong_presenter/base.rb +0 -217
@@ -0,0 +1,138 @@
1
+ module StrongPresenter
2
+ # @private
3
+ #
4
+ # Storage format:
5
+ # The permissions object is shared by collection presenters with its constituent presenters,
6
+ # and it is also shared with all of its associations. Each attribute path is stored as an array
7
+ # of symbols in a Set. There is one top level presenter - the one which initialized the
8
+ # Permissions object.
9
+ #
10
+ # When a presenter checks for permissions, the attribute path relative to the top
11
+ # presenter is prepended to each attribute path, and its existence checked in the Set.
12
+ #
13
+ # Arguments can also be part of permissions control. They are simply additional elements in the attribute path array,
14
+ # and need not be symbols. If they are symbols, there is no way for Permissions to know whether they
15
+ # are part of the attribute path, or additional arguments. Only the presenter knows that.
16
+ class Permissions
17
+
18
+ # Checks whether everything is permitted.
19
+ #
20
+ # @return [Boolean]
21
+ def complete?
22
+ permitted_paths.include? [] and ((@permitted_paths = Set[[]] if permitted_paths.count > 1) or true)
23
+ end
24
+
25
+ # Permits everything
26
+ #
27
+ # @return self
28
+ def permit_all!
29
+ permitted_paths.clear
30
+ permitted_paths << []
31
+ self
32
+ end
33
+
34
+ # @overload permitted? prefix_path = nil, attribute_path
35
+ #
36
+ # Checks if the attribute path is permitted. This is the case if
37
+ # any array prefix has been permitted.
38
+ #
39
+ # @param [Symbol, Array<Symbol>] prefix_path
40
+ # @param [Symbol, Array<Symbol,Object>] attribute_path
41
+ # @return [Boolean]
42
+ def permitted? prefix_path, attribute_path = nil
43
+ raw_permitted? Array(prefix_path), Array(attribute_path)
44
+ end
45
+
46
+ # Selects the attribute paths which are permitted.
47
+ #
48
+ # @param [Array] prefix_path
49
+ # namespace in which each of the given attribute paths are in
50
+ # @param [[Symbol, Array<Symbol,Object>]*] *attribute_paths
51
+ # each attribute path is a symbol or array of symbols
52
+ # @return [Array<Symbol, Array<Symbol,Object>>] array of attribute paths permitted
53
+ def select_permitted prefix_path, *attribute_paths
54
+ raw_select_permitted Array(prefix_path), nested_array(attribute_paths)
55
+ end
56
+
57
+ # Rejects the attribute paths which are permitted. Opposite of select_permitted.
58
+ # Returns the attribute paths which are not permitted.
59
+ #
60
+ # @param [Array] prefix_path
61
+ # namespace in which each of the given attribute paths are in
62
+ # @param [[Symbol, Array<Symbol,Object>]*] *attribute_paths
63
+ # each attribute path is a symbol or array of symbols
64
+ # @return [Array<Symbol, Array<Symbol,Object>>] array of attribute paths remaining
65
+ def reject_permitted prefix_path, *attribute_paths
66
+ raw_reject_permitted Array(prefix_path), nested_array(attribute_paths)
67
+ end
68
+
69
+ # Permits some attribute paths
70
+ #
71
+ # @param [Array<Symbol>] prefix_path
72
+ # path to prepend to each attribute path
73
+ # @param [[Symbol, Array<Symbol,Object>]*] *attribute_paths
74
+ def permit prefix_path, *attribute_paths
75
+ prefix_path = Array(prefix_path)
76
+ # don't permit if already permitted
77
+ raw_reject_permitted(prefix_path, nested_array(attribute_paths)).each do |attribute_path|
78
+ permitted_paths << prefix_path + attribute_path
79
+ end
80
+ self
81
+ end
82
+
83
+ # Merges the permissions from another Permissions object
84
+ #
85
+ # @param [Permissions] permissions
86
+ # @param [Array<Symbol>] prefix
87
+ # prefix to prepend to paths in permissions
88
+ # @return self
89
+ def merge permissions, prefix = []
90
+ permitted_paths.merge permissions.permitted_paths.map{|path| prefix+path} if permissions.is_a? self.class
91
+ self
92
+ end
93
+
94
+ protected
95
+
96
+ def permitted_paths
97
+ @permitted_paths ||= Set.new
98
+ end
99
+
100
+ private
101
+ # We trust path parameters are arrays
102
+ def raw_permitted? prefix_path, attribute_path = nil # const - does not alter arguments
103
+ return true if complete?
104
+ permitted_partial?([], prefix_path + Array(attribute_path))
105
+ end
106
+
107
+ def raw_reject_permitted prefix_path, attribute_paths # const - does not alter arguments
108
+ return [] if raw_permitted? prefix_path
109
+ attribute_paths.reject do |attribute_path|
110
+ permitted_partial? prefix_path.dup, attribute_path
111
+ end
112
+ end
113
+
114
+ def raw_select_permitted prefix_path, attribute_paths # const - does not alter arguments
115
+ return attribute_paths if raw_permitted? prefix_path
116
+ attribute_paths.select do |attribute_path|
117
+ permitted_partial? prefix_path.dup, attribute_path
118
+ end
119
+ end
120
+
121
+ # For internal use, checks if permitted explicitly by a subpath of [prefix_path, attribute_partial+]
122
+ # where attribute_partial is a prefix of at least one symbol from attribute_part
123
+ # Caution: Will mutate prefix_path
124
+ def permitted_partial? prefix_path, attribute_path
125
+ !!Array(attribute_path).detect do |attr|
126
+ break unless attr.is_a? Symbol
127
+ prefix_path << attr
128
+ permitted_paths.include? prefix_path
129
+ end
130
+ end
131
+
132
+ # Ensures that every array element is an array
133
+ def nested_array array
134
+ array.map{|e|Array(e)}
135
+ end
136
+
137
+ end
138
+ end
@@ -0,0 +1,191 @@
1
+ module StrongPresenter
2
+ class Presenter
3
+ include StrongPresenter::ViewHelpers
4
+ include StrongPresenter::Permissible
5
+ include StrongPresenter::Associable
6
+ include StrongPresenter::Delegation
7
+
8
+ include ActiveModel::Serialization
9
+ include ActiveModel::Serializers::JSON
10
+ include ActiveModel::Serializers::Xml
11
+
12
+ # Constructs the presenter, taking 1 argument for the object being wrapped. For example:
13
+ #
14
+ # user_presenter = UserPresenter.new @user
15
+ #
16
+ # A block can also be passed to use the presenter. For example:
17
+ #
18
+ # <% UserPresenter.new @user do |user_presenter| %>
19
+ # Username: <%= user_presenter.username %>
20
+ # <% end %>
21
+ #
22
+
23
+ def initialize(object)
24
+ @object = object
25
+ yield self if block_given?
26
+ end
27
+
28
+ # Performs mass presentation - if it is allowed, subject to `permit`. To permit all without checking, call `permit!` first.
29
+ #
30
+ # Presents and returns the result of each field in the argument list. If a block is given, then each result
31
+ # is passed to the block. Each field is presented by calling the method on the presenter.
32
+ #
33
+ # user_presenter.presents :username, :email # returns [user_presenter.username, user_presenter.email]
34
+ #
35
+ # Or with two arguments, the name of the field is passed first:
36
+ #
37
+ # <ul>
38
+ # <% user_presenter.presents :username, :email, :address do |field, value| %>
39
+ # <li><%= field.capitalize %>: <% value %></li>
40
+ # <% end %>
41
+ # </ul>
42
+ #
43
+ # If only the presented value is desired, use `each`:
44
+ #
45
+ # <% user_presenter.presents(:username, :email).each do |value| %>
46
+ # <td><%= value %></td>
47
+ # <% end %>
48
+ #
49
+ # A field can have arguments in an array:
50
+ #
51
+ # user_presenter.presents :username, [:notifications, :unread] # returns [user_presenter.username, user_presenter.notifications(:unread)]
52
+ #
53
+ # Notice that this interface allows you to concisely put authorization logic in the controller, with a dumb view layer:
54
+ #
55
+ # # app/controllers/users_controller.rb
56
+ # class UsersController < ApplicationController
57
+ # def visible_params
58
+ # @visible_params ||= begin
59
+ # field = [:username]
60
+ # field << :email if can? :read_email, @user
61
+ # field << :edit_link if can? :edit, @user
62
+ # end
63
+ # end
64
+ # def show
65
+ # @users_presenter = UserPresenter.wrap_each(User.all).permit!
66
+ # end
67
+ # end
68
+ #
69
+ # # app/views/users/show.html.erb
70
+ # <table>
71
+ # <tr>
72
+ # <% visible_params.each do |field| %>
73
+ # <th><%= field %></th>
74
+ # <% end %>
75
+ # </tr>
76
+ # <% @users_presenter.each do |user_presenter| %>
77
+ # <tr>
78
+ # <% user_presenter.presents(*visible_params).each do |value| %>
79
+ # <td><%= value %></td>
80
+ # <% end %>
81
+ # </tr>
82
+ # <% end %>
83
+ # </table>
84
+ #
85
+ def presents *attributes
86
+ select_permitted(*attributes).map do |args|
87
+ obj = self # drill into associations
88
+ while (args.size > 1) && self.class.send(:presenter_associations).include?(args[0]) do
89
+ obj = obj.public_send args.slice!(0)
90
+ end
91
+ value = obj.public_send *args # call final method with args
92
+ yield args[0], value if block_given?
93
+ value
94
+ end
95
+ end
96
+
97
+ # Same as presents, but for a single attribute. The differences are:
98
+ # - the return value is not in an Array
99
+ # - passes the value only (without attribute key as the 1st argument) to a block
100
+ def present field
101
+ presents field do |key, value|
102
+ yield value if block_given?
103
+ end.first
104
+ end
105
+
106
+ delegate :to_s
107
+
108
+ # In case object is nil
109
+ delegate :present?, :blank?
110
+
111
+ # ActiveModel compatibility
112
+ # @private
113
+ def to_model
114
+ self
115
+ end
116
+
117
+ # @return [Hash] the object's attributes, sliced to only include those
118
+ # implemented by the presenter.
119
+ def attributes
120
+ object.attributes.select {|attribute, _| respond_to?(attribute) }
121
+ end
122
+
123
+ # ActiveModel compatibility
124
+ delegate :to_param, :to_partial_path
125
+
126
+ # ActiveModel compatibility
127
+ singleton_class.delegate :model_name, to: :object_class
128
+
129
+ protected
130
+ def object
131
+ @object
132
+ end
133
+
134
+ class << self
135
+ def inferred_presenter(object)
136
+ Inferrer.new(object.class.name).inferred_class { |name| "#{name}Presenter" } or raise StrongPresenter::UninferrablePresenterError.new(self)
137
+ end
138
+
139
+ protected
140
+ def alias_object_to_object_class_name
141
+ if object_class?
142
+ alias_method object_class.name.underscore, :object
143
+ private object_class.name.underscore
144
+ end
145
+ end
146
+
147
+ def set_presenter_collection
148
+ collection_presenter = get_collection_presenter
149
+ const_set "Collection", collection_presenter unless const_defined?("Collection")
150
+ end
151
+
152
+ private
153
+ def inherited(subclass)
154
+ subclass.alias_object_to_object_class_name
155
+ subclass.set_presenter_collection
156
+ super
157
+ end
158
+
159
+ def get_collection_presenter
160
+ collection_presenter = Inferrer.new(name).chomp("Presenter").inferred_class {|name| "#{name.pluralize}Presenter"}
161
+ return collection_presenter unless collection_presenter.nil? || collection_presenter == self
162
+ Class.new(StrongPresenter::CollectionPresenter).presents_with(self)
163
+ end
164
+
165
+ # Returns the source class corresponding to the presenter class, as set by
166
+ # {presents}, or as inferred from the presenter class name (e.g.
167
+ # `ProductPresenter` maps to `Product`).
168
+ #
169
+ # @return [Class] the source class that corresponds to this presenter.
170
+ def object_class
171
+ @object_class ||= Inferrer.new(name).chomp("Presenter").inferred_class or raise UninferrableSourceError.new(self)
172
+ end
173
+
174
+ # Checks whether this presenter class has a corresponding {object_class}.
175
+ def object_class?
176
+ !!(@object_class ||= Inferrer.new(name).chomp("Presenter").inferred_class)
177
+ rescue NameError
178
+ false
179
+ end
180
+
181
+ # Sets the model presented by the class
182
+ #
183
+ def presents name
184
+ @object_class = name.to_s.camelize.constantize
185
+ alias_object_to_object_class_name
186
+ end
187
+
188
+ end
189
+ end
190
+ end
191
+
@@ -0,0 +1,29 @@
1
+ module StrongPresenter
2
+ # @private
3
+ class PresenterAssociation
4
+
5
+ def initialize(association, options, &block)
6
+ options.assert_valid_keys(:with, :scope)
7
+
8
+ @association = association
9
+
10
+ @scope = options.delete(:scope)
11
+ @block = block
12
+
13
+ @factory = StrongPresenter::Factory.new(options)
14
+ end
15
+
16
+ def wrap(parent)
17
+ associated = parent.send(:object).send(association)
18
+ associated = associated.send(scope) if scope
19
+
20
+ factory.wrap(associated) do |presenter|
21
+ parent.instance_exec presenter, &@block if @block
22
+ end
23
+ end
24
+
25
+ private
26
+ attr_reader :factory, :association, :scope
27
+
28
+ end
29
+ end
@@ -0,0 +1,60 @@
1
+ module StrongPresenter
2
+ # @private
3
+ # Defines helper methods in controllers to access instance variables as presenters
4
+ class PresenterHelperConstructor
5
+ # settings: controller to define in, presenter factory, block to execute, options for valid actions
6
+ def initialize(controller_class, block, options)
7
+ @controller_class = controller_class
8
+ @factory = StrongPresenter::Factory.new(options.slice!(:only, :except))
9
+ @block = block
10
+ @action_matcher = setup_action_matcher(options)
11
+ end
12
+
13
+ # Returns proc to check if action matches
14
+ def setup_action_matcher(options)
15
+ options.each { |k,v| options[k] = Array(v).map(&:to_sym) unless v.nil? }
16
+ ->(action) { (options[:only].nil? || options[:only].include?(action)) && (options[:except].nil? || !options[:except].include?(action)) }
17
+ end
18
+
19
+ # call to construct helper
20
+ def call(variable)
21
+ @object = "@#{variable}"
22
+ @presenter = "@#{variable}_presenter"
23
+ construct(variable)
24
+ end
25
+
26
+ private
27
+ attr_accessor :controller_class, :factory, :presenter, :block
28
+
29
+ # actually construct the helper
30
+ def construct(variable)
31
+ shadowed_method = get_shadow_method(variable)
32
+ action_matcher = @action_matcher
33
+ memoized_presenter = method(:memoized_presenter).to_proc
34
+
35
+ controller_class.send :define_method, variable do |*args|
36
+ return shadowed_method.call self, *args unless action_matcher.call(action_name.to_sym) # scoped by controller action?
37
+ raise ArgumentError.new("wrong number of arguments (#{args.size} for 0)") unless args.empty?
38
+ memoized_presenter.call(self)
39
+ end
40
+ end
41
+
42
+ # method which will be shadowed by the defined helper
43
+ def get_shadow_method(method_name) # alias_method_chain without name pollution
44
+ shadowed_method = controller_class.send :instance_method, method_name if controller_class.send :method_defined?, method_name
45
+ return lambda { |obj, *args| raise NoMethodError } if shadowed_method.nil?
46
+ return lambda { |obj, *args| shadowed_method.bind(obj).call(*args) }
47
+ end
48
+
49
+ # get presenter, memoized
50
+ def memoized_presenter(controller)
51
+ return controller.send(:instance_variable_get, presenter) if controller.send(:instance_variable_defined?, presenter)
52
+ controller.send(:instance_variable_set, presenter, wrapped_object(controller))
53
+ end
54
+
55
+ # wrap model with presenter and return
56
+ def wrapped_object(controller)
57
+ factory.wrap(controller.send :instance_variable_get, @object) { |presenter| self.instance_exec presenter, &block }
58
+ end
59
+ end
60
+ end
@@ -1,9 +1,33 @@
1
1
  module StrongPresenter
2
2
  class Railtie < Rails::Railtie
3
- initializer "setup_helper_proxy" do |app|
4
- ActiveSupport.on_load :action_controller do
5
- require 'strong_presenter/helper_proxy' # a hack currently
3
+
4
+ config.after_initialize do |app|
5
+ app.config.paths.add 'app/presenters', eager_load: true
6
+
7
+ if Rails.env.test?
8
+ require 'strong_presenter/test_case'
9
+ require 'strong_presenter/test/rspec_integration' if defined?(RSpec) and RSpec.respond_to?(:configure)
10
+ require 'strong_presenter/test/minitest_integration' if defined?(MiniTest::Rails)
6
11
  end
7
12
  end
13
+
14
+ [:action_controller, :action_mailer, :active_model_serializers].each do |klass|
15
+ initializer "strong_presenter.setup_#{klass}" do |app|
16
+ ActiveSupport.on_load klass do
17
+ StrongPresenter.send "setup_#{klass}", self
18
+ end
19
+ end
20
+ end
21
+
22
+ console do
23
+ require 'action_controller/test_case'
24
+ ApplicationController.new.view_context
25
+ StrongPresenter::ViewContext.build
26
+ end
27
+
28
+ rake_tasks do
29
+ Dir[File.join(File.dirname(__FILE__),'tasks/*.rake')].each { |f| load f }
30
+ end
31
+
8
32
  end
9
33
  end
@@ -0,0 +1,22 @@
1
+ require 'rake/testtask'
2
+
3
+ test_task = if Rails.version.to_f < 3.2
4
+ require 'rails/test_unit/railtie'
5
+ Rake::TestTask
6
+ else
7
+ require 'rails/test_unit/sub_test_task'
8
+ Rails::SubTestTask
9
+ end
10
+
11
+ namespace :test do
12
+ test_task.new(:presenters => "test:prepare") do |t|
13
+ t.libs << "test"
14
+ t.pattern = "test/presenters/**/*_test.rb"
15
+ end
16
+ end
17
+
18
+ if Rake::Task.task_defined?('test:run')
19
+ Rake::Task['test:run'].enhance do
20
+ Rake::Task['test:presenters'].invoke
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ module StrongPresenter
2
+ module DeviseHelper
3
+ def sign_in(resource_or_scope, resource = nil)
4
+ scope = begin
5
+ Devise::Mapping.find_scope!(resource_or_scope)
6
+ rescue RuntimeError => e
7
+ # Draper 1.0 didn't require the mapping to exist
8
+ ActiveSupport::Deprecation.warn("#{e.message}.\nUse `sign_in :user, mock_user` instead.", caller)
9
+ :user
10
+ end
11
+
12
+ _stub_current_scope scope, resource || resource_or_scope
13
+ end
14
+
15
+ def sign_out(resource_or_scope)
16
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
17
+ _stub_current_scope scope, nil
18
+ end
19
+
20
+ private
21
+
22
+ def _stub_current_scope(scope, resource)
23
+ StrongPresenter::ViewContext.current.controller.singleton_class.class_eval do
24
+ define_method "current_#{scope}" do
25
+ resource
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,6 @@
1
+ class StrongPresenter::TestCase
2
+ register_spec_type(self) do |desc|
3
+ desc < StrongPresenter::Presenter || desc < StrongPresenter::CollectionPresenter if desc.is_a?(Class)
4
+ end
5
+ register_spec_type(/Presenter( ?Test)?\z/i, self)
6
+ end
@@ -0,0 +1,16 @@
1
+ module StrongPresenter
2
+ module PresenterExampleGroup
3
+ include StrongPresenter::TestCase::Behavior
4
+ extend ActiveSupport::Concern
5
+
6
+ included { metadata[:type] = :presenter }
7
+ end
8
+
9
+ RSpec.configure do |config|
10
+ config.include PresenterExampleGroup, example_group: {file_path: %r{spec/presenters}}, type: :presenter
11
+
12
+ [:presenter, :controller, :mailer].each do |type|
13
+ config.before(:each, type: type) { StrongPresenter::ViewContext.clear! }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,53 @@
1
+ module StrongPresenter
2
+ begin
3
+ require 'minitest/rails'
4
+ rescue LoadError
5
+ end
6
+
7
+ active_support_test_case = begin
8
+ require 'minitest/rails/active_support' # minitest-rails < 0.5
9
+ ::MiniTest::Rails::ActiveSupport::TestCase
10
+ rescue LoadError
11
+ require 'active_support/test_case'
12
+ ::ActiveSupport::TestCase
13
+ end
14
+
15
+ class TestCase < active_support_test_case
16
+ module ViewContextTeardown
17
+ def teardown
18
+ super
19
+ StrongPresenter::ViewContext.clear!
20
+ end
21
+ end
22
+
23
+ module Behavior
24
+ if defined?(::Devise)
25
+ require 'strong_presenter/test/devise_helper'
26
+ include StrongPresenter::DeviseHelper
27
+ end
28
+
29
+ if defined?(::Capybara) && (defined?(::RSpec) || defined?(::MiniTest::Matchers))
30
+ require 'capybara/rspec/matchers'
31
+ include ::Capybara::RSpecMatchers
32
+ end
33
+
34
+ include StrongPresenter::ViewHelpers::ClassMethods
35
+ alias_method :helper, :helpers
36
+ end
37
+
38
+ include Behavior
39
+ include ViewContextTeardown
40
+ end
41
+ end
42
+
43
+ if defined?(ActionController::TestCase)
44
+ class ActionController::TestCase
45
+ include StrongPresenter::TestCase::ViewContextTeardown
46
+ end
47
+ end
48
+
49
+ if defined?(ActionMailer::TestCase)
50
+ class ActionMailer::TestCase
51
+ include StrongPresenter::TestCase::ViewContextTeardown
52
+ end
53
+ end
@@ -1,3 +1,3 @@
1
1
  module StrongPresenter
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,48 @@
1
+ module StrongPresenter
2
+ module ViewContext
3
+ # @private
4
+ module BuildStrategy
5
+
6
+ def self.new(name, &block)
7
+ const_get(name.to_s.camelize).new(&block)
8
+ end
9
+
10
+ class Fast
11
+ def initialize(&block)
12
+ @view_context_class = Class.new(ActionView::Base, &block)
13
+ end
14
+
15
+ def call
16
+ view_context_class.new
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :view_context_class
22
+ end
23
+
24
+ class Full
25
+ def initialize(&block)
26
+ @block = block
27
+ end
28
+
29
+ def call
30
+ controller.view_context.tap do |context|
31
+ context.singleton_class.class_eval(&block) if block
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :block
38
+
39
+ def controller
40
+ (StrongPresenter::ViewContext.controller || ApplicationController.new).tap do |controller|
41
+ controller.request ||= ActionController::TestRequest.new if defined?(ActionController::TestRequest)
42
+ end
43
+ end
44
+ end
45
+
46
+ end
47
+ end
48
+ end