How to create an arbitrary link in Rails? - ruby-on-rails

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&params2=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 ("?...&..")

Related

Better routes in rails with params

I've been working on a movie application in Rails. I have a controller/view for the actor. When passing params to the actors controller i want the URL to be pretty. Now it looks like this: http://localhost:3000/actors/show?actor=Hayley+Atwell and i want it to look like /actors/show/Hayley+Atwell or /actors/Hayley+Atwell.
How do i do this? My link in the movies view is:
<%= link_to a.name, {:controller => 'actors', :action => 'show', :actor => a.name}, :class => "label label-default" %>
My routes.rb is now like this:
get 'actors/show'
I recommend you use friendly_id gem. It perfectly satisfies your needs!
https://github.com/norman/friendly_id
You can use the following in your ./config/route.rb file:
get '/actors/:actor', to: 'actors#show'
I'll give a series of refactoring advice as follow:
Try to make use of the actor's id instead of the name
Change the route to: get '/actors/show/:id', to: 'actors#show'
Then, you can now change the link in your view to something like:
<%= link_to a.name, {:controller => 'actors', :action => 'show', :id => a.id}, :class => "label label-default" %>
Note: the : part in the :id of your route means that this could be interchanged to anything, and whatever comes in that place will be interpreted as the id. So, in /actors/show/7, the id of the actor is 7, you can now find the actor with this id in your controller action, and do anything you want with it.
If I'm right than you try to create RESTful routes anyway.
So I would recommend you to use the Rails' resources method in your routes:
Rails.application.routes.draw do
resources :actors # If you want only the :show action than add:
# , only: [:show]
end
This automatically creates the proper routes for you.
To get the names into the URL you should use the friendly_id gem. It has a great community and is very well tested.

Action Controller error. Url Generation error. No route matches

I'm working through the "Ruby on rails 3 essential training" on lynda.com and am having an issue while generating my server. So far I have a subjects_controller.rb, linked to my views folder, to the file list.html.erb. My error when trying to start the server is:
No route matches {:action=>"show", :controller="subjects", :id=>1}
In my list.html.erb file I have written the code:
<td class="actions">
<%= link_to("Show", {:action => 'show', :id => subject.id}, :class => 'action show') %>
<%= link_to("Edit", '#', :class => 'action edit') %>
<%= link_to("Delete", '#', :class => 'action delete') %>
</td>
My subjects_controller.rb looks like:
class SubjectsController < ApplicationController
def list
#subjects = Subject.order("subjects.position ASC")
end
end
I have double checked to make sure I have everything written the same as the instructor but there seems to be a missing link. Any ideas? If I totally cut out the action:
<%= link_to("Show", {:action => 'show', :id => subject.id}, :class => 'action show') %>
Then the server starts up. There must be a problem here but I'm not sure what it is. Also when the instructor inputs link_to on his text editor, the txt turns a different color and mine does not. Same thing with his "#" instance variable. Mine doesn't change color. Not sure if this means anything either. Thanks for any input!
Here is my config/routes.rb file:
Rails.application.routes.draw do
root :to => "demo#index"
get 'demo/index'
get 'demo/hello'
get 'demo/other_hello'
get 'subjects/list'
end
Short version: The error message is telling you exactly what is wrong. You have no route that matches, because while your action is named list, your link specifies :action => 'show'.
Longer version: The second argument to the link_to helper is supposed to tell Rails what URL to generate for the link, usually by specifying one of your routes by name, but sometimes (as in this case), by specifying the action (and optionally the controller). You're specifying the action show. The subjects controller is implied. Therefore, Rails is trying to find a route (in the ones defined in your routes.rb) that GETs the SubjectsController#show action. However, as you can see from your routes.rb, you only define one route on the SubjectsController, and that's list.
If you're ever confused about what routes you have or what their names are, you can use the rake routes task to list them all out in a nice readable format.
Edit to respond to followup question:
The instructor is telling me that when you generate a controller and
action that its supposed to add a route to the routes.rb folder. This
worked for me earlier but when creating these actions that I'm having
trouble with now, it didn't generate anything in the routes.rb folder.
Do you know why that is?
When your instructor says 'generate', they probably mean 'use the rails generate command'. When you use the generator to create a controller and specify the actions in it, the it will also add those actions to the routes file.
If, on the other hand, you write the action into an existing controller (including using the generator for the controller but not specifying actions), or create the controller file yourself, you'll have to update the routes file manually. If you are using the generator and specifying actions and aren't getting updated routes, I'm not sure what's going on.
Personally, I prefer to write my routes by hand anyway - the generator often doesn't get them exactly right.

