josevalim-inherited_resources 0.6.3 → 0.7.0

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