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