josevalim-inherited_resources 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,65 @@
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, target_name) #:nodoc:
16
+ @current_scopes ||= {}
17
+ scope_config = self.scopes_configuration[target_name] || {}
18
+
19
+ scope_config.each do |scope, options|
20
+ next unless apply_scope_to_action?(options)
21
+ key = options[:key]
22
+
23
+ if params.key?(key)
24
+ value, call_scope = params[key], true
25
+ elsif options.key?(:default)
26
+ value, call_scope = options[:default], true
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
+ if options[:only].empty?
48
+ if options[:except].empty?
49
+ true
50
+ else
51
+ !options[:except].include?(action_name.to_sym)
52
+ end
53
+ else
54
+ options[:only].include?(action_name.to_sym)
55
+ end
56
+ end
57
+
58
+ # Returns the scopes used in this action.
59
+ #
60
+ def current_scopes
61
+ @current_scopes || {}
62
+ end
63
+
64
+ end
65
+ end
@@ -1,8 +1,94 @@
1
- module InheritedResources #:nodoc:
2
- module PolymorphicHelpers #:nodoc:
1
+ module InheritedResources
2
+
3
+ # = polymorphic associations
4
+ #
5
+ # In some cases you have a resource that belongs to two different resources
6
+ # but not at the same time. For example, let's suppose you have File, Message
7
+ # and Task as resources and they are all commentable.
8
+ #
9
+ # Polymorphic associations allows you to create just one controller that will
10
+ # deal with each case.
11
+ #
12
+ # class Comment < InheritedResources::Base
13
+ # belongs_to :file, :message, :task, :polymorphic => true
14
+ # end
15
+ #
16
+ # Your routes should be something like:
17
+ #
18
+ # m.resources :files, :has_many => :comments #=> /files/13/comments
19
+ # m.resources :tasks, :has_many => :comments #=> /tasks/17/comments
20
+ # m.resources :messages, :has_many => :comments #=> /messages/11/comments
21
+ #
22
+ # When using polymorphic associations, you get some free helpers:
23
+ #
24
+ # parent? #=> true
25
+ # parent_type #=> :task
26
+ # parent_class #=> Task
27
+ # parent #=> @task
28
+ #
29
+ # This polymorphic controllers thing is a great idea by James Golick and he
30
+ # built it in resource_controller. Here is just a re-implementation.
31
+ #
32
+ # = optional polymorphic associations
33
+ #
34
+ # Let's take another break from ProjectsController. Let's suppose we are
35
+ # building a store, which sell products.
36
+ #
37
+ # On the website, we can show all products, but also products scoped to
38
+ # categories, brands, users. In this case case, the association is optional, and
39
+ # we deal with it in the following way:
40
+ #
41
+ # class ProductsController < InheritedResources::Base
42
+ # belongs_to :category, :brand, :user, :polymorphic => true, :optional => true
43
+ # end
44
+ #
45
+ # This will handle all those urls properly:
46
+ #
47
+ # /products/1
48
+ # /categories/2/products/5
49
+ # /brands/10/products/3
50
+ # /user/13/products/11
51
+ #
52
+ # = nested polymorphic associations
53
+ #
54
+ # You can have polymorphic associations with nested resources. Let's suppose
55
+ # that our File, Task and Message resources in the previous example belongs to
56
+ # a project.
57
+ #
58
+ # This way we can have:
59
+ #
60
+ # class CommentsController < InheritedResources::Base
61
+ # belongs_to :project {
62
+ # belongs_to :file, :message, :task, :polymorphic => true
63
+ # }
64
+ # end
65
+ #
66
+ # Or:
67
+ #
68
+ # class CommentsController < InheritedResources::Base
69
+ # nested_belongs_to :project
70
+ # nested_belongs_to :file, :message, :task, :polymorphic => true
71
+ # end
72
+ #
73
+ # Choose the syntax that makes more sense to you. :)
74
+ #
75
+ # Finally your routes should be something like:
76
+ #
77
+ # map.resources :projects do |m|
78
+ # m.resources :files, :has_many => :comments #=> /projects/1/files/13/comments
79
+ # m.resources :tasks, :has_many => :comments #=> /projects/1/tasks/17/comments
80
+ # m.resources :messages, :has_many => :comments #=> /projects/1/messages/11/comments
81
+ # end
82
+ #
83
+ # The helpers work in the same way as above.
84
+ #
85
+ module PolymorphicHelpers
3
86
 
