rc_rails 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/.gitignore +7 -0
  2. data/CHANGELOG +355 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock.development +117 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.rdoc +71 -0
  7. data/Rakefile +33 -0
  8. data/Todo.txt +1 -0
  9. data/lib/rc_rails.rb +9 -0
  10. data/lib/resources_controller/actions.rb +147 -0
  11. data/lib/resources_controller/active_record/saved.rb +15 -0
  12. data/lib/resources_controller/helper.rb +123 -0
  13. data/lib/resources_controller/include_actions.rb +37 -0
  14. data/lib/resources_controller/named_route_helper.rb +154 -0
  15. data/lib/resources_controller/railtie.rb +14 -0
  16. data/lib/resources_controller/request_path_introspection.rb +83 -0
  17. data/lib/resources_controller/resource_methods.rb +32 -0
  18. data/lib/resources_controller/singleton_actions.rb +21 -0
  19. data/lib/resources_controller/specification.rb +119 -0
  20. data/lib/resources_controller/version.rb +3 -0
  21. data/lib/resources_controller.rb +849 -0
  22. data/resources_controller.gemspec +29 -0
  23. data/spec/app/database.yml +5 -0
  24. data/spec/app/views/accounts/show.html.erb +0 -0
  25. data/spec/app/views/addresses/edit.html.erb +0 -0
  26. data/spec/app/views/addresses/index.html.erb +0 -0
  27. data/spec/app/views/addresses/new.html.erb +0 -0
  28. data/spec/app/views/addresses/show.html.erb +0 -0
  29. data/spec/app/views/admin/forums/create.html.erb +0 -0
  30. data/spec/app/views/admin/forums/destroy.html.erb +0 -0
  31. data/spec/app/views/admin/forums/edit.html.erb +0 -0
  32. data/spec/app/views/admin/forums/index.html.erb +0 -0
  33. data/spec/app/views/admin/forums/new.html.erb +0 -0
  34. data/spec/app/views/admin/forums/show.html.erb +0 -0
  35. data/spec/app/views/admin/forums/update.html.erb +0 -0
  36. data/spec/app/views/comments/edit.html.erb +0 -0
  37. data/spec/app/views/comments/index.html.erb +0 -0
  38. data/spec/app/views/comments/new.html.erb +0 -0
  39. data/spec/app/views/comments/show.html.erb +0 -0
  40. data/spec/app/views/forum_posts/edit.html.erb +0 -0
  41. data/spec/app/views/forum_posts/index.html.erb +0 -0
  42. data/spec/app/views/forum_posts/new.html.erb +0 -0
  43. data/spec/app/views/forum_posts/show.html.erb +0 -0
  44. data/spec/app/views/forums/create.html.erb +0 -0
  45. data/spec/app/views/forums/destroy.html.erb +0 -0
  46. data/spec/app/views/forums/edit.html.erb +0 -0
  47. data/spec/app/views/forums/index.html.erb +0 -0
  48. data/spec/app/views/forums/new.html.erb +0 -0
  49. data/spec/app/views/forums/show.html.erb +0 -0
  50. data/spec/app/views/forums/update.html.erb +0 -0
  51. data/spec/app/views/infos/edit.html.erb +0 -0
  52. data/spec/app/views/infos/show.html.erb +0 -0
  53. data/spec/app/views/interests/index.html.erb +0 -0
  54. data/spec/app/views/interests/show.html.erb +0 -0
  55. data/spec/app/views/owners/edit.html.erb +0 -0
  56. data/spec/app/views/owners/new.html.erb +0 -0
  57. data/spec/app/views/owners/show.html.erb +0 -0
  58. data/spec/app/views/tags/index.html.erb +0 -0
  59. data/spec/app/views/tags/new.html.erb +0 -0
  60. data/spec/app/views/tags/show.html.erb +0 -0
  61. data/spec/app/views/users/edit.html.erb +0 -0
  62. data/spec/app/views/users/index.html.erb +0 -0
  63. data/spec/app/views/users/show.html.erb +0 -0
  64. data/spec/app.rb +315 -0
  65. data/spec/controllers/accounts_controller_spec.rb +77 -0
  66. data/spec/controllers/addresses_controller_spec.rb +346 -0
  67. data/spec/controllers/admin_forums_controller_spec.rb +638 -0
  68. data/spec/controllers/comments_controller_spec.rb +380 -0
  69. data/spec/controllers/comments_controller_with_models_spec.rb +202 -0
  70. data/spec/controllers/forum_posts_controller_spec.rb +426 -0
  71. data/spec/controllers/forums_controller_spec.rb +694 -0
  72. data/spec/controllers/infos_controller_spec.rb +71 -0
  73. data/spec/controllers/interests_controller_via_forum_spec.rb +80 -0
  74. data/spec/controllers/interests_controller_via_user_spec.rb +114 -0
  75. data/spec/controllers/owners_controller_spec.rb +277 -0
  76. data/spec/controllers/resource_saved_spec.rb +47 -0
  77. data/spec/controllers/resource_service_in_forums_controller_spec.rb +37 -0
  78. data/spec/controllers/resource_service_in_infos_controller_spec.rb +36 -0
  79. data/spec/controllers/resource_service_in_interests_controller_via_forum_spec.rb +51 -0
  80. data/spec/controllers/tags_controller_spec.rb +83 -0
  81. data/spec/controllers/tags_controller_via_account_info_spec.rb +131 -0
  82. data/spec/controllers/tags_controller_via_forum_post_comment_spec.rb +144 -0
  83. data/spec/controllers/tags_controller_via_forum_post_spec.rb +133 -0
  84. data/spec/controllers/tags_controller_via_forum_spec.rb +173 -0
  85. data/spec/controllers/tags_controller_via_user_address_spec.rb +130 -0
  86. data/spec/controllers/users_controller_spec.rb +248 -0
  87. data/spec/lib/action_view_helper_spec.rb +143 -0
  88. data/spec/lib/bug_0001_spec.rb +22 -0
  89. data/spec/lib/include_actions_spec.rb +35 -0
  90. data/spec/lib/load_enclosing_resources_spec.rb +245 -0
  91. data/spec/lib/request_path_introspection_spec.rb +130 -0
  92. data/spec/lib/resource_methods_spec.rb +204 -0
  93. data/spec/lib/resources_controller_spec.rb +57 -0
  94. data/spec/models/comment_saved_spec.rb +24 -0
  95. data/spec/rspec_generator_task.rb +105 -0
  96. data/spec/spec_helper.rb +17 -0
  97. data/spec/verify_rcov.rb +52 -0
  98. 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