responders 1.1.2 → 2.0.0

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