rc_rails 2.1.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.
- 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
|