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?) :)
Related
I'm trying to write a generic route that will allow me to refer to it by the controller's action.
I'm using the following line:
match ':action' => 'pages#:action', :as => 'action'
let's say an action named `foobar' in the pages controller. I'd like to be able to write
link_to 'Click Me', pages_foobar_path
in a view. The problem is that I get the error Invalid route name: ':action' when I try to write that route.
Mind you, the line
match ':action' => 'pages#:action'
without the :as parameter works perfectly, but then I have to manually write the path, as such:
link_to 'Click Me', '/pages/foobar'
any way around that?
If dynamic means "recognize my actions when Rails starts up and generate routes dynamically":
It's not something that I would do, but it does what you want it to do without any redirection nor method_missing runtime overhead.
In config/routes.rb
controller_filenames = Dir.new("#{Rails.root}/app/controllers").entries
controller_filenames.each do |filename|
# you could restrict to only the pages_controller.rb on the next line,
# and in that case, you could simplify even more (see next example)...
if filename =~ /_controller\.rb$/
controller_name = filename.sub(/.rb$/, "")
controller_route_name = controller_name.sub(/_controller$/, "")
controller = controller_name.camelize.constantize.new
controller.action_methods.each do |action|
# if you don't want the controller name in your path match, just omit it...
match "#{controller_route_name}/#{action}" => "#{controller_route_name}##{action}", :as => "#{controller_route_name}_#{action}"
end
end
end
If you only want to do this for your pages_controller.rb file, then:
controller_name = "pages_controller"
controller_route_name = "pages"
controller = controller_name.camelize.constantize.new
controller.action_methods.each do |action|
# I've removed the controller_route_name from the match here...
match "#{action}" => "#{controller_route_name}##{action}", :as => "#{controller_route_name}_#{action}"
end
Now, if dynamic means "generate a route whenever I dynamically generate a new action":
You could really play with fire. Any of your existing actions can define new actions and routes. For example, I could define a route in config/routes.rb (but this could be any existing route):
match '/dynamic_define' => 'application#dynamic_define'
Couple that with a method in ApplicationController (again, this could be any existing action):
def dynamic_define
method_name = params[:mname]
self.class.send(:define_method, method_name) {
render :text => "output from #{method_name}"
}
Rails.application.routes.disable_clear_and_finalize = true
Rails.application.routes.draw do
match "/#{method_name}" => "application##{method_name}", :as => "application_#{method_name}"
end
render :text => "dynamic_define just created a new action named #{method_name}"
end
In your browser, you can visit:
/dynamic_define?mname=my_new_dynamic_action
# browser renders "dynamic_define just created a new action named my_new_dynamic_action"
And then visit:
/my_new_dynamic_action
# browser renders "output from my_new_dynamic_action"
I think you can get as far as:
link_to 'Click me', pages_path(:action)
by redirecting
match ':action' => 'pages#:action'
match '/pages/:action' => redirect(":action") # pages_path(:action) will match
This is less typing than the approach suggested in the first answer, but seems less expressive if anything.
I suppose you could override method_missing in your view class to catch pages_[stuff]_path and generate the proper string, e.g.
def method_missing(name, *args, &block)
if name =~ /^pages_[a-z]*_path$/
"/#{name.to_s.gsub!(/^pages_/,'').gsub!(/_path$/,'')}"
else
super
end
end
Forgive me if my method_missing knowledge or regex capabilities are lacking - hopefully this is helpful directionally at least.
If you write your route like that, the right way to access it is:
link_to 'Click me', action_path(:action => 'foobar')
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!
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! :)
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
I created the following route:
map.todo "todo/today",
:controller => "todo",
:action => "show_date"
Originally, the 'show_date' action and associated view would display all the activities associated for that day for all the Campaigns.
This ended up being very slow on the database...it would generate roughly 30 records but was still slow.
So, I'm thinking of creating a partial that would first list the campaigns separately.
If someone clicked on a link associated with campaign_id = 1, I want it to go to the following route:
todo/today/campaign/1
Then I would like to know how to know that the '1' is the campaign_id in the controller and then just do its thing.
The reason I want a distinct URL is so that I can cache this list. I have to keep going back to this and it's slow.
NOTE: It's possibly the problem actually is that I've written the queries in a slow way and sqlite isn't representative of how it will be in production, in which case this work-around is unnecessary, but right now, I need a way to get back to the whole list quickly.
The code above by #Damien is correct but incomplete. It should be:
map.todo "todo/today/campaign/:id", :controller => "todo", :action => "show_date"
in your views all you have to do is:
<%= link_to "Campaign 1", todo_path(:id => 1) %>
or simply
<%= link_to "Campaign 1", todo_path(1) %>
and the particular campaign id can be fetched using params[:id] in your action.
And yeah, sqlite is not production ready.
EDIT: The latter part is quite easy to implement:
However, you have to change the route slightly,
The route will now become,
map.todo "todo/today/:campaign/:id", :controller => "todo", :action => "show_date"
in your views:
<%= link_to "Campaign 1", todo_path(:campaign => "campaign", :id => 1) %>
In your todo controller, show_date action:
def show_date
#IF YOU ARE USING THIS REPEATEDLY IN LOTS OF DIFFERENT ACTIONS THEN A BETTER PLACE FOR THIS WOULD BE AS A HELPER IN application_controller.rb
if params[:id].nil? && params[:campaign].nil?
#DO SOMETHING WHEN BOTH ARE NIL,
elsif params[:campaign]!="campaign"
#DO SOMETHING WITH CAMPAIGN BEING SOMETHING OTHER THAN "CAMPAIGN"
elsif params[:campain]=="campaign" && params[:id].nil?
#DO SOMETHING WITH ID BEING NIL.
else
#FIND YOUR CAMPAIGN HERE.
end
end
Hope this helps. :)
Just with the following :
map.todo "todo/today/:id",
:controller => "todo",
:action => "show_date"
This will create the /todo/today/:id url where id is whatever you set in the url.
You can then access it in your controller with params[:id].
You might be interested in reading Rails Routing from the Outside In, particularly the section about resources.