josevalim-inherited_resources 0.8.5 → 0.9.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.
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require 'rake'
2
4
  require 'rake/testtask'
3
5
  require 'rake/rdoctask'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.5
1
+ 0.9.0
@@ -1,9 +1,14 @@
1
1
  # respond_to is the only file that should be loaded before hand. All others
2
2
  # are loaded on demand.
3
3
  #
4
- require File.join(File.dirname(__FILE__), 'inherited_resources', 'respond_to')
4
+ unless defined?(ActionController::Responder)
5
+ require File.join(File.dirname(__FILE__), 'inherited_resources', 'legacy', 'responder')
6
+ require File.join(File.dirname(__FILE__), 'inherited_resources', 'legacy', 'respond_to')
7
+ end
5
8
 
6
- module InheritedResources; end
9
+ module InheritedResources
10
+ ACTIONS = [ :index, :show, :new, :edit, :create, :update, :destroy ] unless self.const_defined?(:ACTIONS)
11
+ end
7
12
 
8
13
  class ActionController::Base
9
14
  # If you cannot inherit from InheritedResources::Base you can call
@@ -1,91 +1,69 @@
1
1
  module InheritedResources
2
- RESOURCES_ACTIONS = [ :index, :show, :new, :edit, :create, :update, :destroy ] unless self.const_defined?(:RESOURCES_ACTIONS)
3
-
4
2
  # Holds all default actions for InheritedResouces.
5
3
  module Actions
6
4
 
7
5
  # GET /resources
8
6
  def index(&block)
9
- respond_to(:with => collection, &block)
7
+ respond_with(collection, &block)
10
8
  end
11
9
  alias :index! :index
12
10
 
13
11
  # GET /resources/1
14
12
  def show(&block)
15
- respond_to(:with => resource, &block)
13
+ respond_with(resource, &block)
16
14
  end
17
15
  alias :show! :show
18
16
 
19
17
  # GET /resources/new
20
18
  def new(&block)
21
- respond_to(:with => build_resource, &block)
19
+ respond_with(build_resource, &block)
22
20
  end
23
21
  alias :new! :new
24
22
 
25
23
  # GET /resources/1/edit
26
24
  def edit(&block)
27
- respond_to(:with => resource, &block)
25
+ respond_with(resource, &block)
28
26
  end
29
27
  alias :edit! :edit
30
28
 
31
29
  # POST /resources
32
- def create(&block)
30
+ def create(options={}, &block)
33
31
  object = build_resource
34
- respond_block, redirect_block = select_block_by_arity(block)
35
32
 
36
33
  if object.save
37
34
  set_flash_message!(:notice, '{{resource_name}} was successfully created.')
38
- options = { :with => object, :status => :created, :location => (resource_url rescue nil) }
39
-
40
- respond_to_with_dual_blocks(true, respond_block, options) do |format|
41
- format.html { redirect_to(redirect_block ? redirect_block.call : resource_url) }
42
- end
35
+ options[:location] ||= resource_url rescue nil
36
+ respond_with_dual_blocks(object, options, true, block)
43
37
  else
44
38
  set_flash_message!(:error)
45
- options = { :with => object.errors, :status => :unprocessable_entity }
46
-
47
- respond_to_with_dual_blocks(false, respond_block, options) do |format|
48
- format.html { render :action => 'new' }
49
- end
39
+ respond_with_dual_blocks(object, options, false, block)
50
40
  end
51
41
  end
52
42
  alias :create! :create
53
43
 
54
44
  # PUT /resources/1
55
- def update(&block)
45
+ def update(options={}, &block)
56
46
  object = resource
57
- respond_block, redirect_block = select_block_by_arity(block)
58
47
 
59
48
  if object.update_attributes(params[resource_instance_name])
60
49
  set_flash_message!(:notice, '{{resource_name}} was successfully updated.')
61
-
62
- respond_to_with_dual_blocks(true, block) do |format|
63
- format.html { redirect_to(redirect_block ? redirect_block.call : resource_url) }
64
- format.all { head :ok }
65
- end
50
+ options[:location] ||= resource_url rescue nil
51
+ respond_with_dual_blocks(object, options, true, block)
66
52
  else
67
53
  set_flash_message!(:error)
68
-
69
- options = { :with => object.errors, :status => :unprocessable_entity }
70
-
71
- respond_to_with_dual_blocks(false, block, options) do |format|
72
- format.html { render :action => 'edit' }
73
- end
54
+ respond_with_dual_blocks(object, options, false, block)
74
55
  end
