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')
Related
I have a multilingual site in rails 3.2 that has some language-specific routes that map to the same action. Like:
For mydomain.fr
match "/bonjour_monde" => 'foo#bar'
For mydomain.de
match "/hallo_welt" => 'foo#bar'
To solve this I have used an advanced constraint when declaring the routes:
Application.routes.draw do
constraints(SiteInitializer.for_country("fr")) do
match "/bonjour_monde" => 'foo#bar'
end
constraints(SiteInitializer.for_country("de")) do
match "/hallo_welt" => 'foo#bar'
end
end
Where the SiteInitializer is just a class which responds to the matches? method and determines if the request is for the correct domain. This is actually only pseudo-code just demonstrating my setup.
class SiteInitializer
def initialize(country_code)
#country_code = country_code
end
def self.for_country(country_code)
new(country_code)
end
def matches?(request)
# based on the request, decide if this route should be declared
decide_which_country_code_from_request(request) == #country_code
end
end
This works fine. When requesting mysite.fr/bonjour_monde the app dispatches correctly and the paths are only bound to their specific domain.
mysite.fr/bonjour_monde => HTTP 200
mysite.fr/hallo_welt => HTTP 404
mysite.de/bonjour_monde => HTTP 404
mysite.de/hallo_welt => HTTP 200
Now, all is fine unless you start using things like url_for(:controller => 'foo', :action => 'bar'). If you do this, the constraints are not taken into consideration. This results in that the generated path from rails (Journey class), will be arbitrary.
If I somewhere in any view use url_for, like
url_for(:controller => 'foo', :action => 'bar')
rails will choose any arbitrary declared route that matches the controller action, probably the first one declared, skipping to check any advanced constraint.
If the user visits mysite.de/hallo_welt, and the view does something like:
= url_for(:controller => 'foo', :action => 'bar', :page => '2')
The output might be
mysite.de/bonjour_monde?page=2
^
wrong language
Actually, I don't use url_for specifically in code, but some gems like kaminari (paginator) do this and that's why you might be limited in using libraries that use the standard helper methods when generating paths.
Now, I'm not so sure if the Journey class should take the request context into consideration. But how would you approach this kind of problem?
link_to and helpers use the names of my models and their IDs while I want to have a couple of different, arbitrary, variables in my link. I have no problems to route them, I actually keep the default routing as well, but I suddenly stuck that I cannot easily generate an arbitrary link. For instance I want to have a link like ":name_of_board/:post_number", where :name_of_board and :post_number are variables set by me, and when I use link_to I get instead "posts/:id", where "posts" it's the name of the controller. While it's not hard to use an arbitrary id like
link_to 'Reply', :controller => "posts", :action => "show", :id => number
I cannot get how I can get rid of "posts". So, is there an easy way to generate a link by variables or to convert a string into link? Sure, I can add other queries to the line above, but it will make the link even more ugly, like "posts/:id?name_of_board=:name_of_board".
You can create additional routes for your posts resource in your routes.rb, or make standalone named routes:
resources :posts do
get ':name_of_board/:id' => 'posts#show', as: :with_name_of_board
end
get ':name_of_board/:id' => 'posts#show', as: :board
Now this
#name_of_board = "foo"
#post_id = 5
link_to 'Reply', posts_with_name_of_board_path(#name_of_board, #post_id)
link_to 'Reply', board_path(#name_of_board, #post_id)
would link to /posts/foo/5 and /foo/5 respectively.
You should first edit your route entry, for example a classic show route is the follow:
get "post/:id" => "post#show", :as => :post
# + patch, put, delete have the same link but with different method
And you can call it with the following helper
link_to "Show the post", post_path(:id => #post.id)
You can edit or create a new entry in the routes, applying the parameters you want to use, e.g.:
get "post/:id/:my_platform" => "post#show", :as => :post_custom
Then
link_to "Show the post with custom", post_custom_path(:id => #post.id, :my_platform => "var")
Finally, the link generated for this last entry is for example:
"/post/3/var"
Even in this situation, you can add some other params not defined in the routes, e.g.:
link_to "Show post with params", post_custom_path(:id => #post.id, :my_platform => "var", :params1 => "var1", :params2 => "var2")
=> "/post/3/var?params1=var1¶ms2=var2"
RoR match your variable defined in the routes when you render a link (remember that these variables are mandatory), but you can ever add other vars that come at the end of the url ("?...&..")
Right now I am finding routing and URL constructing within rails to be semi-confusing. I have currently matched the following for tags that are passed in when displaying/filtering data.
match '/posts/standard/' => 'posts#standard'
match '/posts/standard/:tags' => 'posts#standard', :as => :post_tag
match '/posts/standard/:tags' => redirect { |params| "/posts/standard/#{params[:tags].gsub(' ', '+')}" }, :tags => /.+/
However, now I want to add a 'skill' parameter that can only take one state; however, I am very confused by how I want to construct this within my URL. I cannot simply have...
match '/posts/standard/:tags/:skill' => 'posts#standard', as => post_tag, as: => post_skill
So, I am very confused by this at this point, does Rails offer any type of help for constructing URL's?
One way is to just keep your main route
match '/posts/standard/:tags' => 'posts#standard', :as => :post_tag
and handle the additional URL params as params. The url would look like:
/posts/standard/1?skill=something
and it is easy enough to inject the additional params, such as by
link_to post_tag_path(:skill=> 'something')
and your controller would then do
def standard
if params[:skill] == 'something'
...
else
...
end
end
Also, not sure about this, but your first line in your routes 'match '/posts/standard/' => 'posts#standard' may catch all of your routes since there is a match. If this is the case, simply move it to after the first line.
I have a Customer model and I want his controller to repond to a find method
I added this in my routes.rb file:
match 'customers/find/:name' => 'mymodel#find' resources :customers
In my controller I have something like this:
def find
#customers = Customer.fin_all_by_name(params[:name])
end
in my views, when I need to create a link for that resource I'm using this:
= link_to 'Find By Name', :controller => "customers", :action => "find", :name => #customer.name
now, I'm trying integration tests with cucumber and I have a problem: I have to create a step definition in my customer_step.rb file for customers having same name:
when /^customers having same name as "(.*)"/
url_encode('/customers/find/' + $1)
now that line doesn't work, it says undefined method `url_encode'
I need to encode that string because if the name contains a space I get obvious errors.
I'm new to ruby and rails and I think I'm missing something here.
Am I following the right pattern to accomplish this search?
Should I define an helper method in my controller to generate search urls?
Is it correct that line I have in my _step.rb file?
I don't want urls to be like this: customers/find?name=test
but: customers/find/test
I just sorted it out, I slightly modified my match expression and added the :as parameter
and this gave me the possibility to call find_path() helper method
match 'customers/find/:name' => 'customers#find', :as => :find
Is this correct?
Using :as should indeed create a route helper for you. If you want to get a list of your matched routes, to which controller/action they route, and the name of the route helper, use rake routes in console.
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?) :)