responders 1.1.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d10c5ba8c6762f3d9554a9474cf02f8b4c1a0e20
4
- data.tar.gz: 5b9a35e9669c0219287450813a7f6dbeeae1df5b
3
+ metadata.gz: 1dc27831fc7b12f53a28dba5186e256cceab2d17
4
+ data.tar.gz: 277b5266e4495e11b903ca12b36aecd192d494b3
5
5
  SHA512:
6
- metadata.gz: 0514aae5e8307def2b6c6be5970ffaae0195ae50cb6880314dd25e68ee9cf4b3bdcfed65ff18193a08ab53488b4517319ddb66cc4abd6051e11ad05efb9b1052
7
- data.tar.gz: 93db7f8e08401a53d6f0e79f894e2858d59f1ca231293f5b2821b94ce6eee05e83c305cb4ad69fbbefb71e718b6ceae7000fc3b9c02795a72320aa8106fdef0f
6
+ metadata.gz: 6cb93c417ef6b0dd0844684cecc12e29822e422f88bdaf3fee3188d83c581042e6fc302b25e2717168821fc248dd2530f104d2819501aeec4887cdfdaaf3b575
7
+ data.tar.gz: b289b2e512aa9fcbb8ffdeaaa04857bbcf0caeef8a0026a257891856e4e0f13f8d90858bb276d44d4a1fe38919fd832c831b09e26fa79da15ed1855d237465cf
data/CHANGELOG.md CHANGED
@@ -1,12 +1,8 @@
1
- ## 1.1.2
1
+ ## 2.0.0
2
2
 
3
- * Improve support for namespaced models on the scaffold generator.
4
- * Add default `respond_to` in scaffold template.
5
- * Fix require of Action Controller.
6
-
7
- ## 1.1.1
8
-
9
- * Lock Rails requirement to < 4.2.
3
+ * Import `respond_with` and class-level `respond_to` from Rails
4
+ * Support only Rails ~> 4.2
5
+ * `Responders::LocationResponder` is now included by in the default responder (and therefore deprecated)
10
6
 
11
7
  ## 1.1.0
12
8
 