75
56
  end
76
57
  alias :update! :update
77
58
 
78
59
  # DELETE /resources/1
79
- def destroy(&block)
80
- resource.destroy
81
- respond_block, redirect_block = select_block_by_arity(block)
60
+ def destroy(options={}, &block)
61
+ object = resource
62
+ object.destroy
82
63
 
83
64
  set_flash_message!(:notice, '{{resource_name}} was successfully destroyed.')
84
-
85
- respond_to_with_dual_blocks(nil, respond_block) do |format|
86
- format.html { redirect_to(redirect_block ? redirect_block.call : collection_url) }
87
- format.all { head :ok }
88
- end
65
+ options[:location] ||= collection_url rescue nil
66
+ respond_with_dual_blocks(object, options, nil, block)
89
67
  end
90
68
  alias :destroy! :destroy
91
69
 
@@ -20,6 +20,9 @@ module InheritedResources
20
20
  extend InheritedResources::ClassMethods
21
21
  extend InheritedResources::UrlHelpers
22
22
 
23
+ # Add at least :html mime type
24
+ respond_to :html
25
+
23
26
  helper_method :collection_url, :collection_path, :resource_url, :resource_path,
24
27
  :new_resource_url, :new_resource_path, :edit_resource_url, :edit_resource_path,
25
28
  :resource, :collection, :resource_class
@@ -46,7 +46,7 @@ module InheritedResources
46
46
  # instance variable.
47
47
  #
48
48
  def build_resource
49
- get_resource_ivar || set_resource_ivar(end_of_association_chain.send(method_for_build, params[resource_instance_name]))
49
+ get_resource_ivar || set_resource_ivar(end_of_association_chain.send(method_for_build, params[resource_instance_name] || {}))
50
50
  end
51
51
 
52
52
  # This class allows you to set a instance variable to begin your
@@ -271,26 +271,34 @@ module InheritedResources
271
271
  # end
272
272
  # end
273
273
  #
274
- def respond_to_with_dual_blocks(success, dual_block, options={}, &block) #:nodoc:
275
- responder = ActionController::MimeResponds::Responder.new(self)
276
-
277
- if dual_block
278
- if dual_block.arity == 2
279
- dumb_responder = InheritedResources::DumbResponder.new
280
- if success
281
- dual_block.call(responder, dumb_responder)
274
+ # It also calculates the response url in case a block without arity is
275
+ # given and returns it. Otherwise returns nil.
276
+ #
277
+ def respond_with_dual_blocks(object, options, success, given_block, &block) #:nodoc:
278
+ case given_block.try(:arity)
279
+ when 2
280
+ respond_with(object, options) do |responder|
281
+ dumb_responder = InheritedResources::DumbResponder.new
282
+ if success
283
+ given_block.call(responder, dumb_responder)
284
+ else
285
+ given_block.call(dumb_responder, responder)
286
+ end
287
+ block.try(:call, responder)
288
+ end
289
+ when 1
290
+ if block
291
+ respond_with(object, options) do |responder|
292
+ given_block.call(responder)
293
+ block.call(responder)
294
+ end
282
295
  else
283
- dual_block.call(dumb_responder, responder)
296
+ respond_with(object, options, &given_block)
284
297
  end
285
298
  else
286
- dual_block.call(responder)
287
- end
288
-
289
- # Try to respond with the block given
290
- responder.respond_except_any
299
+ options[:location] = given_block.call if given_block
300
+ respond_with(object, options, &block)
291
301
  end
292
-
293
- respond_to(options.merge!(:responder => responder, :prioritize => :html), &block) unless performed?
294
302
  end
295
303
 
296
304
  # Hook to apply scopes. By default returns only the target_object given.
@@ -307,23 +315,5 @@ module InheritedResources
307
315
  []
308
316
  end
309
317
 
310
- # Holds InheritedResources block structure. It returns two blocks: the first
311
- # is used in respond_to blocks and the second is the redirect_to url.
312
- #
313
- def select_block_by_arity(block) #:nodoc
314
- if block
315
- case block.arity
316
- when 2, 1
317
- [block, nil]
318
- when 0, -1
319
- [nil, block]
320
- else
321
- raise ScriptError, "InheritedResources does not know how to handle blocks with arity #{block.arity}"
322
- end
323
- else
324
- [nil, nil]
325
- end
326
- end
327
-
328
318
  end
329
319
  end
@@ -68,7 +68,7 @@ module InheritedResources
68
68
  actions_to_remove = Array(options[:except])