Rails: Generated URL has double backslash

In my Rails project, in my index view, I have a link
<%= link_to 'Show all posts', show_all_path %>
In routes.rb, I have a route:
match "show_all" => "Posts#show_all"
When I click on that link, it goes from
http://<domain name>/my_rails_project
to
http://<domain name>/my_rails_project//show_all
It works fine, but I'm wondering why there are two backslashes in front of show_all instead of one. And can I make it so that only one backslash appear?
I think your route needs more information:
`match "/:project_name/show_all" => "posts#show_all", :as => "show_all"
In your view:
link_to 'Show all posts', show_all_path(#project.name)
This assumes you have a #project variable in the page you're viewing.
try use get
get "show_all", :to => 'posts#show_all', as: 'show_all'

Routing more than one action to the same controller and action

I am trying to get something like this working on my Rails app:
match '/:language', :to => 'posts#search_result'
match '/:tag', :to => 'posts#search_result'
match '/:language/:tag', :to => 'posts#search_result'
I am using this search_result action to filter some posts depending of the language and the tag.
The problem is that sometimes :tag will be nil or :language will be nil; so i have these 3 possibilities when calling the action:
<%=link_to "Spanish", {:controller => 'posts', :action => 'search_result', :language => "spanish"} %>
<%= link_to "Spanish", {:controller => 'posts', :action => 'search_result', :language => "spanish", :tag => #tag} %>
<%=link_to "#{tag.name}", {:controller => 'posts', :action => 'search_result', :tag => #tag} %>
And I am expection to have URLs like:
/spanish (for the first case)
/spanish/rails (where rails is a tag, for the second case)
/rails (for the third case)
But right now i am getting the rigth thing for the first and third case, but for the second case i am getting:
/spanish?tag=rails
or again /spanish (depending on if i had selected a tag first or a language first).
I hope i explained myself right. Any idea??. thanks!.
The router cannot tell the difference between a :language and a :tag.
Just because your routes say "language" and "tag" when you are constructing your code in the view.. remember that in the html this has been translated into just plain ole URLs eg /spanish or /rails
the route then has to be figured out from this URL.
Now as I said, the router can't tell that a particular word is a language or a tag... and the plain-ole-URL doesn't have the word "tag" or "language" in it anymore... so your two routes here:
match '/:language', :to => 'posts#search_result'
match '/:tag', :to => 'posts#search_result'
are both the same kind of URL
Just a single token after the slash. Here are some examples that will match that route:
/greek
/spanish
/rails
/urdu
/whatever
They will all match the first route that matches on "a single token after a slash"... which means your router will match all of them to the "language" route and will never ever match the "/:tag" route, because it's already matched on the route above.
he he: it's all greek to the router ;)
Edit:
Hi, this is helping me a lot to understand how routing works.. but still i can't see it clear. I understand what you said, and so basically i understand i should do something like match '/tags/:tag to at least only route to posts#search_result the URLS starting by /tag .. what would be a solution??
yes, "/tags/:tag" would be clear and unambiguous, but if you want it to truly flexible in tag vs language you would be better served by the simple:
match '/posts/search', :to => 'posts#search_result'
which can use any of your link_to examples above to generate eg:
/posts/search?tag=rails
/posts/search?language=spanish
/posts/search?language=spanish&tag=rails
It's also far more clear what is being passed and why.
The description of the third URL is "I'm searching for a set of posts which have language = spanish and tag = rails"
Your URL should reflect the resource (which in this case is a set of posts) everything else is better done as query params.
Instead of defining /:language and /:language/:tag separately, define them together, with /:tag as an optional URI element.
match '/:language(/:tag)', :to => 'posts#search_result'
I believe routes are matched (and URIs generated from them) in the order that the routes are defined. You defined /:lang before you defined /:lang/:tag, so it matched /:lang and made :tag a GET parameter. I suppose you could optimize the ordering of your definitions, but I believe using the above syntax is the preferred method.

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