Inheriting and routing a custom action from ApplicationController in Rails - ruby-on-rails

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 there 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! :)

Related

Manage Single Rails form to other controller with create and update action

i'm new of rails. I use rails 3.0.5 .
I have an EMPLOYEE resource, but I would like to manage it with another extern controller (emp_profile_controller).
This extern controller (emp_profile_controller) manages some actions (index, new_employee, create_employee, edit_employee, update_employee ecc.. ) .
My routes for this controller are :
controller :emp_profile do
get 'emp_profile' => :index
get 'emp_edit_profile' => :edit_employee
put 'emp_edit_profile' => :update_employee
get 'new_employee' => :new_employee
post 'new_employee' => :create_employee
get 'emp_list' => :emp_list
end
How can i use one form to handle both Create and Update actions in this controller ?
I tried with :
form_for(#employee, :url => { :controller => "emp_profile"}) do |f|
but it doesn't work.
If i manage only one action at time (create OR update), url_for works, for example :
form_for(#employee, :url => { :controller => "emp_profile", :action => "update_employee" }
but how can i handle both actions with one form ?
Thanks for your availability and I apologize if I asked a stupid question.
EDIT
For now, i solved checking if object exist in the form file, if exist i set a variable with the UPDATE action path, else, i set a variable with the CREATE action path. So in the form_for statement i use url_for with the above variable.
<% if #employee.new_record?
action = "create_employee"
method = "post"
else
action = "update_employee"
method = "put"
end
%>
form_for(#employee, :url => { :controller => "emp_profile", :action => action }, :method => method
I don't think it is the best way but it works and i can use only one form file.
As your model name and controller name are different, you can add this line to your routes
resources :employees,:controller=>"emp_profile",:path=>"emp_profile"
Change the method names of create_employee,update_employee to create and update respectively.
And change your form_for as given below
<%= form_for #employee do |f| %>
....
<% end %>
First of all, if you want to update something, this object should exist.
How do plan to find it out, I don't know (cause there different ways, depends on background).
There are 2 ways of solving this issue.
You can just check if object exist in view file, and if exists, renfer form for update, else for create.
Other way is to do it in controller.
For example:
def create
#employee=Employee.find_by_name('Jack Black') #for example
if #employee!=nil
render :action=> 'update'
else
#employee=Employee.new(:employee)
#employee.save
end
as i understand you want to execute two different actions on the same controller using a form submitting, this is not possible, you can only execute one action using a form submitting,
because the form is reaching to an action controller that action is suppose to render some view at the end of it's execution code, if it was possible to use to actions on form submitting how rails will know which view to render??? (that's why it's not possible).
if you want to do some more code execution at the controller, the right way to it is to call a method with some code in it that you want to execute, that method should be in the model,
because it is a good practice to write all massive chunks of code in the model and leave the controller as light from code as possible :-)
hope this helps.

How do I force Rails to use the superclass name in link_to helpers when using STI?

I'm using STI with a Rails 3.2 app. I want to force Rails to use the superclass name in link_to helpers (or any where else when it's generating paths) and not the subclass name.
So, <%= link_to current_user.name, current_user %> produces /:class_name/:id (class name can be "Moderator," "Member," etc...).
I would like it to produce /users/:id, where users does not change to the name of the subclass. I know I can change current_user to user_path(current_user), but I prefer to use the shortcut, letting Rails figure it out.
Is this possible?
I think you should define url helpers, something like this
def moderator_url record
user_url record
end
Or just use aliases
alias :moderator_url :user_url
This is code which rails use for url generation when you pass a record as a option
https://github.com/rails/rails/blob/537ede912895d421b24acfcbc86daf08f8f22157/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb#L90
Use the named route:
<%= link_to current_user.name, user_path(current_user) %>
For links, I can get around this by adding a resource:
resources :owners, path: 'users', controller: 'users'
For forms, I need to specify generic form. My initial form was
= simple_form_for #user do |f|
To make this work, I had to specify the path and the method, while using a generic name instead of passing the user object to the form directly:
= simple_form_for :user, url: user_path(#user) do |f|

Passing path to current_page method

I have a helper method to help to determine whether or not a navigation menu item should be active/inactive.
def events_nav_link
nav_item = 'Events'
if current_page?(events_path) # additional conditions here
# do nothing
else
nav_item = link_to nav_item, events_path
end
content_tag(:li, nav_item + vertical_divider, :class => 'first')
end
I want this link to be inactive not only for the events_path page, but for other pages as well. Problem is that I don't know what param to pass the current_page?(path) method based on this route:
map.list_events '/events/:league/:year/:month/:day', :controller => 'events', :action => 'list'
This route generates a path such as /pow or /chow/2011/09/25. Leagues can be pow, chow or wow.
I like unobtrusive JS approach with add/remove classes and unwrap() deactivated links, but it requries specific rules.
The Rails way is to use link_to_unless_current built-in helper to highlight and unlink on current page href.
You're looking for named routes. In your routes.rb file, add
:as => :foo
after the route. Then use
if current_page(events_path) || current_page(foo_path)
in your condition.
This is what I do:
application_helper.rb:
def already_here?(this_controller,this_action)
controller_name == this_controller && action_name == this_action ? true : false
end
in a view:
<%= already_here?("users","index") ? "Manage Users" : link_to("Manage Users", users_path, :title => "Manage Users.") %>
Of course, you can abstract it further by passing the title of the link and path to the helper if you want, but that's a bit of a hassle (to me).
UPDATE: never mind, see mikhailov's answer- much cleaner (why reinvent the wheel?) :)

Rails: set a value using a link

I need help trying to create a link that submits an edit form.
Let's say I have a list of objects
Object - Color - Own?
Ball - Red - false - [button]
Hat - Blue - true - [button]
Shoe - Green - false - [button]
When I click on the [button] I want to set "Own?" to True.
Routes
resources :toys
Controller
def edit
#toy = Toy.find(params[:id])
end
def update
#toy = Toy.find(params[:id])
if #Toy.update_attributes(params[:toy])
flash[:notice] = "Toy Updated"
redirect_to #toy
else
render 'edit'
end
end
View
<h2>Toys</h2>
<% if #toys %>
<% #toys.each do |toy| %>
<%= toy.name %> - <%= link_to 'Set Own', edit_toy_path(:id=>toy.id, :owned=>'true')%>
<br/>
<% end %>
<% else %>
None
<% end %>
This is all about how you setup your controller actions. I'm not totally sure I understand how you want to use yours, but I have a similar case that I'll show you which I think you should be able to adapt to your situation.
In my case, I have a menu button that sets a value in the session to either keep a menu panel open or closed across any views a user looks at.
First, you need a controller action that is going to do the work you're interested in. I created a "SharedController" which handles application-wide things that don't belong to any particular view or other controller.
class SharedController < ApplicationController
# Used by AJAX links to set various settings in shared views
def edit
session[:admin_menu] = params[:admin_menu].to_sym if params[:admin_menu]
session[:advanced_search] = params[:advanced_search].to_sym if params[:advanced_search]
render :nothing => true
end
end
This controller action can set one of two values in the session, either: "admin_menu" (boolean) or "advanced_search" (boolean). Then certain views ask whether the session value for admin_menu or advanced_search is true, and if so they show the view.
You could use the same logic. Something like:
def edit
object= Object.find(params[:object_id])
object.own = params[:own]
object.save
end
To trigger this controller action with a link you need to have a route that accepts GET requests. edit is a logical choice.
resource :shared, :only => [:edit], :controller => 'shared'
Note: I think SharedController makes more sense than SharedsController, and edit_shared_path makes more sense than edit_shareds_path, so I had to specify :controller => 'shared' in my routes.rb.
Then you just need a link to a url with params. To add params onto a path you just add them to the path helper, like so:
edit_shared_path(:key => 'value')
You can retrieve these params in your controller via:
params[:key]
Make this a link like so:
link_to 'Set Own to True for This Object', edit_shared_path(:object_id=>object.id, :own=>'true')
NOTE: It's best to do this via AJAX, so be sure to set :remote=>true. If you don't use AJAX then you need to specify a redirect in your controller for what page should be loaded after this link is triggered.
In the case of my admin menu preference link, I need a link with two possible states. I generate these using a helper:
# Shows Admin Menu Button
def admin_toggle_button
if session[:admin_menu] == :on
link_to( 'Admin Tools', edit_shared_path(:admin_menu => :off), :remote=>true, :class => 'selected', :id => 'admin_toggle_button', :title => 'Hide Admin Menu' )
else
link_to( 'Admin Tools', edit_shared_path(:admin_menu => :on), :remote=>true, :id => 'admin_toggle_button', :title => 'Show Admin Menu' )
end
end
In a view I just call this using admin_toggle_button. You can do something similar if you like, but it's optional.
I hope that gets you on the right track, let me know if you have any questions.
EDIT: Based on your comment:
Links issue GET requests, which mean you're going to the EDIT action. See: http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions
A further issue, you have resources :toys instead of resource :shared (which I used for this purpose). This means your link helper is already expecting a specific toy to edit, rather than handling a singular resource. See: http://guides.rubyonrails.org/routing.html#singular-resources
Your link would work if you changed it to be:
link_to 'Set Own', edit_toy_path(#toy, :owned=>'true'), :remote => true
... and set your edit action in your controller to the following:
def edit
#toy = Toy.find(params[:id])
#toy.owned = params[:owned]
if #toy.save!
head :ok
else
head :internal_server_error
end
end
See: http://guides.rubyonrails.org/layouts_and_rendering.html#using-head-to-build-header-only-responses
Now, be aware, you really should only do this with AJAX links, and you should normally not do it with your "real" controller. The reason is, now this is the only action that can be processed by EDIT, so your normal toys#edit view would no longer work.
You can get around this by create a new action and a new route, for instance:
resources :toys do
member do
get 'set_ownership'
end
end
Then simply take the same method above and call it set_ownership instead of edit. IE:
class ToysController < ApplicationController
...
def set_ownership
...
end
end
Hope that all makes sense.
The edit_toy_path method that your link_to method is calling is going to the edit action inside your controller. It's not going to the update method that I'm guessing you want.
Your link_to will need to change to something like:
<%= link_to 'Set Own', toy_path(:id=>toy.id, :owned=>'true'), :method => :put %>
But I question this particular approach. I don't think the variable will update correctly in the update action because it is not namespaced to the proper params[:toy] object that update_attributes is expecting. And in my quick and dirty tests I couldn't get it to namespace properly.
When I have a situation like the one that you are describing I usually setup another action, like toggle_ownership and I call that from my link_to with a :remote => true option. Then the controller toggles the attributes as desired.
Thus, my routes looks something like:
resources :toys do
member do
put :toggle_ownership
end
end
And my view looks like
<%= link_to 'Set Own', toggle_ownership_toy_path(toy.id), :method => :put %>
The controller sets the variable and renders back a toggle_ownership.js.erb file that updates the appropriate section of the page.
Hope that helps!

Adjust routes.rb for method call in Controller

I have an online portfolio created in Rails featuring different projects. I want to be able to filter the projects by keywords. My approach is to define a method for each keyword in the ProjectsController and link the keywords to call the methods.
For Example Keyword = graphic_design:
<%= link_to 'Graphic Design', :action => "filter_"+#project.keyword.to_s %>
That way I get the error:
Couldn't find Project with ID=filter_graphic_design
This is quite obvious to me. My question: Is there a way to tell the routes.rb to behave differently only for 'filter_' methods? Any other suggestions?
Your approach is wrong. Why do you need a filter_ method for each keyword in the first place? Its pretty simple a solution. First define a named route in your routes.rb:
map.filter '/projects/:filter_this_for_me', :controller => 'projects', :action => 'filter'
In your views,
<%= link_to 'Graphic Design', filter_path("filter_" + #project.keyword.to_s) %>
In your filter action,
def filter
logger.info("Parameters that is being received: #{params}")
filter_what = params[:filter_this_for_me]
if(!filter_what.nil? && !filter_what.blank?)
# Here filter_what will have "filter_graphic_design" or "filter_something"
# With which you can filter any data that you want.
# Filter your projects here.
end
end
I think something like this could work
map.connect "/projects/filter_{filter}", :controller => 'projects', :action => 'filter'
Haven't tried it though

Resources