rc_rails 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/CHANGELOG +355 -0
- data/Gemfile +5 -0
- data/Gemfile.lock.development +117 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +71 -0
- data/Rakefile +33 -0
- data/Todo.txt +1 -0
- data/lib/rc_rails.rb +9 -0
- data/lib/resources_controller/actions.rb +147 -0
- data/lib/resources_controller/active_record/saved.rb +15 -0
- data/lib/resources_controller/helper.rb +123 -0
- data/lib/resources_controller/include_actions.rb +37 -0
- data/lib/resources_controller/named_route_helper.rb +154 -0
- data/lib/resources_controller/railtie.rb +14 -0
- data/lib/resources_controller/request_path_introspection.rb +83 -0
- data/lib/resources_controller/resource_methods.rb +32 -0
- data/lib/resources_controller/singleton_actions.rb +21 -0
- data/lib/resources_controller/specification.rb +119 -0
- data/lib/resources_controller/version.rb +3 -0
- data/lib/resources_controller.rb +849 -0
- data/resources_controller.gemspec +29 -0
- data/spec/app/database.yml +5 -0
- data/spec/app/views/accounts/show.html.erb +0 -0
- data/spec/app/views/addresses/edit.html.erb +0 -0
- data/spec/app/views/addresses/index.html.erb +0 -0
- data/spec/app/views/addresses/new.html.erb +0 -0
- data/spec/app/views/addresses/show.html.erb +0 -0
- data/spec/app/views/admin/forums/create.html.erb +0 -0
- data/spec/app/views/admin/forums/destroy.html.erb +0 -0
- data/spec/app/views/admin/forums/edit.html.erb +0 -0
- data/spec/app/views/admin/forums/index.html.erb +0 -0
- data/spec/app/views/admin/forums/new.html.erb +0 -0
- data/spec/app/views/admin/forums/show.html.erb +0 -0
- data/spec/app/views/admin/forums/update.html.erb +0 -0
- data/spec/app/views/comments/edit.html.erb +0 -0
- data/spec/app/views/comments/index.html.erb +0 -0
- data/spec/app/views/comments/new.html.erb +0 -0
- data/spec/app/views/comments/show.html.erb +0 -0
- data/spec/app/views/forum_posts/edit.html.erb +0 -0
- data/spec/app/views/forum_posts/index.html.erb +0 -0
- data/spec/app/views/forum_posts/new.html.erb +0 -0
- data/spec/app/views/forum_posts/show.html.erb +0 -0
- data/spec/app/views/forums/create.html.erb +0 -0
- data/spec/app/views/forums/destroy.html.erb +0 -0
- data/spec/app/views/forums/edit.html.erb +0 -0
- data/spec/app/views/forums/index.html.erb +0 -0
- data/spec/app/views/forums/new.html.erb +0 -0
- data/spec/app/views/forums/show.html.erb +0 -0
- data/spec/app/views/forums/update.html.erb +0 -0
- data/spec/app/views/infos/edit.html.erb +0 -0
- data/spec/app/views/infos/show.html.erb +0 -0
- data/spec/app/views/interests/index.html.erb +0 -0
- data/spec/app/views/interests/show.html.erb +0 -0
- data/spec/app/views/owners/edit.html.erb +0 -0
- data/spec/app/views/owners/new.html.erb +0 -0
- data/spec/app/views/owners/show.html.erb +0 -0
- data/spec/app/views/tags/index.html.erb +0 -0
- data/spec/app/views/tags/new.html.erb +0 -0
- data/spec/app/views/tags/show.html.erb +0 -0
- data/spec/app/views/users/edit.html.erb +0 -0
- data/spec/app/views/users/index.html.erb +0 -0
- data/spec/app/views/users/show.html.erb +0 -0
- data/spec/app.rb +315 -0
- data/spec/controllers/accounts_controller_spec.rb +77 -0
- data/spec/controllers/addresses_controller_spec.rb +346 -0
- data/spec/controllers/admin_forums_controller_spec.rb +638 -0
- data/spec/controllers/comments_controller_spec.rb +380 -0
- data/spec/controllers/comments_controller_with_models_spec.rb +202 -0
- data/spec/controllers/forum_posts_controller_spec.rb +426 -0
- data/spec/controllers/forums_controller_spec.rb +694 -0
- data/spec/controllers/infos_controller_spec.rb +71 -0
- data/spec/controllers/interests_controller_via_forum_spec.rb +80 -0
- data/spec/controllers/interests_controller_via_user_spec.rb +114 -0
- data/spec/controllers/owners_controller_spec.rb +277 -0
- data/spec/controllers/resource_saved_spec.rb +47 -0
- data/spec/controllers/resource_service_in_forums_controller_spec.rb +37 -0
- data/spec/controllers/resource_service_in_infos_controller_spec.rb +36 -0
- data/spec/controllers/resource_service_in_interests_controller_via_forum_spec.rb +51 -0
- data/spec/controllers/tags_controller_spec.rb +83 -0
- data/spec/controllers/tags_controller_via_account_info_spec.rb +131 -0
- data/spec/controllers/tags_controller_via_forum_post_comment_spec.rb +144 -0
- data/spec/controllers/tags_controller_via_forum_post_spec.rb +133 -0
- data/spec/controllers/tags_controller_via_forum_spec.rb +173 -0
- data/spec/controllers/tags_controller_via_user_address_spec.rb +130 -0
- data/spec/controllers/users_controller_spec.rb +248 -0
- data/spec/lib/action_view_helper_spec.rb +143 -0
- data/spec/lib/bug_0001_spec.rb +22 -0
- data/spec/lib/include_actions_spec.rb +35 -0
- data/spec/lib/load_enclosing_resources_spec.rb +245 -0
- data/spec/lib/request_path_introspection_spec.rb +130 -0
- data/spec/lib/resource_methods_spec.rb +204 -0
- data/spec/lib/resources_controller_spec.rb +57 -0
- data/spec/models/comment_saved_spec.rb +24 -0
- data/spec/rspec_generator_task.rb +105 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/verify_rcov.rb +52 -0
- metadata +193 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
module ResourcesController
|
2
|
+
# standard CRUD actions, with html, js and xml responses, re-written to mnake best use of resources_cotroller.
|
3
|
+
# This helps if you're writing controllers that you want to share via mixin or inheritance.
|
4
|
+
#
|
5
|
+
# This module is used as the actions for the controller by default, but you can change this behaviour:
|
6
|
+
#
|
7
|
+
# resources_controller_for :foos, :actions_include => false # don't include any actions
|
8
|
+
# resources_controller_for :foos, :actions_include => Some::Other::Module # use this module instead
|
9
|
+
#
|
10
|
+
# == Why?
|
11
|
+
#
|
12
|
+
# The idea is to decouple the <b>model name</b> from the action code.
|
13
|
+
#
|
14
|
+
# Here's how:
|
15
|
+
#
|
16
|
+
# === finding and making new resources
|
17
|
+
# Instead of this:
|
18
|
+
# @post = Post.find(params[:id])
|
19
|
+
# @post = Post.new
|
20
|
+
# @posts = Post.find(:all)
|
21
|
+
#
|
22
|
+
# do this:
|
23
|
+
# self.resource = find_resource
|
24
|
+
# self.resource = new_resource
|
25
|
+
# self.resources = find_resources
|
26
|
+
#
|
27
|
+
# === referring to resources
|
28
|
+
# Instead of this:
|
29
|
+
# format.xml { render :xml => @post }
|
30
|
+
# format.xml { render :xml => @posts }
|
31
|
+
#
|
32
|
+
# do this:
|
33
|
+
# format.xml { render :xml => resource }
|
34
|
+
# format.xml { render :xml => resources }
|
35
|
+
#
|
36
|
+
# === urls
|
37
|
+
# Instead of this:
|
38
|
+
# redirect_to posts_url
|
39
|
+
# redirect_to new_post_url
|
40
|
+
#
|
41
|
+
# do this:
|
42
|
+
# redirect_to resources_url
|
43
|
+
# redirect_to new_resource_url
|
44
|
+
#
|
45
|
+
module Actions
|
46
|
+
# GET /events
|
47
|
+
# GET /events.xml
|
48
|
+
def index
|
49
|
+
self.resources = find_resources
|
50
|
+
|
51
|
+
respond_to do |format|
|
52
|
+
format.html # index.rhtml
|
53
|
+
format.js
|
54
|
+
format.xml { render :xml => resources }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# GET /events/1
|
59
|
+
# GET /events/1.xml
|
60
|
+
def show
|
61
|
+
self.resource = find_resource
|
62
|
+
|
63
|
+
respond_to do |format|
|
64
|
+
format.html # show.erb.html
|
65
|
+
format.js
|
66
|
+
format.xml { render :xml => resource }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# GET /events/new
|
71
|
+
def new
|
72
|
+
self.resource = new_resource
|
73
|
+
|
74
|
+
respond_to do |format|
|
75
|
+
format.html # new.html.erb
|
76
|
+
format.js
|
77
|
+
format.xml { render :xml => resource }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# GET /events/1/edit
|
82
|
+
def edit
|
83
|
+
self.resource = find_resource
|
84
|
+
respond_to do |format|
|
85
|
+
format.html # edit.html.erb
|
86
|
+
format.js
|
87
|
+
format.xml { render :xml => resource }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# POST /events
|
92
|
+
# POST /events.xml
|
93
|
+
def create
|
94
|
+
self.resource = new_resource
|
95
|
+
|
96
|
+
respond_to do |format|
|
97
|
+
if resource.save
|
98
|
+
format.html do
|
99
|
+
flash[:notice] = "#{resource_name.humanize} was successfully created."
|
100
|
+
redirect_to resource_url
|
101
|
+
end
|
102
|
+
format.js
|
103
|
+
format.xml { render :xml => resource, :status => :created, :location => resource_url }
|
104
|
+
else
|
105
|
+
format.html { render :action => "new" }
|
106
|
+
format.js { render :action => "new" }
|
107
|
+
format.xml { render :xml => resource.errors, :status => :unprocessable_entity }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# PUT /events/1
|
113
|
+
# PUT /events/1.xml
|
114
|
+
def update
|
115
|
+
self.resource = find_resource
|
116
|
+
|
117
|
+
respond_to do |format|
|
118
|
+
if resource.update_attributes(params[resource_name])
|
119
|
+
format.html do
|
120
|
+
flash[:notice] = "#{resource_name.humanize} was successfully updated."
|
121
|
+
redirect_to resource_url
|
122
|
+
end
|
123
|
+
format.js
|
124
|
+
format.xml { head :ok }
|
125
|
+
else
|
126
|
+
format.html { render :action => "edit" }
|
127
|
+
format.js { render :action => "edit" }
|
128
|
+
format.xml { render :xml => resource.errors, :status => :unprocessable_entity }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# DELETE /events/1
|
134
|
+
# DELETE /events/1.xml
|
135
|
+
def destroy
|
136
|
+
self.resource = destroy_resource
|
137
|
+
respond_to do |format|
|
138
|
+
format.html do
|
139
|
+
flash[:notice] = "#{resource_name.humanize} was successfully destroyed."
|
140
|
+
redirect_to resources_url
|
141
|
+
end
|
142
|
+
format.js
|
143
|
+
format.xml { head :ok }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ResourcesController
|
2
|
+
module ActiveRecord
|
3
|
+
module Saved
|
4
|
+
# returns true if this record is not new, and has no errors
|
5
|
+
def saved?
|
6
|
+
!new_record? && (@errors.nil? || errors.empty?)
|
7
|
+
end
|
8
|
+
|
9
|
+
# returns true if this instance has had validation (maybe via save) attempted
|
10
|
+
def validation_attempted?
|
11
|
+
!@errors.nil?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module ResourcesController
|
2
|
+
# Often it won't be appropriate to re-use views, but
|
3
|
+
# sometimes it is. These helper methods enable reuse by referencing whatever resource the
|
4
|
+
# controller is for.
|
5
|
+
#
|
6
|
+
# ==== Example:
|
7
|
+
#
|
8
|
+
# instead of writing:
|
9
|
+
# <% for event in @events %>
|
10
|
+
# <%= link_to 'edit', edit_event_path(event) %>
|
11
|
+
#
|
12
|
+
# you may write:
|
13
|
+
# <% for event in resources %>
|
14
|
+
# <%= link_to 'edit', edit_resource_path(event) %>
|
15
|
+
#
|
16
|
+
# == Enclosing resource
|
17
|
+
#
|
18
|
+
# For controllers with enclosing resources instead of writing:
|
19
|
+
# <%= link_to 'back to Forum', forum_path(@forum) %>
|
20
|
+
#
|
21
|
+
# you may write: (which will work for any enclosing path)
|
22
|
+
# <%= link_to "back to #{enclosing_resource.class.name.titleize}", enclosing_resource_path %>
|
23
|
+
#
|
24
|
+
# == Enclosing named routes:
|
25
|
+
#
|
26
|
+
# In addition you can reference named routes that are 'below' the current resource
|
27
|
+
# by appending resource_ to that named route.
|
28
|
+
#
|
29
|
+
# ==== Example: shared polymorphic view
|
30
|
+
#
|
31
|
+
# Let's say you have a resource controller for tags, and you're writing the
|
32
|
+
# taggable views. In a view shared amongst taggables you can write
|
33
|
+
#
|
34
|
+
# <%= link_to 'tags', resource_tags_path %>
|
35
|
+
# <%= link_to 'edit tag', edit_resource_tag_path(@tag) %>
|
36
|
+
#
|
37
|
+
# or:
|
38
|
+
# <% for taggable in resources %>
|
39
|
+
# <%= link_to 'tags', resource_tags_path(taggable) %>
|
40
|
+
#
|
41
|
+
module Helper
|
42
|
+
def self.included(base)
|
43
|
+
base.class_eval do
|
44
|
+
alias_method_chain :method_missing, :named_route_helper
|
45
|
+
alias_method_chain :respond_to?, :named_route_helper
|
46
|
+
delegate :resource_name, :resources_name, :resource, :resources, :enclosing_resource, :enclosing_resource_name, :to => :controller
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# DEPRECATED: you should just be able to use <tt>form_for resource</tt>
|
51
|
+
#
|
52
|
+
# Calls form_for with the apropriate action and method for the resource
|
53
|
+
#
|
54
|
+
# resource.new_record? is used to decide between a create or update action
|
55
|
+
#
|
56
|
+
# You can optionally pass a resource object, default is to use self.resource
|
57
|
+
#
|
58
|
+
# You may also override the url by passing <tt>:url</tt>, or pass extra options
|
59
|
+
# to resource path url with <tt>:url_options</tt>
|
60
|
+
#
|
61
|
+
# === Example
|
62
|
+
#
|
63
|
+
# <% form_for_resource do |f| %>
|
64
|
+
# <%= f.text_field :name %>
|
65
|
+
# <%= f.submit resource.new_record? ? 'Create' : 'Update'
|
66
|
+
# <% end %>
|
67
|
+
#
|
68
|
+
# <% for attachment in resources %>
|
69
|
+
# <% form_for_resource attachment, :html => {:multipart => true} %>
|
70
|
+
# <%= f.file_field :uploaded_data %>
|
71
|
+
# <%= f.submit 'Update' %>
|
72
|
+
# <% end %>
|
73
|
+
# <% end %>
|
74
|
+
#
|
75
|
+
def form_for_resource(*args, &block)
|
76
|
+
options = args.extract_options!
|
77
|
+
resource = args[0] || self.resource
|
78
|
+
form_for(resource_name, resource, form_for_resource_options(resource, options), &block)
|
79
|
+
end
|
80
|
+
|
81
|
+
# same API as form_for_resource
|
82
|
+
def remote_form_for_resource(*args, &block)
|
83
|
+
options = args.extract_options!
|
84
|
+
resource = args[0] || self.resource
|
85
|
+
remote_form_for(resource_name, resource, form_for_resource_options(resource, options), &block)
|
86
|
+
end
|
87
|
+
|
88
|
+
# print the error messages for the current resource
|
89
|
+
def error_messages_for_resource
|
90
|
+
error_messages_for resource_name
|
91
|
+
end
|
92
|
+
|
93
|
+
# Delegate named_route helper method to the controller. Create the delegation
|
94
|
+
# to short circuit the method_missing call for future invocations.
|
95
|
+
def method_missing_with_named_route_helper(method, *args, &block)
|
96
|
+
if controller.resource_named_route_helper_method?(method)
|
97
|
+
self.class.send(:delegate, method, :to => :controller)
|
98
|
+
controller.send(method, *args)
|
99
|
+
else
|
100
|
+
method_missing_without_named_route_helper(method, *args, &block)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# delegate url help method creation to the controller
|
105
|
+
def respond_to_with_named_route_helper?(*args)
|
106
|
+
respond_to_without_named_route_helper?(*args) || controller.resource_named_route_helper_method?(args.first)
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
def form_for_resource_options(resource, options)
|
111
|
+
options.dup.tap do |options|
|
112
|
+
options[:html] ||= {}
|
113
|
+
options[:html][:method] ||= resource.new_record? ? :post : :put
|
114
|
+
args = options[:url_options] ? [options.delete(:url_options)] : []
|
115
|
+
options[:url] ||= if resource.new_record?
|
116
|
+
controller.resource_specification.singleton? ? resource_path(*args) : resources_path(*args)
|
117
|
+
else
|
118
|
+
controller.resource_specification.singleton? ? resource_path(*args) : resource_path(*([resource] + args))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ResourcesController
|
2
|
+
# extension for any module that is used as an Actions module.
|
3
|
+
#
|
4
|
+
# After extending the module (say 'MyActions'), instead of doing this:
|
5
|
+
# self.include ActionsModule
|
6
|
+
# do this:
|
7
|
+
# ActionsModule.include_actions(self, <:only or :except options>)
|
8
|
+
#
|
9
|
+
# RC extends any actions module with this automatically, so you don't need to know about it.
|
10
|
+
#
|
11
|
+
# However, if you ahve any special behaviour in your actions module that is sensitive to
|
12
|
+
# :only and :except, you can define your own include_actions method on that module
|
13
|
+
# to effect this special behaviour.
|
14
|
+
module IncludeActions
|
15
|
+
def include_actions(controller, options = {})
|
16
|
+
options.assert_valid_keys(:only, :except)
|
17
|
+
raise ArgumentError, "you can only specify either :except or :only, not both" if options[:only] && options[:except]
|
18
|
+
mixin = self.clone
|
19
|
+
action_methods_to_remove(options).each {|a| mixin.remove_action_method(a) }
|
20
|
+
controller.send :include, mixin
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove_action_method(action)
|
24
|
+
undef_method action
|
25
|
+
end
|
26
|
+
|
27
|
+
def action_methods_to_remove(options = {})
|
28
|
+
if options[:only]
|
29
|
+
instance_methods - Array(options[:only]).map(&:to_s)
|
30
|
+
elsif options[:except]
|
31
|
+
Array(options[:except]).map(&:to_s) & instance_methods
|
32
|
+
else
|
33
|
+
[]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module ResourcesController
|
2
|
+
|
3
|
+
class CantMapRoute < ArgumentError #:nodoc:
|
4
|
+
end
|
5
|
+
|
6
|
+
# This module provides methods are provided to aid in writing inheritable controllers.
|
7
|
+
#
|
8
|
+
# When writing an action that redirects to the list of resources, you may use *resources_url* and the controller
|
9
|
+
# will call the url_writer method appropriate to what the controller is a resources controller for.
|
10
|
+
#
|
11
|
+
# If the route specified requires a member argument and you don't provide it, the current resource is used.
|
12
|
+
#
|
13
|
+
# In general you may subsitute 'resource' for the current (maybe polymorphic) resource. e.g.
|
14
|
+
#
|
15
|
+
# You may also substitute 'enclosing_resource' to get urls for the enclosing resource
|
16
|
+
#
|
17
|
+
# (in attachable/attachments where attachable is a Post)
|
18
|
+
#
|
19
|
+
# resources_path # => post_attachments_path
|
20
|
+
# formatted_edit_resource_path('js') # => formatted_post_attachments_path(<current post>, <current attachment>, 'js')
|
21
|
+
# resource_tags_path # => post_attachments_tags_paths(<current post>, <current attachment>)
|
22
|
+
# resource_tags_path(foo) # => post_attachments_tags_paths(<current post>, foo)
|
23
|
+
#
|
24
|
+
# enclosing_resource_path # => post_path(<current post>)
|
25
|
+
# enclosing_resources_path # => posts_path
|
26
|
+
# enclosing_resource_tags_path # => post_tags_path(<current post>)
|
27
|
+
# enclosing_resource_path(2) # => post_path(2)
|
28
|
+
#
|
29
|
+
# The enclosing_resource stuff works with deep nesting if you're into that.
|
30
|
+
#
|
31
|
+
# These methods are defined as they are used. The ActionView Helper module delegates to the current controller to access these
|
32
|
+
# methods
|
33
|
+
module NamedRouteHelper
|
34
|
+
def self.included(base)
|
35
|
+
base.class_eval do
|
36
|
+
alias_method_chain :method_missing, :named_route_helper
|
37
|
+
alias_method_chain :respond_to?, :named_route_helper
|
38
|
+
end
|
39
|
+
base.hide_action *instance_methods
|
40
|
+
base.hide_action :method_missing_without_named_route_helper, :respond_to_without_named_route_helper?, :respond_to?
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing_with_named_route_helper(method, *args, &block)
|
44
|
+
# TODO: test that methods are only defined once
|
45
|
+
if resource_named_route_helper_method?(method, raise_error = true)
|
46
|
+
define_resource_named_route_helper_method(method)
|
47
|
+
send(method, *args)
|
48
|
+
elsif resource_named_route_helper_method_for_name_prefix?(method)
|
49
|
+
define_resource_named_route_helper_method_for_name_prefix(method)
|
50
|
+
send(method, *args)
|
51
|
+
else
|
52
|
+
method_missing_without_named_route_helper(method, *args, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def respond_to_with_named_route_helper?(*args)
|
57
|
+
respond_to_without_named_route_helper?(*args) || resource_named_route_helper_method?(args.first)
|
58
|
+
end
|
59
|
+
|
60
|
+
# return true if the passed method (e.g. 'resources_path') corresponds to a defined
|
61
|
+
# named route helper method
|
62
|
+
def resource_named_route_helper_method?(resource_method, raise_error = false)
|
63
|
+
if resource_method.to_s =~ /_(path|url)$/ && resource_method.to_s =~ /(^|^.*_)enclosing_resource(s)?_/
|
64
|
+
_, route_method = *route_and_method_from_enclosing_resource_method_and_name_prefix(resource_method, name_prefix)
|
65
|
+
elsif resource_method.to_s =~ /_(path|url)$/ && resource_method.to_s =~ /(^|^.*_)resource(s)?_/
|
66
|
+
_, route_method = *route_and_method_from_resource_method_and_name_prefix(resource_method, name_prefix)
|
67
|
+
else
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
respond_to_without_named_route_helper?(route_method) || (raise_error && raise_resource_url_mapping_error(resource_method, route_method))
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def raise_resource_url_mapping_error(resource_method, route_method)
|
75
|
+
raise CantMapRoute, <<-end_str
|
76
|
+
Tried to map :#{resource_method} to :#{route_method},
|
77
|
+
which doesn't exist. You may not have defined the route in config/routes.rb.
|
78
|
+
|
79
|
+
Or, if you have unconventianal route names or name prefixes, you may need
|
80
|
+
to explicictly set the :route option in resources_controller_for, and set
|
81
|
+
the :name_prefix option on your enclosing resources.
|
82
|
+
|
83
|
+
Currently:
|
84
|
+
:route is '#{route_name}'
|
85
|
+
generated name_prefix is '#{name_prefix}'
|
86
|
+
end_str
|
87
|
+
end
|
88
|
+
|
89
|
+
# passed something like (^|.*_)enclosing_resource(s)_.*(url|path)$, will
|
90
|
+
# return the [route, route_method] for the expanded resource
|
91
|
+
def route_and_method_from_enclosing_resource_method_and_name_prefix(method, name_prefix)
|
92
|
+
if enclosing_resource
|
93
|
+
enclosing_route = name_prefix.sub(/_$/,'')
|
94
|
+
route_method = method.to_s.sub(/enclosing_resource(s)?/) { $1 ? enclosing_route.pluralize : enclosing_route }
|
95
|
+
return [Rails.application.routes.named_routes.get(route_method.sub(/_(path|url)$/,'').to_sym), route_method]
|
96
|
+
else
|
97
|
+
raise NoMethodError, "Tried to map :#{method} but there is no enclosing_resource for this controller"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# passed something like (^|.*_)resource(s)_.*(url|path)$, will
|
102
|
+
# return the [route, route_method] for the expanded resource
|
103
|
+
def route_and_method_from_resource_method_and_name_prefix(method, name_prefix)
|
104
|
+
route_method = method.to_s.sub(/resource(s)?/) { $1 ? "#{name_prefix}#{route_name.pluralize}" : "#{name_prefix}#{route_name}" }
|
105
|
+
return [Rails.application.routes.named_routes.get(route_method.sub(/_(path|url)$/,'').to_sym), route_method]
|
106
|
+
end
|
107
|
+
|
108
|
+
# defines a method that calls the appropriate named route method, with appropraite args.
|
109
|
+
def define_resource_named_route_helper_method(method)
|
110
|
+
self.class.send :module_eval, <<-end_eval, __FILE__, __LINE__
|
111
|
+
def #{method}(*args)
|
112
|
+
send "#{method}_for_\#{name_prefix}", *args
|
113
|
+
end
|
114
|
+
end_eval
|
115
|
+
end
|
116
|
+
|
117
|
+
def resource_named_route_helper_method_for_name_prefix?(method)
|
118
|
+
method.to_s =~ /_for_.*$/ && resource_named_route_helper_method?(method.to_s.sub(/_for_.*$/,''))
|
119
|
+
end
|
120
|
+
|
121
|
+
def define_resource_named_route_helper_method_for_name_prefix(method)
|
122
|
+
resource_method = method.to_s.sub(/_for_.*$/,'')
|
123
|
+
name_prefix = method.to_s.sub(/^.*_for_/,'')
|
124
|
+
if resource_method =~ /enclosing_resource/
|
125
|
+
route, route_method = *route_and_method_from_enclosing_resource_method_and_name_prefix(resource_method, name_prefix)
|
126
|
+
required_args = (route.segment_keys - [:format]).size
|
127
|
+
|
128
|
+
self.class.send :module_eval, <<-end_eval, __FILE__, __LINE__
|
129
|
+
def #{method}(*args)
|
130
|
+
options = args.extract_options!
|
131
|
+
args = args.size < #{required_args} ? enclosing_collection_resources + args : enclosing_collection_resources - [enclosing_resource] + args
|
132
|
+
args = args + [options] if options.size > 0
|
133
|
+
send :#{route_method}, *args
|
134
|
+
end
|
135
|
+
end_eval
|
136
|
+
|
137
|
+
else
|
138
|
+
route, route_method = *route_and_method_from_resource_method_and_name_prefix(resource_method, name_prefix)
|
139
|
+
required_args = (route.segment_keys - [:format]).size
|
140
|
+
|
141
|
+
self.class.send :module_eval, <<-end_eval, __FILE__, __LINE__
|
142
|
+
def #{method}(*args)
|
143
|
+
options = args.extract_options!
|
144
|
+
#{"args = [resource] + args if enclosing_collection_resources.size + args.size < #{required_args}" if required_args > 0}
|
145
|
+
args = args + [options] if options.size > 0
|
146
|
+
send :#{route_method}, *(enclosing_collection_resources + args)
|
147
|
+
end
|
148
|
+
end_eval
|
149
|
+
end
|
150
|
+
|
151
|
+
self.class.send :private, method
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ResourcesController
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer 'resources_controller' do
|
4
|
+
ActiveSupport.on_load(:action_controller) do
|
5
|
+
extend ResourcesController
|
6
|
+
include ResourcesController::RequestPathIntrospection
|
7
|
+
end
|
8
|
+
|
9
|
+
ActiveSupport.on_load(:active_record) do
|
10
|
+
include ResourcesController::ActiveRecord::Saved
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module ResourcesController
|
2
|
+
# included into ActionController::Base
|
3
|
+
#
|
4
|
+
# provides ability to determine what nesting segments are for a given request, and whether those segments are singletons,
|
5
|
+
# these methods are aware of resource specifications specified either by map_enclosing_resource.
|
6
|
+
module RequestPathIntrospection
|
7
|
+
protected
|
8
|
+
def request_path
|
9
|
+
@request_path ||= params[:resource_path] || request.path
|
10
|
+
end
|
11
|
+
|
12
|
+
def nesting_request_path
|
13
|
+
@nesting_request_path ||= remove_namespace(remove_current_segment(request_path))
|
14
|
+
end
|
15
|
+
|
16
|
+
# returns an array of hashes like {:segment => 'forum', :singleton => false}
|
17
|
+
def nesting_segments
|
18
|
+
@nesting_segments ||= segments_for_path_and_keys(nesting_request_path, param_keys)
|
19
|
+
end
|
20
|
+
|
21
|
+
# returns an array of segments correspopnding to the namespace of the controller.
|
22
|
+
# If your controller is at a non standard location wrt it's path, you can modify this array in a before filter
|
23
|
+
# to help resources_controller do the right thing
|
24
|
+
def namespace_segments
|
25
|
+
unless @namespace_segments
|
26
|
+
namespace = controller_path.sub(%r(#{controller_name}$), '')
|
27
|
+
@namespace_segments = (request_path =~ %r(^/#{namespace}) ? namespace.split('/') : [])
|
28
|
+
end
|
29
|
+
@namespace_segments
|
30
|
+
end
|
31
|
+
|
32
|
+
def param_keys
|
33
|
+
params.keys.map(&:to_s).select{|k| k[-3..-1] == '_id'}
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def remove_current_segment(path)
|
38
|
+
if respond_to?(:resource_specification) && resource_specification.singleton?
|
39
|
+
path.sub(%r(/#{current_segment}(?!.*/#{current_segment}).*$), '')
|
40
|
+
else
|
41
|
+
path.sub(%r(/#{current_segment}(?!.+/#{current_segment}).*$), '')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def current_segment
|
46
|
+
respond_to?(:resource_specification) ? resource_specification.segment : controller_name
|
47
|
+
end
|
48
|
+
|
49
|
+
def remove_namespace(path)
|
50
|
+
if namespace_segments.any?
|
51
|
+
path.sub(%r(^/#{namespace_segments.join('/')}), '')
|
52
|
+
else
|
53
|
+
path
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def segments_for_path_and_keys(path, keys)
|
58
|
+
key_segments = keys.map{|k| segment_for_key(k)}
|
59
|
+
path_segments = path[1..-1].to_s.split('/')
|
60
|
+
segments = []
|
61
|
+
while path_segments.any? do
|
62
|
+
segment = path_segments.shift
|
63
|
+
if key_segments.include?(segment)
|
64
|
+
segments << {:segment => segment, :singleton => false}
|
65
|
+
path_segments.shift # swallow following :id
|
66
|
+
else
|
67
|
+
segments << {:segment => segment, :singleton => true}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
segments
|
71
|
+
end
|
72
|
+
|
73
|
+
def segment_for_key(key)
|
74
|
+
if respond_to?(:specifications) && spec = specifications.find{|s| s.respond_to?(:key) && s.key == key.to_s}
|
75
|
+
spec.segment
|
76
|
+
elsif spec = resource_specification_map.values.find{|s| s.key == key.to_s}
|
77
|
+
spec.segment
|
78
|
+
else
|
79
|
+
key.to_s[0..-4].pluralize
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ResourcesController
|
2
|
+
# methods which communicate with the resource_service to find/create resources
|
3
|
+
module ResourceMethods
|
4
|
+
protected
|
5
|
+
# finds the collection of resources
|
6
|
+
def find_resources
|
7
|
+
resource_service.all
|
8
|
+
end
|
9
|
+
|
10
|
+
# finds the resource, using the passed id, defaults to the current params[:id]
|
11
|
+
def find_resource(id = nil)
|
12
|
+
id ||= respond_to?(:params) && params.is_a?(Hash) && params[:id]
|
13
|
+
resource_service.find id
|
14
|
+
end
|
15
|
+
|
16
|
+
# makes a new resource, if attributes are not supplied, determine them from the
|
17
|
+
# params hash and the current resource_class, or resource_name (the latter left in for BC)
|
18
|
+
def new_resource(attributes = nil, &block)
|
19
|
+
if attributes.blank? && respond_to?(:params) && params.is_a?(Hash)
|
20
|
+
resource_form_name = ActiveModel::Naming.singular(resource_class)
|
21
|
+
attributes = params[resource_form_name] || params[resource_name] || {}
|
22
|
+
end
|
23
|
+
resource_service.new attributes, &block
|
24
|
+
end
|
25
|
+
|
26
|
+
# destroys and returns the resource, using the passed id, defaults to the current params[:id]
|
27
|
+
def destroy_resource(id = nil)
|
28
|
+
id ||= respond_to?(:params) && params.is_a?(Hash) && params[:id]
|
29
|
+
resource_service.destroy id
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ResourcesController
|
2
|
+
module SingletonActions
|
3
|
+
include Actions
|
4
|
+
|
5
|
+
undef index
|
6
|
+
|
7
|
+
# DELETE /event
|
8
|
+
# DELETE /event.xml
|
9
|
+
def destroy
|
10
|
+
self.resource = destroy_resource
|
11
|
+
respond_to do |format|
|
12
|
+
format.html do
|
13
|
+
flash[:notice] = "#{resource_name.humanize} was successfully destroyed."
|
14
|
+
redirect_to enclosing_resource_url if enclosing_resource
|
15
|
+
end
|
16
|
+
format.js
|
17
|
+
format.xml { head :ok }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|