inherited_resources 0.9.2

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.
Files changed (37) hide show
  1. data/CHANGELOG +103 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +524 -0
  4. data/Rakefile +40 -0
  5. data/lib/inherited_resources.rb +23 -0
  6. data/lib/inherited_resources/actions.rb +79 -0
  7. data/lib/inherited_resources/base.rb +42 -0
  8. data/lib/inherited_resources/base_helpers.rb +363 -0
  9. data/lib/inherited_resources/belongs_to_helpers.rb +89 -0
  10. data/lib/inherited_resources/class_methods.rb +338 -0
  11. data/lib/inherited_resources/dsl.rb +26 -0
  12. data/lib/inherited_resources/dumb_responder.rb +20 -0
  13. data/lib/inherited_resources/has_scope_helpers.rb +83 -0
  14. data/lib/inherited_resources/legacy/respond_to.rb +156 -0
  15. data/lib/inherited_resources/legacy/responder.rb +200 -0
  16. data/lib/inherited_resources/polymorphic_helpers.rb +155 -0
  17. data/lib/inherited_resources/singleton_helpers.rb +95 -0
  18. data/lib/inherited_resources/url_helpers.rb +179 -0
  19. data/test/aliases_test.rb +139 -0
  20. data/test/association_chain_test.rb +125 -0
  21. data/test/base_test.rb +225 -0
  22. data/test/belongs_to_test.rb +87 -0
  23. data/test/class_methods_test.rb +138 -0
  24. data/test/customized_base_test.rb +162 -0
  25. data/test/customized_belongs_to_test.rb +76 -0
  26. data/test/defaults_test.rb +70 -0
  27. data/test/flash_test.rb +88 -0
  28. data/test/has_scope_test.rb +139 -0
  29. data/test/nested_belongs_to_test.rb +108 -0
  30. data/test/optional_belongs_to_test.rb +164 -0
  31. data/test/polymorphic_test.rb +186 -0
  32. data/test/redirect_to_test.rb +51 -0
  33. data/test/respond_to_test.rb +155 -0
  34. data/test/singleton_test.rb +83 -0
  35. data/test/test_helper.rb +38 -0
  36. data/test/url_helpers_test.rb +537 -0
  37. metadata +89 -0