4
87
  protected
5
88
 
89
+ # Returns the parent type. A Comments class can have :task, :file, :note
90
+ # as parent types.
91
+ #
6
92
  def parent_type
7
93
  @parent_type
8
94
  end
@@ -11,12 +97,15 @@ module InheritedResources #:nodoc:
11
97
  parent.class if @parent_type
12
98
  end
13
99
 
100
+ # Returns the parent object. They are also available with the instance
101
+ # variable name: @task, @file, @note...
102
+ #
14
103
  def parent
15
104
  instance_variable_get("@#{@parent_type}") if @parent_type
16
105
  end
17
106
 
18
- private
19
-
107
+ # If the polymorphic association is optional, we might not have a parent.
108
+ #
20
109
  def parent?
21
110
  if resources_configuration[:polymorphic][:optional]
22
111
  parents_symbols.size > 1 || !@parent_type.nil?
@@ -25,6 +114,8 @@ module InheritedResources #:nodoc:
25
114
  end
26
115
  end
27
116
 
117
+ private
118
+
28
119
  # Maps parents_symbols to build association chain.
29
120
  #
30
121
  # If the parents_symbols find :polymorphic, it goes through the
@@ -33,7 +124,7 @@ module InheritedResources #:nodoc:
33
124
  # When optional is given, it does not raise errors if the polymorphic
34
125
  # params are missing.
35
126
  #
36
- def symbols_for_chain
127
+ def symbols_for_association_chain #:nodoc:
37
128
  polymorphic_config = resources_configuration[:polymorphic]
38
129
 
39
130
  parents_symbols.map do |symbol|
@@ -1,8 +1,8 @@
1
- # Provides an extension for Rails respond_to by expading MimeResponds::Responder
2
- # and adding respond_to class method and respond_with instance method.
3
- #
4
- module ActionController #:nodoc:
5
- class Base #:nodoc:
1
+ module ActionController
2
+ # Provides an extension for Rails respond_to by expading MimeResponds::Responder
3
+ # and adding respond_to class method and respond_with instance method.
4
+ #
5
+ class Base
6
6
 
7
7
  protected
8
8
  # Defines respond_to method to store formats that are rendered by default.
@@ -240,10 +240,10 @@ module ActionController #:nodoc:
240
240
 
241
241
  private
242
242
 
243
- # Define template_exists? for Rails 2.3
244
243
  unless ActionController::Base.private_instance_methods.include?('template_exists?') ||
245
244
  ActionController::Base.private_instance_methods.include?(:template_exists?)
246
245
 
246
+ # Define template_exists? for Rails 2.3
247
247
  def template_exists?
248
248
  default_template ? true : false
249
249
  rescue ActionView::MissingTemplate
@@ -254,7 +254,7 @@ module ActionController #:nodoc:
254
254
  # We respond to the default template if it's a valid format AND the template
255
255
  # exists.
256
256
  #
257
- def respond_to_default_template?(responder)
257
+ def respond_to_default_template?(responder) #:nodoc:
258
258
  responder.action_respond_to_format?(default_template_format) && template_exists?
259
259
  end
260
260
 
@@ -1,8 +1,43 @@
1
- module InheritedResources #:nodoc:
2
- module SingletonHelpers #:nodoc:
1
+ module InheritedResources
2
+
3
+ # = singleton
4
+ #
5
+ # Singletons are usually used in associations which are related through has_one
6
+ # and belongs_to. You declare those associations like this:
7
+ #
8
+ # class ManagersController < InheritedResources::Base
9
+ # belongs_to :project, :singleton => true
10
+ # end
11
+ #
12
+ # But in some cases, like an AccountsController, you have a singleton object
13
+ # that is not necessarily associated with another:
14
+ #
15
+ # class AccountsController < InheritedResources::Base
16
+ # defaults :singleton => true
17
+ # end
18
+ #
19
+ # Besides that, you should overwrite the methods :resource and :build_resource
20
+ # to make it work properly:
21
+ #
22
+ # class AccountsController < InheritedResources::Base
23
+ # defaults :singleton => true
24
+ #
25
+ # protected
26
+ # def resource
27
+ # @current_user.account
28
+ # end
29
+ #
30
+ # def build_resource(attributes = {})
31
+ # Account.new(attributes)
32
+ # end
33
+ # end
34
+ #
35
+ # When you have a singleton controller, the action index is removed.
36
+ #
37
+ module SingletonHelpers
3
38
 
