josevalim-inherited_resources 0.8.5 → 0.9.0

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