69
69
  actions_to_remove.map!{ |a| a.to_s }
70
70
 
71
- actions_to_remove += RESOURCES_ACTIONS.map{|a| a.to_s } - actions_to_keep unless actions_to_keep.first == 'all'
71
+ actions_to_remove += ACTIONS.map{ |a| a.to_s } - actions_to_keep unless actions_to_keep.first == 'all'
72
72
  actions_to_remove.uniq!
73
73
 
74
74
  (instance_methods & actions_to_remove).each do |action|
@@ -108,18 +108,18 @@ module InheritedResources
108
108
  #
109
109
  # == Options
110
110
  #
111
- # * <tt>:boolean</tt> - When set to true, call the scope only when the params is true or 1,
111
+ # * <tt>:boolean</tt> - When set to true, call the scope only when the param is true or 1,
112
112
  # and does not send the value as argument.
113
113
  #
114
- # * <tt>:only</tt> - In each actions the scope is applied. By default is :all.
114
+ # * <tt>:only</tt> - In which actions the scope is applied. By default is :all.
115
115
  #
116
- # * <tt>:except</tt> - In each actions the scope is not applied. By default is :none.
116
+ # * <tt>:except</tt> - In which actions the scope is not applied. By default is :none.
117
117
  #
118
118
  # * <tt>:key</tt> - The key in the params hash expected to find the scope.
119
119
  # Defaults to the scope name.
120
120
  #
121
121
  # * <tt>:default</tt> - Default value for the scope. Whenever supplied the scope
122
- # is always called. This is useful to add easy pagination!
122
+ # is always called. This is useful to add easy pagination.
123
123
  #
124
124
  def has_scope(*scopes)
125
125
  options = scopes.extract_options!
@@ -208,6 +208,7 @@ module InheritedResources
208
208
  optional = options.delete(:optional)
209
209
  singleton = options.delete(:singleton)
210
210
  polymorphic = options.delete(:polymorphic)
211
+ finder = options.delete(:finder)
211
212
 
212
213
  include BelongsToHelpers if self.parents_symbols.empty?
213
214
 
@@ -234,8 +235,8 @@ module InheritedResources
234
235
  config[:collection_name] = options.delete(:collection_name) || symbol.to_s.pluralize.to_sym
235
236
  config[:instance_name] = options.delete(:instance_name) || symbol
236
237
  config[:param] = options.delete(:param) || :"#{symbol}_id"
237
- config[:finder] = options.delete(:finder) || :find
238
238
  config[:route_name] = options.delete(:route_name) || symbol
239
+ config[:finder] = finder || :find
239
240
  end
240
241
 
241
242
  if block_given?