4
- # Protected helpers. You might want to overwrite some of them.
5
39
  protected
40
+
6
41
  # Singleton methods does not deal with collections.
7
42
  #
8
43
  def collection
@@ -10,6 +45,7 @@ module InheritedResources #:nodoc:
10
45
  end
11
46
 
12
47
  # Overwrites how singleton deals with resource.
48
+ #
13
49
  # If you are going to overwrite it, you should notice that the
14
50
  # end_of_association_chain here is not the same as in default belongs_to.
15
51
  #
@@ -34,20 +70,26 @@ module InheritedResources #:nodoc:
34
70
  #
35
71
  # Project.find(params[:project_id])
36
72
  #
37
- # So we have to call manager on it. And again, this is what happens.
73
+ # So we have to call manager on it, not find.
38
74
  #
39
75
  def resource
40
76
  get_resource_ivar || set_resource_ivar(end_of_association_chain.send(resource_instance_name))
41
77
  end
42
78
 
43
- # Private helpers, you probably don't have to worry with them.
44
79
  private
45
80
 
46
81
  # Returns the appropriated method to build the resource.
47
82
  #
48
- def method_for_build
83
+ def method_for_build #:nodoc:
49
84
  "build_#{resource_instance_name}"
50
85
  end
51
86
 
87
+ # Sets the method_for_association_chain to nil. See <tt>resource</tt>
88
+ # above for more information.
89
+ #
90
+ def method_for_association_chain #:nodoc:
91
+ nil
92
+ end
93
+
52
94
  end
53
95
  end
@@ -1,34 +1,34 @@
1
- # = URLHelpers
2
- #
3
- # When you use InheritedResources it creates some UrlHelpers for you.
4
- # And they handle everything for you.
5
- #
6
- # # /posts/1/comments
7
- # resource_url # => /posts/1/comments/#{@comment.to_param}
8
- # resource_url(comment) # => /posts/1/comments/#{comment.to_param}
9
- # new_resource_url # => /posts/1/comments/new
10
- # edit_resource_url # => /posts/1/comments/#{@comment.to_param}/edit
11
- # collection_url # => /posts/1/comments
12
- #
13
- # # /projects/1/tasks
14
- # resource_url # => /products/1/tasks/#{@task.to_param}
15
- # resource_url(task) # => /products/1/tasks/#{task.to_param}
16
- # new_resource_url # => /products/1/tasks/new
17
- # edit_resource_url # => /products/1/tasks/#{@task.to_param}/edit
18
- # collection_url # => /products/1/tasks
19
- #
20
- # # /users
21
- # resource_url # => /users/#{@user.to_param}
22
- # resource_url(user) # => /users/#{user.to_param}
23
- # new_resource_url # => /users/new
24
- # edit_resource_url # => /users/#{@user.to_param}/edit
25
- # collection_url # => /users
26
- #
27
- # The nice thing is that those urls are not guessed during runtime. They are
28
- # all created when you inherit.
29
- #
30
- module InheritedResources #:nodoc:
31
- module UrlHelpers #:nodoc:
1
+ module InheritedResources
2
+ # = URLHelpers
3
+ #
4
+ # When you use InheritedResources it creates some UrlHelpers for you.
5
+ # And they handle everything for you.
6
+ #
7
+ # # /posts/1/comments
8
+ # resource_url # => /posts/1/comments/#{@comment.to_param}
9
+ # resource_url(comment) # => /posts/1/comments/#{comment.to_param}
10
+ # new_resource_url # => /posts/1/comments/new
11
+ # edit_resource_url # => /posts/1/comments/#{@comment.to_param}/edit
12
+ # collection_url # => /posts/1/comments
13
+ #
14
+ # # /projects/1/tasks
15
+ # resource_url # => /products/1/tasks/#{@task.to_param}
16
+ # resource_url(task) # => /products/1/tasks/#{task.to_param}
17
+ # new_resource_url # => /products/1/tasks/new
18
+ # edit_resource_url # => /products/1/tasks/#{@task.to_param}/edit
19
+ # collection_url # => /products/1/tasks
20
+ #
21
+ # # /users
22
+ # resource_url # => /users/#{@user.to_param}
23
+ # resource_url(user) # => /users/#{user.to_param}
24
+ # new_resource_url # => /users/new
25
+ # edit_resource_url # => /users/#{@user.to_param}/edit
26
+ # collection_url # => /users
27
+ #
28
+ # The nice thing is that those urls are not guessed during runtime. They are
29
+ # all created when you inherit.
30
+ #
31
+ module UrlHelpers
32
32
 
