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.
@@ -56,6 +56,14 @@ module InheritedResources
56
56
  def parent?
57
57
  true
58
58
  end
59
+
60
+ def parent
61
+ @parent ||= association_chain[-1]
62
+ end
63
+
64
+ def parent_type
65
+ parent.class.name.underscore.to_sym
66
+ end
59
67
 
60
68
  private
61
69
 
@@ -0,0 +1,12 @@
1
+ module InheritedResources
2
+ # An object from BlankSlate simply discards all messages sent to it.
3
+ class BlankSlate
4
+ instance_methods.each do |m|
5
+ undef_method m unless m =~ /^(__|object_id)/
6
+ end
7
+
8
+ def method_missing(*args)
9
+ nil
10
+ end
11
+ end
12
+ end
@@ -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
- config[:parent_class] = options.delete(:parent_class)
243
- config[:parent_class] ||= (options.delete(:class_name) || symbol).to_s.pluralize.classify.constantize rescue nil
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) respond_with searches for a template at people/index.xml;
17
+ # 1) responder searches for a template at people/index.xml;
18
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;
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
- responder = new(*args)
105
- method = :"to_#{responder.format}"
106
- responder.respond_to?(method) ? responder.send(method) : responder.to_format
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
- if get?
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.post? ? :new : :edit
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.'
@@ -0,0 +1,6 @@
1
+ module InheritedResources
2
+ class Responder < ActionController::Responder
3
+ include Responders::FlashResponder
4
+ include Responders::HttpCacheResponder
5
+ end
6
+ end
@@ -1,3 +1,3 @@
1
1
  module InheritedResources
2
- VERSION = '0.9.5'.freeze
2
+ VERSION = '1.0.pre'.freeze
3
3
  end
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(stubs={})
136
- @mock_student ||= mock(stubs)
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
- def mock_user(stubs={})
23
- @mock_user ||= mock(stubs)
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[:error], 'User could not be destroyed.'
236
+ assert_equal flash[:alert], 'User could not be destroyed.'
216
237
  end
217
238
 
218
239
  def test_redirects_to_users_list
@@ -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