inherited_resources 0.9.5 → 1.0.pre
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/CHANGELOG +7 -2
- data/README.rdoc +40 -113
- data/Rakefile +3 -1
- data/init.rb +1 -0
- data/lib/inherited_resources.rb +13 -4
- data/lib/inherited_resources/actions.rb +10 -22
- data/lib/inherited_resources/base.rb +3 -2
- data/lib/inherited_resources/base_helpers.rb +22 -115
- data/lib/inherited_resources/belongs_to_helpers.rb +8 -0
- data/lib/inherited_resources/blank_slate.rb +12 -0
- data/lib/inherited_resources/class_methods.rb +8 -84
- data/lib/inherited_resources/legacy/respond_to.rb +11 -13
- data/lib/inherited_resources/legacy/responder.rb +43 -23
- data/lib/inherited_resources/locales/en.yml +10 -0
- data/lib/inherited_resources/responder.rb +6 -0
- data/lib/inherited_resources/version.rb +1 -1
- data/test/aliases_test.rb +8 -3
- data/test/base_test.rb +25 -4
- data/test/class_methods_test.rb +1 -19
- data/test/customized_base_test.rb +10 -4
- metadata +28 -9
- data/lib/inherited_resources/dumb_responder.rb +0 -20
- data/lib/inherited_resources/has_scope_helpers.rb +0 -83
- data/test/flash_test.rb +0 -88
- data/test/has_scope_test.rb +0 -139
@@ -71,86 +71,6 @@ module InheritedResources
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
-
# Detects params from url and apply as scopes to your classes.
|
75
|
-
#
|
76
|
-
# Your model:
|
77
|
-
#
|
78
|
-
# class Graduation < ActiveRecord::Base
|
79
|
-
# named_scope :featured, :conditions => { :featured => true }
|
80
|
-
# named_scope :by_degree, proc {|degree| { :conditions => { :degree => degree } } }
|
81
|
-
# end
|
82
|
-
#
|
83
|
-
# Your controller:
|
84
|
-
#
|
85
|
-
# class GraduationsController < InheritedResources::Base
|
86
|
-
# has_scope :featured, :boolean => true, :only => :index
|
87
|
-
# has_scope :by_degree, :only => :index
|
88
|
-
# end
|
89
|
-
#
|
90
|
-
# Then for each request:
|
91
|
-
#
|
92
|
-
# /graduations
|
93
|
-
# #=> acts like a normal request
|
94
|
-
#
|
95
|
-
# /graduations?featured=true
|
96
|
-
# #=> calls the named scope and bring featured graduations
|
97
|
-
#
|
98
|
-
# /graduations?featured=true&by_degree=phd
|
99
|
-
# #=> brings featured graduations with phd degree
|
100
|
-
#
|
101
|
-
# You can retrieve the current scopes in use with <tt>current_scopes</tt>
|
102
|
-
# method. In the last case, it would return: { :featured => "true", :by_degree => "phd" }
|
103
|
-
#
|
104
|
-
# == Options
|
105
|
-
#
|
106
|
-
# * <tt>:boolean</tt> - When set to true, call the scope only when the param is true or 1,
|
107
|
-
# and does not send the value as argument.
|
108
|
-
#
|
109
|
-
# * <tt>:only</tt> - In which actions the scope is applied. By default is :all.
|
110
|
-
#
|
111
|
-
# * <tt>:except</tt> - In which actions the scope is not applied. By default is :none.
|
112
|
-
#
|
113
|
-
# * <tt>:as</tt> - The key in the params hash expected to find the scope.
|
114
|
-
# Defaults to the scope name.
|
115
|
-
#
|
116
|
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
117
|
-
# if the scope should apply
|
118
|
-
#
|
119
|
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
|
120
|
-
# if the scope should NOT apply.
|
121
|
-
#
|
122
|
-
# * <tt>:default</tt> - Default value for the scope. Whenever supplied the scope
|
123
|
-
# is always called. This is useful to add easy pagination.
|
124
|
-
#
|
125
|
-
def has_scope(*scopes)
|
126
|
-
options = scopes.extract_options!
|
127
|
-
|
128
|
-
options.symbolize_keys!
|
129
|
-
options.assert_valid_keys(:boolean, :key, :only, :except,
|
130
|
-
:if, :unless, :default, :as)
|
131
|
-
|
132
|
-
if options[:key]
|
133
|
-
ActiveSupport::Deprecation.warn "has_scope :key is deprecated, use :as instead"
|
134
|
-
options[:as] ||= options[:key]
|
135
|
-
end
|
136
|
-
|
137
|
-
if self.scopes_configuration.empty?
|
138
|
-
include HasScopeHelpers
|
139
|
-
helper_method :current_scopes
|
140
|
-
end
|
141
|
-
|
142
|
-
scopes.each do |scope|
|
143
|
-
self.scopes_configuration[scope] ||= {}
|
144
|
-
self.scopes_configuration[scope][:as] = options[:as] || scope
|
145
|
-
self.scopes_configuration[scope][:only] = Array(options[:only])
|
146
|
-
self.scopes_configuration[scope][:except] = Array(options[:except])
|
147
|
-
|
148
|
-
[:if, :unless, :boolean, :default].each do |opt|
|
149
|
-
self.scopes_configuration[scope][opt] = options[opt] if options.key?(opt)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
74
|
# Defines that this controller belongs to another resource.
|
155
75
|
#
|
156
76
|
# belongs_to :projects
|
@@ -239,8 +159,13 @@ module InheritedResources
|
|
239
159
|
end
|
240
160
|
|
241
161
|
config = self.resources_configuration[symbol] = {}
|
242
|
-
|
243
|
-
config[:parent_class]
|
162
|
+
|
163
|
+
config[:parent_class] = options.delete(:parent_class) || begin
|
164
|
+
(options.delete(:class_name) || symbol).to_s.pluralize.classify.constantize
|
165
|
+
rescue NameError
|
166
|
+
nil
|
167
|
+
end
|
168
|
+
|
244
169
|
config[:collection_name] = options.delete(:collection_name) || symbol.to_s.pluralize.to_sym
|
245
170
|
config[:instance_name] = options.delete(:instance_name) || symbol
|
246
171
|
config[:param] = options.delete(:param) || :"#{symbol}_id"
|
@@ -321,8 +246,7 @@ module InheritedResources
|
|
321
246
|
config[:route_prefix] = namespaces.join('_') unless namespaces.empty?
|
322
247
|
|
323
248
|
# Initialize polymorphic, singleton, scopes and belongs_to parameters
|
324
|
-
self.parents_symbols
|
325
|
-
self.scopes_configuration ||= {}
|
249
|
+
self.parents_symbols ||= []
|
326
250
|
self.resources_configuration[:polymorphic] ||= { :symbols => [], :optional => false }
|
327
251
|
end
|
328
252
|
|
@@ -2,6 +2,16 @@ module ActionController #:nodoc:
|
|
2
2
|
class Base #:nodoc:
|
3
3
|
attr_accessor :formats
|
4
4
|
|
5
|
+
class_inheritable_accessor :mimes_for_respond_to, :responder, :instance_writer => false
|
6
|
+
|
7
|
+
self.responder = ActionController::Responder
|
8
|
+
self.mimes_for_respond_to = ActiveSupport::OrderedHash.new
|
9
|
+
|
10
|
+
if defined?(ApplicationController)
|
11
|
+
ApplicationController.responder ||= ActionController::Responder
|
12
|
+
ApplicationController.mimes_for_respond_to ||= ActiveSupport::OrderedHash.new
|
13
|
+
end
|
14
|
+
|
5
15
|
# Defines mimes that are rendered by default when invoking respond_with.
|
6
16
|
#
|
7
17
|
# Examples:
|
@@ -25,6 +35,7 @@ module ActionController #:nodoc:
|
|
25
35
|
#
|
26
36
|
def self.respond_to(*mimes)
|
27
37
|
options = mimes.extract_options!
|
38
|
+
clear_respond_to unless mimes_for_respond_to
|
28
39
|
|
29
40
|
only_actions = Array(options.delete(:only))
|
30
41
|
except_actions = Array(options.delete(:except))
|
@@ -38,19 +49,10 @@ module ActionController #:nodoc:
|
|
38
49
|
end
|
39
50
|
|
40
51
|
# Clear all mimes in respond_to.
|
41
|
-
#
|
42
52
|
def self.clear_respond_to
|
43
53
|
write_inheritable_attribute(:mimes_for_respond_to, ActiveSupport::OrderedHash.new)
|
44
54
|
end
|
45
55
|
|
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
56
|
def respond_to(*mimes, &block)
|
55
57
|
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
|
56
58
|
if response = retrieve_response_from_mimes(mimes, &block)
|
@@ -66,10 +68,6 @@ module ActionController #:nodoc:
|
|
66
68
|
end
|
67
69
|
end
|
68
70
|
|
69
|
-
def responder
|
70
|
-
ActionController::Responder
|
71
|
-
end
|
72
|
-
|
73
71
|
protected
|
74
72
|
|
75
73
|
# Collect mimes declared in the class method respond_to valid for the
|
@@ -14,12 +14,11 @@ module ActionController #:nodoc:
|
|
14
14
|
#
|
15
15
|
# When a request comes, for example with format :xml, three steps happen:
|
16
16
|
#
|
17
|
-
# 1)
|
17
|
+
# 1) responder searches for a template at people/index.xml;
|
18
18
|
#
|
19
|
-
# 2) if the template is not available, it will
|
20
|
-
# the controller and the resource and invoke :to_xml on it;
|
19
|
+
# 2) if the template is not available, it will invoke :to_xml in the given resource;
|
21
20
|
#
|
22
|
-
# 3) if the responder does not respond_to :to_xml, call to_format on it.
|
21
|
+
# 3) if the responder does not respond_to :to_xml, call :to_format on it.
|
23
22
|
#
|
24
23
|
# === Builtin HTTP verb semantics
|
25
24
|
#
|
@@ -81,6 +80,11 @@ module ActionController #:nodoc:
|
|
81
80
|
class Responder
|
82
81
|
attr_reader :controller, :request, :format, :resource, :resources, :options
|
83
82
|
|
83
|
+
ACTIONS_FOR_VERBS = {
|
84
|
+
:post => :new,
|
85
|
+
:put => :edit
|
86
|
+
}
|
87
|
+
|
84
88
|
def initialize(controller, resources, options={})
|
85
89
|
@controller = controller
|
86
90
|
@request = controller.request
|
@@ -88,22 +92,29 @@ module ActionController #:nodoc:
|
|
88
92
|
@resource = resources.is_a?(Array) ? resources.last : resources
|
89
93
|
@resources = resources
|
90
94
|
@options = options
|
95
|
+
@action = options.delete(:action)
|
91
96
|
@default_response = options.delete(:default_response)
|
92
97
|
end
|
93
98
|
|
94
99
|
delegate :head, :render, :redirect_to, :to => :controller
|
95
100
|
delegate :get?, :post?, :put?, :delete?, :to => :request
|
96
101
|
|
97
|
-
# Undefine :to_json since it's defined on Object
|
98
|
-
undef_method :to_json
|
102
|
+
# Undefine :to_json and :to_yaml since it's defined on Object
|
103
|
+
undef_method(:to_json) if method_defined?(:to_json)
|
104
|
+
undef_method(:to_yaml) if method_defined?(:to_yaml)
|
99
105
|
|
100
106
|
# Initializes a new responder an invoke the proper format. If the format is
|
101
107
|
# not defined, call to_format.
|
102
108
|
#
|
103
109
|
def self.call(*args)
|
104
|
-
|
105
|
-
|
106
|
-
|
110
|
+
new(*args).respond
|
111
|
+
end
|
112
|
+
|
113
|
+
# Main entry point for responder responsible to dispatch to the proper format.
|
114
|
+
#
|
115
|
+
def respond
|
116
|
+
method = :"to_#{format}"
|
117
|
+
respond_to?(method) ? send(method) : to_format
|
107
118
|
end
|
108
119
|
|
109
120
|
# HTML format does not render the resource, it always attempt to render a
|
@@ -111,14 +122,8 @@ module ActionController #:nodoc:
|
|
111
122
|
#
|
112
123
|
def to_html
|
113
124
|
default_render
|
114
|
-
rescue ActionView::MissingTemplate
|
115
|
-
|
116
|
-
raise
|
117
|
-
elsif has_errors?
|
118
|
-
render :action => default_action
|
119
|
-
else
|
120
|
-
redirect_to resource_location
|
121
|
-
end
|
125
|
+
rescue ActionView::MissingTemplate => e
|
126
|
+
navigation_behavior(e)
|
122
127
|
end
|
123
128
|
|
124
129
|
# All others formats follow the procedure below. First we try to render a
|
@@ -127,9 +132,26 @@ module ActionController #:nodoc:
|
|
127
132
|
#
|
128
133
|
def to_format
|
129
134
|
default_render
|
130
|
-
rescue ActionView::MissingTemplate
|
135
|
+
rescue ActionView::MissingTemplate => e
|
131
136
|
raise unless resourceful?
|
137
|
+
api_behavior(e)
|
138
|
+
end
|
132
139
|
|
140
|
+
protected
|
141
|
+
|
142
|
+
# This is the common behavior for "navigation" requests, like :html, :iphone and so forth.
|
143
|
+
def navigation_behavior(error)
|
144
|
+
if get?
|
145
|
+
raise error
|
146
|
+
elsif has_errors? && default_action
|
147
|
+
render :action => default_action
|
148
|
+
else
|
149
|
+
redirect_to resource_location
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# This is the common behavior for "API" requests, like :xml and :json.
|
154
|
+
def api_behavior(error)
|
133
155
|
if get?
|
134
156
|
display resource
|
135
157
|
elsif has_errors?
|
@@ -141,8 +163,6 @@ module ActionController #:nodoc:
|
|
141
163
|
end
|
142
164
|
end
|
143
165
|
|
144
|
-
protected
|
145
|
-
|
146
166
|
# Checks whether the resource responds to the current format or not.
|
147
167
|
#
|
148
168
|
def resourceful?
|
@@ -181,7 +201,7 @@ module ActionController #:nodoc:
|
|
181
201
|
# render :xml => @user, :status => :created
|
182
202
|
#
|
183
203
|
def display(resource, given_options={})
|
184
|
-
render given_options.merge!(options).merge!(format => resource)
|
204
|
+
controller.send :render, given_options.merge!(options).merge!(format => resource)
|
185
205
|
end
|
186
206
|
|
187
207
|
# Check if the resource has errors or not.
|
@@ -194,7 +214,7 @@ module ActionController #:nodoc:
|
|
194
214
|
# the verb is post.
|
195
215
|
#
|
196
216
|
def default_action
|
197
|
-
request.
|
217
|
+
@action ||= ACTIONS_FOR_VERBS[request.method]
|
198
218
|
end
|
199
219
|
end
|
200
|
-
end
|
220
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
en:
|
2
|
+
flash:
|
3
|
+
actions:
|
4
|
+
create:
|
5
|
+
notice: '{{resource_name}} was successfully created.'
|
6
|
+
update:
|
7
|
+
notice: '{{resource_name}} was successfully updated.'
|
8
|
+
destroy:
|
9
|
+
notice: '{{resource_name}} was successfully destroyed.'
|
10
|
+
alert: '{{resource_name}} could not be destroyed.'
|
data/test/aliases_test.rb
CHANGED
@@ -104,7 +104,7 @@ class AliasesTest < ActionController::TestCase
|
|
104
104
|
end
|
105
105
|
|
106
106
|
def test_wont_render_edit_template_on_update_with_failure_if_failure_block_is_given
|
107
|
-
Student.stubs(:find).returns(mock_student(:update_attributes => false))
|
107
|
+
Student.stubs(:find).returns(mock_student(:update_attributes => false, :errors => { :fail => true }))
|
108
108
|
put :update
|
109
109
|
assert_response :success
|
110
110
|
assert_equal "I won't render!", @response.body
|
@@ -132,8 +132,13 @@ class AliasesTest < ActionController::TestCase
|
|
132
132
|
end
|
133
133
|
|
134
134
|
protected
|
135
|
-
def mock_student(
|
136
|
-
@mock_student ||=
|
135
|
+
def mock_student(expectations={})
|
136
|
+
@mock_student ||= begin
|
137
|
+
student = mock(expectations.except(:errors))
|
138
|
+
student.stubs(:class).returns(Student)
|
139
|
+
student.stubs(:errors).returns(expectations.fetch(:errors, {}))
|
140
|
+
student
|
141
|
+
end
|
137
142
|
end
|
138
143
|
end
|
139
144
|
|
data/test/base_test.rb
CHANGED
@@ -9,6 +9,14 @@ end
|
|
9
9
|
|
10
10
|
class UsersController < AccountsController
|
11
11
|
respond_to :html, :xml
|
12
|
+
attr_reader :scopes_applied
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def apply_scopes(object)
|
17
|
+
@scopes_applied = true
|
18
|
+
object
|
19
|
+
end
|
12
20
|
end
|
13
21
|
|
14
22
|
module UserTestHelper
|
@@ -16,11 +24,18 @@ module UserTestHelper
|
|
16
24
|
@controller = UsersController.new
|
17
25
|
@controller.request = @request = ActionController::TestRequest.new
|
18
26
|
@controller.response = @response = ActionController::TestResponse.new
|
27
|
+
@controller.stubs(:user_url).returns("/")
|
19
28
|
end
|
20
29
|
|
21
30
|
protected
|
22
|
-
|
23
|
-
|
31
|
+
|
32
|
+
def mock_user(expectations={})
|
33
|
+
@mock_user ||= begin
|
34
|
+
user = mock(expectations.except(:errors))
|
35
|
+
user.stubs(:class).returns(User)
|
36
|
+
user.stubs(:errors).returns(expectations.fetch(:errors, {}))
|
37
|
+
user
|
38
|
+
end
|
24
39
|
end
|
25
40
|
end
|
26
41
|
|
@@ -33,6 +48,12 @@ class IndexActionBaseTest < ActionController::TestCase
|
|
33
48
|
assert_equal [mock_user], assigns(:users)
|
34
49
|
end
|
35
50
|
|
51
|
+
def test_apply_scopes_if_method_is_available
|
52
|
+
User.expects(:find).with(:all).returns([mock_user])
|
53
|
+
get :index
|
54
|
+
assert @controller.scopes_applied
|
55
|
+
end
|
56
|
+
|
36
57
|
def test_controller_should_render_index
|
37
58
|
User.stubs(:find).returns([mock_user])
|
38
59
|
get :index
|
@@ -210,9 +231,9 @@ class DestroyActionBaseTest < ActionController::TestCase
|
|
210
231
|
end
|
211
232
|
|
212
233
|
def test_show_flash_message_when_cannot_be_deleted
|
213
|
-
User.stubs(:find).returns(mock_user(:destroy => false))
|
234
|
+
User.stubs(:find).returns(mock_user(:destroy => false, :errors => { :fail => true }))
|
214
235
|
delete :destroy
|
215
|
-
assert_equal flash[:
|
236
|
+
assert_equal flash[:alert], 'User could not be destroyed.'
|
216
237
|
end
|
217
238
|
|
218
239
|
def test_redirects_to_users_list
|
data/test/class_methods_test.rb
CHANGED
@@ -18,11 +18,6 @@ class Dean
|
|
18
18
|
def self.human_name; 'Dean'; end
|
19
19
|
end
|
20
20
|
|
21
|
-
class SchoolsController < InheritedResources::Base
|
22
|
-
has_scope :by_city
|
23
|
-
has_scope :featured, :boolean => true, :only => :index, :as => :by_featured
|
24
|
-
end
|
25
|
-
|
26
21
|
class DeansController < InheritedResources::Base
|
27
22
|
belongs_to :school
|
28
23
|
end
|
@@ -134,17 +129,4 @@ class BelongsToErrorsTest < ActiveSupport::TestCase
|
|
134
129
|
ensure
|
135
130
|
DeansController.send(:parents_symbols=, [:school])
|
136
131
|
end
|
137
|
-
end
|
138
|
-
|
139
|
-
class HasScopeClassMethods < ActiveSupport::TestCase
|
140
|
-
def test_scope_configuration_is_stored_as_hashes
|
141
|
-
config = SchoolsController.send(:scopes_configuration)
|
142
|
-
|
143
|
-
assert config.key?(:by_city)
|
144
|
-
assert config.key?(:featured)
|
145
|
-
|
146
|
-
assert_equal config[:by_city], { :as => :by_city, :only => [], :except => [] }
|
147
|
-
assert_equal config[:featured], { :as => :by_featured, :only => [ :index ], :except => [], :boolean => true }
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
132
|
+
end
|