33
33
  # This method hard code url helpers in the class.
34
34
  #
@@ -76,7 +76,7 @@ module InheritedResources #:nodoc:
76
76
  #
77
77
  # This is where you are going to be redirected after destroying the manager.
78
78
  #
79
- unless base.singleton
79
+ unless base.resources_configuration[:self][:singleton]
80
80
  resource_segments << resource_config[:route_collection_name]
81
81
  generate_url_and_path_helpers(base, nil, :collection, resource_segments, resource_ivars, polymorphic)
82
82
  resource_segments.pop
@@ -97,20 +97,21 @@ module InheritedResources #:nodoc:
97
97
  #
98
98
  # edit_project_manager_url(@project, @manager)
99
99
  #
100
- resource_ivars << resource_config[:instance_name] unless base.singleton
100
+ resource_ivars << resource_config[:instance_name] unless base.resources_configuration[:self][:singleton]
101
101
 
102
102
  # Prepare and add resource_url and edit_resource_url
103
103
  generate_url_and_path_helpers(base, nil, :resource, resource_segments, resource_ivars, polymorphic)
104
104
  generate_url_and_path_helpers(base, :edit, :resource, resource_segments, resource_ivars, polymorphic)
105
105
  end
106
106
 
107
- def self.generate_url_and_path_helpers(base, prefix, name, resource_segments, resource_ivars, polymorphic=false)
107
+ def self.generate_url_and_path_helpers(base, prefix, name, resource_segments, resource_ivars, polymorphic=false) #:nodoc:
108
108
  ivars = resource_ivars.map{|i| i == :parent ? :parent : "@#{i}" }
109
109
 
110
110
  # If it's not a singleton, ivars are not empty, not a collection or
111
111
  # not a "new" named route, we can pass a resource as argument.
112
112
  #
113
- unless base.singleton || ivars.empty? || name == :collection || prefix == :new
113
+ unless base.resources_configuration[:self][:singleton] || ivars.empty? ||
114
+ name == :collection || prefix == :new
114
115
  ivars.push "(given_args.first || #{ivars.pop})"
115
116
  end
116
117
 
@@ -159,7 +160,7 @@ module InheritedResources #:nodoc:
159
160
  # polymorphic_url(@project, Task.new)
160
161
  # new_polymorphic_url(@project, Task.new)
161
162
  #
162
- if base.singleton
163
+ if base.resources_configuration[:self][:singleton]
163
164
  ivars << base.resources_configuration[:self][:instance_name].inspect unless name == :collection
164
165
  elsif name == :collection || prefix == :new
165
166
  ivars << 'resource_class.new'
@@ -1,4 +1,6 @@
1
- dir = File.dirname(__FILE__)
2
- require File.join(dir, 'inherited_resources', 'respond_to')
1
+ # respond_to is the only file that should be loaded before hand. All others
2
+ # are loaded on demand.
3
+ #
4
+ require File.join(File.dirname(__FILE__), 'inherited_resources', 'respond_to')
3
5
 
4
6
  module InheritedResources; end
@@ -1,94 +1,10 @@
1
1
  require File.dirname(__FILE__) + '/test_helper'
2
2
 