@@ -0,0 +1,20 @@
1
+ module InheritedResources
2
+ # = Dumb Responder
3
+ #
4
+ # This responder discards all messages sent to him.
5
+ #
6
+ class DumbResponder
7
+
8
+ instance_methods.each do |m|
9
+ undef_method m unless m =~ /^(__|object_id)/
10
+ end
11
+
12
+ # This is like a good husband, he will just listen everything that his wife
13
+ # says (which is a lot) without complaining. :)
14
+ #
15
+ def method_missing(*args)
16
+ nil
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,83 @@
1
+ module InheritedResources
2
+
3
+ # = has_scopes
4
+ #
5
+ # This module in included in your controller when has_scope is called for the
6
+ # first time.
7
+ #
8
+ module HasScopeHelpers
9
+ TRUE_VALUES = ["true", true, "1", 1] unless self.const_defined?(:TRUE_VALUES)
10
+
11
+ protected
12
+
13
+ # Overwrites apply to scope to implement default scope logic.
14
+ #
15
+ def apply_scope_to(target_object) #:nodoc:
16
+ @current_scopes ||= {}
17
+
18
+ self.scopes_configuration.each do |scope, options|
19
+ next unless apply_scope_to_action?(options)
20
+ key = options[:as]
21
+
22
+ if params.key?(key)
23
+ value, call_scope = params[key], true
24
+ elsif options.key?(:default)
25
+ value, call_scope = options[:default], true
26
+ value = value.call(self) if value.is_a?(Proc)
27
+ end
28
+
29
+ if call_scope
30
+ @current_scopes[key] = value
31
+
32
+ if options[:boolean]
33
+ target_object = target_object.send(scope) if TRUE_VALUES.include?(value)
34
+ else
35
+ target_object = target_object.send(scope, value)
36
+ end
37
+ end
38
+ end
39
+
40
+ target_object
41
+ end
42
+
43
+ # Given an options with :only and :except arrays, check if the scope
44
+ # can be performed in the current action.
45
+ #
46
+ def apply_scope_to_action?(options) #:nodoc:
47
+ return false unless applicable?(options[:if], true)
48
+ return false unless applicable?(options[:unless], false)
49
+ if options[:only].empty?
50
+ if options[:except].empty?
51
+ true
52
+ else
53
+ !options[:except].include?(action_name.to_sym)
54
+ end
55
+ else
56
+ options[:only].include?(action_name.to_sym)
57
+ end
58
+ end
59
+
60
+ # Evaluates the scope options :if or :unless. Returns true if the proc
61
+ # method, or string evals to the expected value.
62
+ #
63
+ def applicable?(string_proc_or_symbol, expected)
64
+ case string_proc_or_symbol
65
+ when String
66
+ eval(string_proc_or_symbol) == expected
67
+ when Proc
68
+ string_proc_or_symbol.call(self) == expected
69
+ when Symbol
70
+ send(string_proc_or_symbol) == expected
71
+ else
72
+ true
73
+ end
74
+ end
75
+
76
+ # Returns the scopes used in this action.
77
+ #
78
+ def current_scopes
79
+ @current_scopes || {}
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,156 @@
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
+ if response = retrieve_response_from_mimes(mimes, &block)
57
+ response.call
58
+ end
59
+ end
60
+
61
+ def respond_with(*resources, &block)
62
+ if response = retrieve_response_from_mimes([], &block)
63
+ options = resources.extract_options!
64
+ options.merge!(:default_response => response)
65
+ (options.delete(:responder) || responder).call(self, resources, options)
66
+ end
67
+ end
68
+
69
+ def responder
70
+ ActionController::Responder
71
+ end
72
+
73
+ protected
74
+
75
+ # Collect mimes declared in the class method respond_to valid for the
76
+ # current action.
77
+ #
78
+ def collect_mimes_from_class_level #:nodoc:
79
+ action = action_name.to_sym
80
+
81
+ mimes_for_respond_to.keys.select do |mime|
82
+ config = mimes_for_respond_to[mime]
83
+
84
+ if config[:except]
85
+ !config[:except].include?(action)
86
+ elsif config[:only]
87
+ config[:only].include?(action)
88
+ else
89
+ true
90
+ end
91
+ end
92
+ end
93
+
94
+ # Collects mimes and return the response for the negotiated format. Returns
95
+ # nil if :not_acceptable was sent to the client.
96
+ #
97
+ def retrieve_response_from_mimes(mimes, &block)
98
+ responder = ActionController::MimeResponds::Responder.new(self)
99
+ mimes = collect_mimes_from_class_level if mimes.empty?
100
+ mimes.each { |mime| responder.send(mime) }
101
+ block.call(responder) if block_given?
102
+
103
+ if format = responder.negotiate_mime
104
+ self.response.template.template_format = format.to_sym
105
+ self.response.content_type = format.to_s
106
+ self.formats = [ format.to_sym ]
107
+ responder.response_for(format) || proc { default_render }
108
+ else
109
+ head :not_acceptable
110
+ nil
111
+ end
112
+ end
113
+ end
114
+
115
+ module MimeResponds
116
+ class Responder #:nodoc:
117
+ attr_reader :order
118
+
119
+ def any(*args, &block)
120
+ if args.any?
121
+ args.each { |type| send(type, &block) }
122
+ else
123
+ custom(Mime::ALL, &block)
124
+ end
125
+ end
126
+ alias :all :any
127
+
128
+ def custom(mime_type, &block)
129
+ mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
130
+ @order << mime_type
131
+ @responses[mime_type] ||= block
132
+ end
133
+
134
+ def response_for(mime)
135
+ @responses[mime] || @responses[Mime::ALL]
136
+ end
137
+
138
+ def negotiate_mime
139
+ @mime_type_priority.each do |priority|
140
+ if priority == Mime::ALL
141
+ return @order.first
142
+ elsif @order.include?(priority)
143
+ return priority
144
+ end
145
+ end
146
+
147
+ if @order.include?(Mime::ALL)
148
+ return Mime::SET.first if @mime_type_priority.first == Mime::ALL
149
+ return @mime_type_priority.first
150
+ end
151
+
152
+ nil
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,200 @@
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, :resources, :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
+ @resources = resources
90
+ @options = options
91
+ @default_response = options.delete(:default_response)
92
+ end
93
+
94
+ delegate :head, :render, :redirect_to, :to => :controller
95
+ delegate :get?, :post?, :put?, :delete?, :to => :request
96
+
97
+ # Undefine :to_json since it's defined on Object
98
+ undef_method :to_json
99
+
100
+ # Initializes a new responder an invoke the proper format. If the format is
101
+ # not defined, call to_format.
102
+ #
103
+ def self.call(*args)
104
+ responder = new(*args)
105
+ method = :"to_#{responder.format}"
106
+ responder.respond_to?(method) ? responder.send(method) : responder.to_format
107
+ end
108
+
109
+ # HTML format does not render the resource, it always attempt to render a
110
+ # template.
111
+ #
112
+ def to_html
113
+ default_render
114
+ rescue ActionView::MissingTemplate
115
+ if get?
116
+ raise
117
+ elsif has_errors?
118
+ render :action => default_action
119
+ else
120
+ redirect_to resource_location
121
+ end
122
+ end
123
+
124
+ # All others formats follow the procedure below. First we try to render a
125
+ # template, if the template is not available, we verify if the resource
126
+ # responds to :to_format and display it.
127
+ #
128
+ def to_format
129
+ default_render
130
+ rescue ActionView::MissingTemplate
131
+ raise unless resourceful?
132
+
133
+ if get?
134
+ display resource
135
+ elsif has_errors?
136
+ display resource.errors, :status => :unprocessable_entity
137
+ elsif post?
138
+ display resource, :status => :created, :location => resource_location
139
+ else
140
+ head :ok
141
+ end
142
+ end
143
+
144
+ protected
145
+
146
+ # Checks whether the resource responds to the current format or not.
147
+ #
148
+ def resourceful?
149
+ resource.respond_to?(:"to_#{format}")
150
+ end
151
+
152
+ # Returns the resource location by retrieving it from the options or
153
+ # returning the resources array.
154
+ #
155
+ def resource_location
156
+ options[:location] || resources
157
+ end
158
+
159
+ # If a given response block was given, use it, otherwise call render on
160
+ # controller.
161
+ #
162
+ def default_render
163
+ @default_response.call
164
+ end
165
+
166
+ # display is just a shortcut to render a resource with the current format.
167
+ #
168
+ # display @user, :status => :ok
169
+ #
170
+ # For xml request is equivalent to:
171
+ #
172
+ # render :xml => @user, :status => :ok
173
+ #
174
+ # Options sent by the user are also used:
175
+ #
176
+ # respond_with(@user, :status => :created)
177
+ # display(@user, :status => :ok)
178
+ #
179
+ # Results in:
180
+ #
181
+ # render :xml => @user, :status => :created
182
+ #
183
+ def display(resource, given_options={})
184
+ render given_options.merge!(options).merge!(format => resource)
185
+ end
186
+
187
+ # Check if the resource has errors or not.
188
+ #
189
+ def has_errors?
190
+ resource.respond_to?(:errors) && !resource.errors.empty?
191
+ end
192
+
193
+ # By default, render the :edit action for html requests with failure, unless
194
+ # the verb is post.
195
+ #
196
+ def default_action
197
+ request.post? ? :new : :edit
198
+ end
199
+ end
200
+ end