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.
@@ -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