I am using Rails 2.3.2 but I'm sure this applies to newer versions as well. I would like to define a custom action in ApplicationController. However, I don't want to add a custom route to every single controller subclass which uses this action. Is t开发者_如何学Gohere an easy way to do this?
My first inclination was to just route directly to the ApplicationController, since the method does not need to be overridden by any subclasses. But I don't think Rails lets you route to the ApplicationController anymore.
Somebody else suggested something like this:
map.connect ":controller/:action", :controller => my_regex, :action => my_regex
But I'm wondering if this has the potential of conflicting with or overriding other routes? Or if there's generally a better way? Thanks!
I don't think this is a case of modifying ApplicationController
, but of monkey-patching the code in ActionDispatch::Routing
to include the new actions you want. This seems like a pretty crazy thing to do in the scheme of things as there's no standard way to augment or extend the usual REST actions. I hope you've got a good reason for doing this.
In looking through the code you can see where the default actions are defined, and you might be able to introduce a new one. Rails 3 has a slightly different structure but the idea is the same:
class ActionDispatch::Routing::Mapper::Resources::Resource
ENHANCED_DEFAULT_ACTIONS = DEFAULT_ACTIONS + [ :myaction ]
def self.default_actions
ENHANCED_DEFAULT_ACTIONS
end
end
You'll have to modify ActionDispatch::Routing::Mapper::Resources#resources
to behave differently, too, but you didn't specify if you're talking about a collection, new or member type of action so you'll have to just copy and modify the routine to behave as you want.
if you are declaring this controller as a resource such as map.resource
then you will have to use the default actions or create your own by adding a member
or collection
to that resource.
map.resources :post, :member => {:update_live_boolean => :post }, :collection => {:get_all_live_posts => :get}
Otherwise if you have the old routing format and are not using REST
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
Then all you need to do to link to a custom controller is provide the :controller' and
:action/
:id` variables when needed
<%= link_to "New Custom Controller", {:controller => "new_custom_controller", :action => "index"%>
This is the part of your post that concerns me:
I don't want to add a custom route to every single controller subclass which uses this action.
The rails community is rightly getting more and more careful about not over-defining needless routes. It's getting more common to see things like this:
map.resources :comments, :only => [:new, :create]
In the above example, only the new
and create
routes are generated. It's better security, and cleaner routing. While I'm not directly answering your question of how to make a new route available to every resource, I'm saying that rails best practices would discourage it. Add the custom route only to the resources that will actually use it.
In the end I decided to consider my js_form_builder a resource of its own, so I have a controller that will deliver it. The controller takes optional resource_name and id parameters. If provided, I can instantiate an object like so:
@object = params[:resource_name].classify.constantize.find(params[:id])
Then I simply send down a js.erb template that has all my js_form_builder goodness contained within. If @object has been instantiated, then I can create a form_for @object, then loop through its attributes and create methods on the javascript form builder object that will return inputs for each attribute, using Rails FormBuilder tags to create them.
For example:
window.FormBuilder = function() {
var builder = {};
builder.form = function() {
var js = "";
js += '<%= form_for @object do |f| %>';
<% @object.attributes.each do |name, val| %>
var methodName = '<%= name.camelize(:lower) %>';
<% if val.class == String %>
builder[methodName] = $('<%= f.text_field name.to_sym %>');
<% end %>
<% if val.class == TrueClass || val.class == FalseClass %>
builder[methodName] = $('<%= f.check_box name.to_sym %> <%= f.label name.to_sym %>');
<% end %>
<% end %>
js += '<% end %>';
return $(js);
};
builder.newForm = function() {
var js = "";
js += '<%= form_for @object.class.new do |f| %>';
js += '<% end %>';
}
return builder;
}
I'm not currently sure how useful these inputs will be, since I can't think of a scenario where I wouldn't just use html.erb for these. But it was sure fun making it work! :)
精彩评论