@@ -0,0 +1,229 @@
1
+ require 'active_support/core_ext/array/extract_options'
2
+ require 'action_controller/metal/mime_responds'
3
+
4
+ module ActionController #:nodoc:
5
+ module RespondWith
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_attribute :responder, :mimes_for_respond_to
10
+ self.responder = ActionController::Responder
11
+ clear_respond_to
12
+ end
13
+
14
+ module ClassMethods
15
+ # Defines mime types that are rendered by default when invoking
16
+ # <tt>respond_with</tt>.
17
+ #
18
+ # respond_to :html, :xml, :json
19
+ #
20
+ # Specifies that all actions in the controller respond to requests
21
+ # for <tt>:html</tt>, <tt>:xml</tt> and <tt>:json</tt>.
22
+ #
23
+ # To specify on per-action basis, use <tt>:only</tt> and
24
+ # <tt>:except</tt> with an array of actions or a single action:
25
+ #
26
+ # respond_to :html
27
+ # respond_to :xml, :json, except: [ :edit ]
28
+ #
29
+ # This specifies that all actions respond to <tt>:html</tt>
30
+ # and all actions except <tt>:edit</tt> respond to <tt>:xml</tt> and
31
+ # <tt>:json</tt>.
32
+ #
33
+ # respond_to :json, only: :create
34
+ #
35
+ # This specifies that the <tt>:create</tt> action and no other responds
36
+ # to <tt>:json</tt>.
37
+ def respond_to(*mimes)
38
+ options = mimes.extract_options!
39
+
40
+ only_actions = Array(options.delete(:only)).map(&:to_s)
41
+ except_actions = Array(options.delete(:except)).map(&:to_s)
42
+
43
+ new = mimes_for_respond_to.dup
44
+ mimes.each do |mime|
45
+ mime = mime.to_sym
46
+ new[mime] = {}
47
+ new[mime][:only] = only_actions unless only_actions.empty?
48
+ new[mime][:except] = except_actions unless except_actions.empty?
49
+ end
50
+ self.mimes_for_respond_to = new.freeze
51
+ end
52
+
53
+ # Clear all mime types in <tt>respond_to</tt>.
54
+ #
55
+ def clear_respond_to
56
+ self.mimes_for_respond_to = Hash.new.freeze
57
+ end
58
+ end
59
+
60
+ # For a given controller action, respond_with generates an appropriate
61
+ # response based on the mime-type requested by the client.
62
+ #
63
+ # If the method is called with just a resource, as in this example -
64
+ #
65
+ # class PeopleController < ApplicationController
66
+ # respond_to :html, :xml, :json
67
+ #
68
+ # def index
69
+ # @people = Person.all
70
+ # respond_with @people
71
+ # end
72
+ # end
73
+ #
74
+ # then the mime-type of the response is typically selected based on the
75
+ # request's Accept header and the set of available formats declared
76
+ # by previous calls to the controller's class method +respond_to+. Alternatively
77
+ # the mime-type can be selected by explicitly setting <tt>request.format</tt> in
78
+ # the controller.
79
+ #
80
+ # If an acceptable format is not identified, the application returns a
81
+ # '406 - not acceptable' status. Otherwise, the default response is to render
82
+ # a template named after the current action and the selected format,
83
+ # e.g. <tt>index.html.erb</tt>. If no template is available, the behavior
84
+ # depends on the selected format:
85
+ #
86
+ # * for an html response - if the request method is +get+, an exception
87
+ # is raised but for other requests such as +post+ the response
88
+ # depends on whether the resource has any validation errors (i.e.
89
+ # assuming that an attempt has been made to save the resource,
90
+ # e.g. by a +create+ action) -
91
+ # 1. If there are no errors, i.e. the resource
92
+ # was saved successfully, the response +redirect+'s to the resource
93
+ # i.e. its +show+ action.
94
+ # 2. If there are validation errors, the response
95
+ # renders a default action, which is <tt>:new</tt> for a
96
+ # +post+ request or <tt>:edit</tt> for +patch+ or +put+.
97
+ # Thus an example like this -
98
+ #
99
+ # respond_to :html, :xml
100
+ #
101
+ # def create
102
+ # @user = User.new(params[:user])
103
+ # flash[:notice] = 'User was successfully created.' if @user.save
104
+ # respond_with(@user)
105
+ # end
106
+ #
107
+ # is equivalent, in the absence of <tt>create.html.erb</tt>, to -
108
+ #
109
+ # def create
110
+ # @user = User.new(params[:user])
111
+ # respond_to do |format|
112
+ # if @user.save
113
+ # flash[:notice] = 'User was successfully created.'
114
+ # format.html { redirect_to(@user) }
115
+ # format.xml { render xml: @user }
116
+ # else
117
+ # format.html { render action: "new" }
118
+ # format.xml { render xml: @user }
119
+ # end
120
+ # end
121
+ # end
122
+ #
123
+ # * for a JavaScript request - if the template isn't found, an exception is
124
+ # raised.
125
+ # * for other requests - i.e. data formats such as xml, json, csv etc, if
126
+ # the resource passed to +respond_with+ responds to <code>to_<format></code>,
127
+ # the method attempts to render the resource in the requested format
128
+ # directly, e.g. for an xml request, the response is equivalent to calling
129
+ # <code>render xml: resource</code>.
130
+ #
131
+ # === Nested resources
132
+ #
133
+ # As outlined above, the +resources+ argument passed to +respond_with+
134
+ # can play two roles. It can be used to generate the redirect url
135
+ # for successful html requests (e.g. for +create+ actions when
136
+ # no template exists), while for formats other than html and JavaScript
137
+ # it is the object that gets rendered, by being converted directly to the
138
+ # required format (again assuming no template exists).
139
+ #
140
+ # For redirecting successful html requests, +respond_with+ also supports
141
+ # the use of nested resources, which are supplied in the same way as
142
+ # in <code>form_for</code> and <code>polymorphic_url</code>. For example -
143
+ #
144
+ # def create
145
+ # @project = Project.find(params[:project_id])
146
+ # @task = @project.comments.build(params[:task])
147
+ # flash[:notice] = 'Task was successfully created.' if @task.save
148
+ # respond_with(@project, @task)
149
+ # end
150
+ #
151
+ # This would cause +respond_with+ to redirect to <code>project_task_url</code>
152
+ # instead of <code>task_url</code>. For request formats other than html or
153
+ # JavaScript, if multiple resources are passed in this way, it is the last
154
+ # one specified that is rendered.
155
+ #
156
+ # === Customizing response behavior
157
+ #
158
+ # Like +respond_to+, +respond_with+ may also be called with a block that
159
+ # can be used to overwrite any of the default responses, e.g. -
160
+ #
161
+ # def create
162
+ # @user = User.new(params[:user])
163
+ # flash[:notice] = "User was successfully created." if @user.save
164
+ #
165
+ # respond_with(@user) do |format|
166
+ # format.html { render }
167
+ # end
168
+ # end
169
+ #
170
+ # The argument passed to the block is an ActionController::MimeResponds::Collector
171
+ # object which stores the responses for the formats defined within the
172
+ # block. Note that formats with responses defined explicitly in this way
173
+ # do not have to first be declared using the class method +respond_to+.
174
+ #
175
+ # Also, a hash passed to +respond_with+ immediately after the specified
176
+ # resource(s) is interpreted as a set of options relevant to all
177
+ # formats. Any option accepted by +render+ can be used, e.g.
178
+ # respond_with @people, status: 200
179
+ # However, note that these options are ignored after an unsuccessful attempt
180
+ # to save a resource, e.g. when automatically rendering <tt>:new</tt>
181
+ # after a post request.
182
+ #
183
+ # Two additional options are relevant specifically to +respond_with+ -
184
+ # 1. <tt>:location</tt> - overwrites the default redirect location used after
185
+ # a successful html +post+ request.
186
+ # 2. <tt>:action</tt> - overwrites the default render action used after an
187
+ # unsuccessful html +post+ request.
188
+ def respond_with(*resources, &block)
189
+ if self.class.mimes_for_respond_to.empty?
190
+ raise "In order to use respond_with, first you need to declare the " \
191
+ "formats your controller responds to in the class level."
192
+ end
193
+
194
+ mimes = collect_mimes_from_class_level()
195
+ collector = ActionController::MimeResponds::Collector.new(mimes, request.variant)
196
+ block.call(collector) if block_given?
197
+
198
+ if format = collector.negotiate_format(request)
199
+ _process_format(format)
200
+ options = resources.size == 1 ? {} : resources.extract_options!
201
+ options = options.clone
202
+ options[:default_response] = collector.response
203
+ (options.delete(:responder) || self.class.responder).call(self, resources, options)
204
+ else
205
+ raise ActionController::UnknownFormat
206
+ end
207
+ end
208
+
209
+ protected
210
+
211
+ # Collect mimes declared in the class method respond_to valid for the
212
+ # current action.
213
+ def collect_mimes_from_class_level #:nodoc:
214
+ action = action_name.to_s
215
+
216
+ self.class.mimes_for_respond_to.keys.select do |mime|
217
+ config = self.class.mimes_for_respond_to[mime]
218
+
219
+ if config[:except]
220
+ !config[:except].include?(action)
221
+ elsif config[:only]
222
+ config[:only].include?(action)
223
+ else
224
+ true
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,302 @@
1
+ require 'active_support/json'
2
+
3
+ module ActionController #:nodoc:
4
+ # Responsible for exposing a resource to different mime requests,
5
+ # usually depending on the HTTP verb. The responder is triggered when
6
+ # <code>respond_with</code> is called. The simplest case to study is a GET request:
7
+ #
8
+ # class PeopleController < ApplicationController
9
+ # respond_to :html, :xml, :json
10
+ #
11
+ # def index
12
+ # @people = Person.all
13
+ # respond_with(@people)
14
+ # end
15
+ # end
16
+ #
17
+ # When a request comes in, for example for an XML response, three steps happen:
18
+ #
19
+ # 1) the responder searches for a template at people/index.xml;
20
+ #
21
+ # 2) if the template is not available, it will invoke <code>#to_xml</code> on the given resource;
22
+ #
23
+ # 3) if the responder does not <code>respond_to :to_xml</code>, call <code>#to_format</code> on it.
24
+ #
25
+ # === Built-in HTTP verb semantics
26
+ #
27
+ # The default \Rails responder holds semantics for each HTTP verb. Depending on the
28
+ # content type, verb and the resource status, it will behave differently.
29
+ #
30
+ # Using \Rails default responder, a POST request for creating an object could
31
+ # be written as:
32
+ #
33
+ # def create
34
+ # @user = User.new(params[:user])
35
+ # flash[:notice] = 'User was successfully created.' if @user.save
36
+ # respond_with(@user)
37
+ # end
38
+ #
39
+ # Which is exactly the same as:
40
+ #
41
+ # def create
42
+ # @user = User.new(params[:user])
43
+ #
44
+ # respond_to do |format|
45
+ # if @user.save
46
+ # flash[:notice] = 'User was successfully created.'
47
+ # format.html { redirect_to(@user) }
48
+ # format.xml { render xml: @user, status: :created, location: @user }
49
+ # else
50
+ # format.html { render action: "new" }
51
+ # format.xml { render xml: @user.errors, status: :unprocessable_entity }
52
+ # end
53
+ # end
54
+ # end
55
+ #
56
+ # The same happens for PATCH/PUT and DELETE requests.
57
+ #
58
+ # === Nested resources
59
+ #
60
+ # You can supply nested resources as you do in <code>form_for</code> and <code>polymorphic_url</code>.
61
+ # Consider the project has many tasks example. The create action for
62
+ # TasksController would be like:
63
+ #
64
+ # def create
65
+ # @project = Project.find(params[:project_id])
66
+ # @task = @project.tasks.build(params[:task])
67
+ # flash[:notice] = 'Task was successfully created.' if @task.save
68
+ # respond_with(@project, @task)
69
+ # end
70
+ #
71
+ # Giving several resources ensures that the responder will redirect to
72
+ # <code>project_task_url</code> instead of <code>task_url</code>.
73
+ #
74
+ # Namespaced and singleton resources require a symbol to be given, as in
75
+ # polymorphic urls. If a project has one manager which has many tasks, it
76
+ # should be invoked as:
77
+ #
78
+ # respond_with(@project, :manager, @task)
79
+ #
80
+ # Note that if you give an array, it will be treated as a collection,
81
+ # so the following is not equivalent:
82
+ #
83
+ # respond_with [@project, :manager, @task]
84
+ #
85
+ # === Custom options
86
+ #
87
+ # <code>respond_with</code> also allows you to pass options that are forwarded
88
+ # to the underlying render call. Those options are only applied for success
89
+ # scenarios. For instance, you can do the following in the create method above:
90
+ #
91
+ # def create
92
+ # @project = Project.find(params[:project_id])
93
+ # @task = @project.tasks.build(params[:task])
94
+ # flash[:notice] = 'Task was successfully created.' if @task.save
95
+ # respond_with(@project, @task, status: 201)
96
+ # end
97
+ #
98
+ # This will return status 201 if the task was saved successfully. If not,
99
+ # it will simply ignore the given options and return status 422 and the
100
+ # resource errors. You can also override the location to redirect to:
101
+ #
102
+ # respond_with(@project, location: root_path)
103
+ #
104
+ # To customize the failure scenario, you can pass a block to
105
+ # <code>respond_with</code>:
106
+ #
107
+ # def create
108
+ # @project = Project.find(params[:project_id])
109
+ # @task = @project.tasks.build(params[:task])
110
+ # respond_with(@project, @task, status: 201) do |format|
111
+ # if @task.save
112
+ # flash[:notice] = 'Task was successfully created.'
113
+ # else
114
+ # format.html { render "some_special_template" }
115
+ # end
116
+ # end
117
+ # end
118
+ #
119
+ # Using <code>respond_with</code> with a block follows the same syntax as <code>respond_to</code>.
120
+ class Responder
121
+ attr_reader :controller, :request, :format, :resource, :resources, :options
122
+
123
+ DEFAULT_ACTIONS_FOR_VERBS = {
124
+ :post => :new,
125
+ :patch => :edit,
126
+ :put => :edit
127
+ }
128
+
129
+ def initialize(controller, resources, options={})
130
+ @controller = controller
131
+ @request = @controller.request
132
+ @format = @controller.formats.first
133
+ @resource = resources.last
134
+ @resources = resources
135
+ @options = options
136
+ @action = options.delete(:action)
137
+ @default_response = options.delete(:default_response)
138
+
139
+ if options[:location].respond_to?(:call)
140
+ location = options.delete(:location)
141
+ options[:location] = location.call unless has_errors?
142
+ end
143
+ end
144
+
145
+ delegate :head, :render, :redirect_to, :to => :controller
146
+ delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request
147
+
148
+ # Undefine :to_json and :to_yaml since it's defined on Object
149
+ undef_method(:to_json) if method_defined?(:to_json)
150
+ undef_method(:to_yaml) if method_defined?(:to_yaml)
151
+
152
+ # Initializes a new responder and invokes the proper format. If the format is
153
+ # not defined, call to_format.
154
+ #
155
+ def self.call(*args)
156
+ new(*args).respond
157
+ end
158
+
159
+ # Main entry point for responder responsible to dispatch to the proper format.
160
+ #
161
+ def respond
162
+ method = "to_#{format}"
163
+ respond_to?(method) ? send(method) : to_format
164
+ end
165
+
166
+ # HTML format does not render the resource, it always attempt to render a
167
+ # template.
168
+ #
169
+ def to_html
170
+ default_render
171
+ rescue ActionView::MissingTemplate => e
172
+ navigation_behavior(e)
173
+ end
174
+
175
+ # to_js simply tries to render a template. If no template is found, raises the error.
176
+ def to_js
177
+ default_render
178
+ end
179
+
180
+ # All other formats follow the procedure below. First we try to render a
181
+ # template, if the template is not available, we verify if the resource
182
+ # responds to :to_format and display it.
183
+ #
184
+ def to_format
185
+ if get? || !has_errors? || response_overridden?
186
+ default_render
187
+ else
188
+ display_errors
189
+ end
190
+ rescue ActionView::MissingTemplate => e
191
+ api_behavior(e)
192
+ end
193
+
194
+ protected
195
+
196
+ # This is the common behavior for formats associated with browsing, like :html, :iphone and so forth.
197
+ def navigation_behavior(error)
198
+ if get?
199
+ raise error
200
+ elsif has_errors? && default_action
201
+ render :action => default_action
202
+ else
203
+ redirect_to navigation_location
204
+ end
205
+ end
206
+
207
+ # This is the common behavior for formats associated with APIs, such as :xml and :json.
208
+ def api_behavior(error)
209
+ raise error unless resourceful?
210
+ raise MissingRenderer.new(format) unless has_renderer?
211
+
212
+ if get?
213
+ display resource
214
+ elsif post?
215
+ display resource, :status => :created, :location => api_location
216
+ else
217
+ head :no_content
218
+ end
219
+ end
220
+
221
+ # Checks whether the resource responds to the current format or not.
222
+ #
223
+ def resourceful?
224
+ resource.respond_to?("to_#{format}")
225
+ end
226
+
227
+ # Returns the resource location by retrieving it from the options or
228
+ # returning the resources array.
229
+ #
230
+ def resource_location
231
+ options[:location] || resources
232
+ end
233
+ alias :navigation_location :resource_location
234
+ alias :api_location :resource_location
235
+
236
+ # If a response block was given, use it, otherwise call render on
237
+ # controller.
238
+ #
239
+ def default_render
240
+ if @default_response
241
+ @default_response.call(options)
242
+ else
243
+ controller.default_render(options)
244
+ end
245
+ end
246
+
247
+ # Display is just a shortcut to render a resource with the current format.
248
+ #
249
+ # display @user, status: :ok
250
+ #
251
+ # For XML requests it's equivalent to:
252
+ #
253
+ # render xml: @user, status: :ok
254
+ #
255
+ # Options sent by the user are also used:
256
+ #
257
+ # respond_with(@user, status: :created)
258
+ # display(@user, status: :ok)
259
+ #
260
+ # Results in:
261
+ #
262
+ # render xml: @user, status: :created
263
+ #
264
+ def display(resource, given_options={})
265
+ controller.render given_options.merge!(options).merge!(format => resource)
266
+ end
267
+
268
+ def display_errors
269
+ controller.render format => resource_errors, :status => :unprocessable_entity
270
+ end
271
+
272
+ # Check whether the resource has errors.
273
+ #
274
+ def has_errors?
275
+ resource.respond_to?(:errors) && !resource.errors.empty?
276
+ end
277
+
278
+ # Check whether the necessary Renderer is available
279
+ def has_renderer?
280
+ Renderers::RENDERERS.include?(format)
281
+ end
282
+
283
+ # By default, render the <code>:edit</code> action for HTML requests with errors, unless
284
+ # the verb was POST.
285
+ #
286
+ def default_action
287
+ @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol]
288
+ end
289
+
290
+ def resource_errors
291
+ respond_to?("#{format}_resource_errors", true) ? send("#{format}_resource_errors") : resource.errors
292
+ end
293
+
294
+ def json_resource_errors
295
+ {:errors => resource.errors}
296
+ end
297
+
298
+ def response_overridden?
299
+ @default_response.present?
300
+ end
301
+ end
302
+ end