@@ -0,0 +1,151 @@
1
+ module ActionController #:nodoc:
2
+ class Base #:nodoc:
3
+ attr_accessor :formats
4
+
5
+ # Defines mimes that are rendered by default when invoking respond_with.
6
+ #
7
+ # Examples:
8
+ #
9
+ # respond_to :html, :xml, :json
10
+ #
11
+ # All actions on your controller will respond to :html, :xml and :json.
12
+ #
13
+ # But if you want to specify it based on your actions, you can use only and
14
+ # except:
15
+ #
16
+ # respond_to :html
17
+ # respond_to :xml, :json, :except => [ :edit ]
18
+ #
19
+ # The definition above explicits that all actions respond to :html. And all
20
+ # actions except :edit respond to :xml and :json.
21
+ #
22
+ # You can specify also only parameters:
23
+ #
24
+ # respond_to :rjs, :only => :create
25
+ #
26
+ def self.respond_to(*mimes)
27
+ options = mimes.extract_options!
28
+
29
+ only_actions = Array(options.delete(:only))
30
+ except_actions = Array(options.delete(:except))
31
+
32
+ mimes.each do |mime|
33
+ mime = mime.to_sym
34
+ mimes_for_respond_to[mime] = {}
35
+ mimes_for_respond_to[mime][:only] = only_actions unless only_actions.empty?
36
+ mimes_for_respond_to[mime][:except] = except_actions unless except_actions.empty?
37
+ end
38
+ end
39
+
40
+ # Clear all mimes in respond_to.
41
+ #
42
+ def self.clear_respond_to
43
+ write_inheritable_attribute(:mimes_for_respond_to, ActiveSupport::OrderedHash.new)
44
+ end
45
+
46
+ class_inheritable_reader :mimes_for_respond_to
47
+ clear_respond_to
48
+
49
+ # If ApplicationController is already defined around here, we have to set
50
+ # mimes_for_respond_to hash as well.
51
+ #
52
+ ApplicationController.clear_respond_to if defined?(ApplicationController)
53
+
54
+ def respond_to(*mimes, &block)
55
+ raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
56
+
57
+ responder = ActionController::MimeResponds::Responder.new(self)
58
+ mimes = collect_mimes_from_class_level if mimes.empty?
59
+ mimes.each { |mime| responder.send(mime) }
60
+ block.call(responder) if block_given?
61
+
62
+ if format = responder.negotiate_mime
63
+ self.response.template.template_format = format.to_sym
64
+ self.response.content_type = format.to_s
65
+ self.formats = [ format.to_sym ]
66
+
67
+ if response = responder.response_for(format)
68
+ response.call
69
+ else
70
+ default_render
71
+ end
72
+ else
73
+ head :not_acceptable
74
+ end
75
+ end
76
+
77
+ def respond_with(*resources, &block)
78
+ respond_to(&block)
79
+ rescue ActionView::MissingTemplate
80
+ options = resources.extract_options!
81
+ (options.delete(:responder) || responder).call(self, resources, options)
82
+ end
83
+
84
+ def responder
85
+ ActionController::Responder
86
+ end
87
+
88
+ protected
89
+
90
+ # Collect mimes declared in the class method respond_to valid for the
91
+ # current action.
92
+ #
93
+ def collect_mimes_from_class_level #:nodoc:
94
+ action = action_name.to_sym
95
+
96
+ mimes_for_respond_to.keys.select do |mime|
97
+ config = mimes_for_respond_to[mime]
98
+
99
+ if config[:except]
100
+ !config[:except].include?(action)
101
+ elsif config[:only]
102
+ config[:only].include?(action)
103
+ else
104
+ true
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ module MimeResponds
111
+ class Responder #:nodoc:
112
+ attr_reader :order
113
+
114
+ def any(*args, &block)
115
+ if args.any?
116
+ args.each { |type| send(type, &block) }
117
+ else
118
+ custom(Mime::ALL, &block)
119
+ end
120
+ end
121
+ alias :all :any
122
+
123
+ def custom(mime_type, &block)
124
+ mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
125
+ @order << mime_type
126
+ @responses[mime_type] ||= block
127
+ end
128
+
129
+ def response_for(mime)
130
+ @responses[mime] || @responses[Mime::ALL]
131
+ end
132
+
133
+ def negotiate_mime
134
+ @mime_type_priority.each do |priority|
135
+ if priority == Mime::ALL
136
+ return @order.first
137
+ elsif @order.include?(priority)
138
+ return priority
139
+ end
140
+ end
141
+
142
+ if @order.include?(Mime::ALL)
143
+ return Mime::SET.first if @mime_type_priority.first == Mime::ALL
144
+ return @mime_type_priority.first
145
+ end
146
+
147
+ nil
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,181 @@
1
+ module ActionController #:nodoc:
2
+ # Responder is responsible to expose a resource for different mime requests,
3
+ # usually depending on the HTTP verb. The responder is triggered when
4
+ # respond_with is called. The simplest case to study is a GET request:
5
+ #
6
+ # class PeopleController < ApplicationController
7
+ # respond_to :html, :xml, :json
8
+ #
9
+ # def index
10
+ # @people = Person.find(:all)
11
+ # respond_with(@people)
12
+ # end
13
+ # end
14
+ #
15
+ # When a request comes, for example with format :xml, three steps happen:
16
+ #
17
+ # 1) respond_with searches for a template at people/index.xml;
18
+ #
19
+ # 2) if the template is not available, it will create a responder, passing
20
+ # the controller and the resource and invoke :to_xml on it;
21
+ #
22
+ # 3) if the responder does not respond_to :to_xml, call to_format on it.
23
+ #
24
+ # === Builtin HTTP verb semantics
25
+ #
26
+ # Rails default responder holds semantics for each HTTP verb. Depending on the
27
+ # content type, verb and the resource status, it will behave differently.
28
+ #
29
+ # Using Rails default responder, a POST request for creating an object could
30
+ # be written as:
31
+ #
32
+ # def create
33
+ # @user = User.new(params[:user])
34
+ # flash[:notice] = 'User was successfully created.' if @user.save
35
+ # respond_with(@user)
36
+ # end
37
+ #
38
+ # Which is exactly the same as:
39
+ #
40
+ # def create
41
+ # @user = User.new(params[:user])
42
+ #
43
+ # respond_to do |format|
44
+ # if @user.save
45
+ # flash[:notice] = 'User was successfully created.'
46
+ # format.html { redirect_to(@user) }
47
+ # format.xml { render :xml => @user, :status => :created, :location => @user }
48
+ # else
49
+ # format.html { render :action => "new" }
50
+ # format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
51
+ # end
52
+ # end
53
+ # end
54
+ #
55
+ # The same happens for PUT and DELETE requests.
56
+ #
57
+ # === Nested resources
58
+ #
59
+ # You can given nested resource as you do in form_for and polymorphic_url.
60
+ # Consider the project has many tasks example. The create action for
61
+ # TasksController would be like:
62
+ #
63
+ # def create
64
+ # @project = Project.find(params[:project_id])
65
+ # @task = @project.comments.build(params[:task])
66
+ # flash[:notice] = 'Task was successfully created.' if @task.save
67
+ # respond_with(@project, @task)
68
+ # end
69
+ #
70
+ # Giving an array of resources, you ensure that the responder will redirect to
71
+ # project_task_url instead of task_url.
72
+ #
73
+ # Namespaced and singleton resources requires a symbol to be given, as in
74
+ # polymorphic urls. If a project has one manager which has many tasks, it
75
+ # should be invoked as:
76
+ #
77
+ # respond_with(@project, :manager, @task)
78
+ #
79
+ # Check polymorphic_url documentation for more examples.
80
+ #
81
+ class Responder
82
+ attr_reader :controller, :request, :format, :resource, :resource_location, :options
83
+
84
+ def initialize(controller, resources, options={})
85
+ @controller = controller
86
+ @request = controller.request
87
+ @format = controller.formats.first
88
+ @resource = resources.is_a?(Array) ? resources.last : resources
89
+ @resource_location = options[:location] || resources
90
+ @options = options
91
+ end
92
+
93
+ delegate :head, :render, :redirect_to, :to => :controller
94
+ delegate :get?, :post?, :put?, :delete?, :to => :request
95
+
96
+ # Undefine :to_json since it's defined on Object
97
+ undef_method :to_json
98
+
99
+ # Initializes a new responder an invoke the proper format. If the format is
100
+ # not defined, call to_format.
101
+ #
102
+ def self.call(*args)
103
+ responder = new(*args)
104
+ method = :"to_#{responder.format}"
105
+ responder.respond_to?(method) ? responder.send(method) : responder.to_format
106
+ end
107
+
108
+ # HTML format does not render the resource, it always attempt to render a
109
+ # template.
110
+ #
111
+ def to_html
112
+ if get?
113
+ render
114
+ elsif has_errors?
115
+ render :action => default_action
116
+ else
117
+ redirect_to resource_location
118
+ end
119
+ end
120
+
121
+ # All others formats try to render the resource given instead. For this
122
+ # purpose a helper called display as a shortcut to render a resource with
123
+ # the current format.
124
+ #
125
+ def to_format
126
+ return render unless resourceful?
127
+
128
+ if get?
129
+ display resource
130
+ elsif has_errors?
131
+ display resource.errors, :status => :unprocessable_entity
132
+ elsif post?
133
+ display resource, :status => :created, :location => resource_location
134
+ else
135
+ head :ok
136
+ end
137
+ end
138
+
139
+ protected
140
+
141
+ # Checks whether the resource responds to the current format or not.
142
+ #
143
+ def resourceful?
144
+ resource.respond_to?(:"to_#{format}")
145
+ end
146
+
147
+ # display is just a shortcut to render a resource with the current format.
148
+ #
149
+ # display @user, :status => :ok
150
+ #
151
+ # For xml request is equivalent to:
152
+ #
153
+ # render :xml => @user, :status => :ok
154
+ #
155
+ # Options sent by the user are also used:
156
+ #
157
+ # respond_with(@user, :status => :created)
158
+ # display(@user, :status => :ok)
159
+ #
160
+ # Results in:
161
+ #
162
+ # render :xml => @user, :status => :created
163
+ #
164
+ def display(resource, given_options={})
165
+ render given_options.merge!(options).merge!(format => resource)
166
+ end
167
+
168
+ # Check if the resource has errors or not.
169
+ #
170
+ def has_errors?
171
+ resource.respond_to?(:errors) && !resource.errors.empty?
172
+ end
173
+
174
+ # By default, render the :edit action for html requests with failure, unless
175
+ # the verb is post.
176
+ #
177
+ def default_action
178
+ request.post? ? :new : :edit
179
+ end
180
+ end
181
+ end