3
- class Address
4
- def self.human_name; 'Address'; end
5
- end
6
-
7
- class AddressesController < InheritedResources::Base
8
- protected
9
- def interpolation_options
10
- { :reference => 'Ocean Avenue' }
11
- end
12
- end
13
-
14
- module Admin; end
15
- class Admin::AddressesController < InheritedResources::Base
16
- protected
17
- def interpolation_options
18
- { :reference => 'Ocean Avenue' }
19
- end
20
- end
21
-
22
- class FlashBaseHelpersTest < ActionController::TestCase
23
- tests AddressesController
24
-
25
- def setup
26
- @request.accept = 'application/xml'
27
- end
28
-
29
- def test_success_flash_message_on_create_with_yml
30
- Address.stubs(:new).returns(mock_address(:save => true))
31
- @controller.stubs(:address_url)
32
- post :create
33
- assert_equal 'You created a new address close to <b>Ocean Avenue</b>.', flash[:notice]
34
- end
35
-
36
- def test_success_flash_message_on_create_with_namespaced_controller
37
- @controller = Admin::AddressesController.new
38
- Address.stubs(:new).returns(mock_address(:save => true))
39
- @controller.stubs(:address_url)
40
- post :create
41
- assert_equal 'Admin, you created a new address close to <b>Ocean Avenue</b>.', flash[:notice]
42
- end
43
-
44
- def test_failure_flash_message_on_create_with_namespaced_controller_actions
45
- @controller = Admin::AddressesController.new
46
- Address.stubs(:new).returns(mock_address(:save => false))
47
- @controller.stubs(:address_url)
48
- post :create
49
- assert_equal 'Admin error message.', flash[:error]
50
- end
51
-
52
- def test_inherited_success_flash_message_on_update_on_namespaced_controllers
53
- @controller = Admin::AddressesController.new
54
- Address.stubs(:find).returns(mock_address(:update_attributes => true))
55
- put :update
56
- assert_response :success
57
- assert_equal 'Nice! Address was updated with success!', flash[:notice]
58
- end
59
-
60
- def test_success_flash_message_on_update
61
- Address.stubs(:find).returns(mock_address(:update_attributes => true))
62
- put :update
63
- assert_response :success
64
- assert_equal 'Nice! Address was updated with success!', flash[:notice]
65
- end
66
-
67
- def test_failure_flash_message_on_update
68
- Address.stubs(:find).returns(mock_address(:update_attributes => false, :errors => []))
69
- put :update
70
- assert_equal 'Oh no! We could not update your address!', flash[:error]
71
- end
72
-
73
- def test_success_flash_message_on_destroy
74
- Address.stubs(:find).returns(mock_address(:destroy => true))
75
- delete :destroy
76
- assert_equal 'Address was successfully destroyed.', flash[:notice]
77
- end
78
-
79
- protected
80
- def mock_address(stubs={})
81
- @mock_address ||= mock(stubs)
82
- end
83
-
84
- end
85
-
86
3
  class Pet
87
4
  def self.human_name; 'Pet'; end
88
5
  end
89
6
 
90
7
  class PetsController < InheritedResources::Base
91
- respond_to :xml
92
8
  attr_accessor :current_user
93
9
 
94
10
  def edit
@@ -111,38 +27,33 @@ class AssociationChainBaseHelpersTest < ActionController::TestCase
111
27
 
112
28
  def setup
113
29
  @controller.current_user = mock()
114
- @request.accept = 'application/xml'
115
30
  end
116
31
 
117
32
  def test_begin_of_association_chain_is_called_on_index
118
33
  @controller.current_user.expects(:pets).returns(Pet)
119
34
  Pet.expects(:all).returns(mock_pet)
120
- mock_pet.expects(:to_xml).returns('Generated XML')
121
35
  get :index
122
36
  assert_response :success
123
- assert 'Generated XML', @response.body.strip
37
+ assert 'Index HTML', @response.body.strip
124
38
  end
125
39
 
126
40
  def test_begin_of_association_chain_is_called_on_new
127
41
  @controller.current_user.expects(:pets).returns(Pet)
128
42
  Pet.expects(:build).returns(mock_pet)
129
- mock_pet.expects(:to_xml).returns('Generated XML')
130
43
  get :new
131
44
  assert_response :success
132
- assert 'Generated XML', @response.body.strip
45
+ assert 'New HTML', @response.body.strip
133
46
  end
134
47
 
135
48
  def test_begin_of_association_chain_is_called_on_show
136
49
  @controller.current_user.expects(:pets).returns(Pet)
137
50
  Pet.expects(:find).with('47').returns(mock_pet)
138
- mock_pet.expects(:to_xml).returns('Generated XML')
139
51
  get :show, :id => '47'
140
52
  assert_response :success
141
- assert 'Generated XML', @response.body.strip
53
+ assert 'Show HTML', @response.body.strip
142
54
  end
143
55
 
144
56
  def test_instance_variable_should_not_be_set_if_already_defined
145
- @request.accept = 'text/html'
146
57
  @controller.current_user.expects(:pets).never
147
58
  Pet.expects(:find).never
148
59
  get :edit