Creating a custom URL in Rails 5 Resource's show action - ruby-on-rails

Given the resource Events, I want /events/1 to navigate to /events/1/column_name in the URL bar when entered. Column name is a t.string :name in the Events DB migration. This column_name will need parameterize to be called on it before redirecting. Any ideas on how to get this done?
Example:
If you navigate to https://stackoverflow.com/users/4180797 the URL will automatically become https://stackoverflow.com/users/4180797/james-lowrey after loading. Holds true for 4180797/any-other-txt-here. So "James Lowrey" would be the name column, and it would become james-lowrey after calling parameterize on it.

Another option will be to use pushState with javascript/jquery, for example:
First, add the following script in show.html.erb
<script>
$(document).ready(function() {
history.pushState(null, null, "/events/<%= #event.id %>/<%= #event.name.parameterize %>");
});
</script>
This will change the url on load every time, no matter what comes after /:id.
Second, modify routes to accept anything after /:id (and ignore it):
Rails.application.routes.draw do
#...
get "/events/:id/*other" => "events#show"
end
This option has the added benefit of avoiding redirects if any text after /:id do not match, it will just get the id and replace any other text after that with #event.name.parameterize in the url (without any reload).

Ok this was really tricky to figure out, didn't even know I had access to a lot of these variables before.
First edit your config/routes.rb to accept id/other_stuff
Rails.application.routes.draw do
resources :events
get "/events/:id/*other" => "events#show" #if any txt is trailing id, also send this route to events#show
end
Next modify event_controller.show to redirect if the URL is incorrect.
def show
#redirect if :name is not seen in the URL
if request.format.html?
name_param = #event.name.parameterize
url = request.original_url
id_end_indx = url.index(#event.id.to_s) + (#event.id.to_s).length + 1 #+1 for '/' character
##all URL txt after id does not match name.parameterize
if url[id_end_indx..-1] != #event.name.parameterize
redirect_to "/events/#{#event.id}/#{name_param}"
end
end
end
This will result in the exact same behavior as the Stack Overflow examples gave in the question.

Related

route is redirecting to wrong path

This is what my routes currently look like:
which gives
On my homepage I have a create vacancy button
<%= link_to "plaats", new_employer_vacancy_path(:employer_id)%>
Which should be linked to the line from the first image
get '/employers/:employer_id/vacancies/new', to: 'vacancies#new', as: 'new_employer_vacancy'
In the vacancies_controller#new - create I have:
def new
#vacancy = Vacancy.new
#employervacancy = Employervacancy.new
end
def create
#vacancy = Vacancy.create(vacancy_params)
createEmployervacancy
redirect_to employer_vacancy_path(current_employer, #vacancy)
end
def createEmployervacancy
#employer = current_employer
Employervacancy.create(vacancy_id: #vacancy.id, employer_id: #employer.id)
end
But whenever I click the button I get redirected to some other method in my vacancies_controller that is totally irrelevant.
How is this even possible? Don't I clearly define that when that path is clicked he should go to vacancies#new? and not to vacancies#show_specific_employer_vacancies?
EDIT
After following the answers I am indeed being linked to the correct route.
First, it gave me this error.
After trying to pass the current_employer.id instead of #employer like suggested I got following error:
For your routes, you'd better to change into nested route for easily maintaining routes.
Remove these codes:
get '/employers/:employer_id/vacancies/:id', to:"vacancies#show_specific_employer_vacancies", as: "employer_vacancy"
get '/employers/:employer_id/vacancies/edit/:id' ...
get '/employers/:employer_id/vacancies/index' ...
get '/employers/:employer_id/vacancies/new' ...
path '/employers/:employer_id/vacancies/:id' ...
change into:
resources :employers do
resources :vacancies
end
Try to use basic routes here because you use standard simple form url. For example:
<%= simple_form_for(#employee, #vacancy) %>
The simple_form_for will generate url well if you use nested routes above.
Finally, in your link you have to add #employer_id
<%= link_to "plaats", new_employer_vacancy_path(:employer_id => #employer_id)%>
I hope this help you
Your router cannot tell the difference between your employer_vacancy and new_emplyer_vacancy routes because the :id parameter accepts anything. Because of this, when you point your browser to "/employers/5/vacancies/new", the route is taking your employer_vacancy route and assigning {:employer_id => 5, :id => "new"} instead of going to your new_employer_vacancy route (because routes are first-come-first-serve).
To correct this, add a constraint to your first route to ensure that only numbers (and not the string "new") is accepted into the employer_vacancy route:
get '/employers/:employer_id/vacancies/:id',
to: 'vacancies#show_specific_emplyer_vacancies',
as: 'employer_vacancy',
constraints: { id: /\d+/ } # <- This line
As Wes Foster said rails router is trying to find a first match.
It means that given a path /employers/999/vacancies/new your router looks through the routes and when it sees get '/employers/:employer_id/vacancies/:id he thinks that this route matches. So :employer_id is 999 and :id is new.
I'd suggest to put the route with :id at the end of employers routes:
...
get '/employers/:employer_id/vacancies/new'
...
get '/employers/:employer_id/vacancies/:id'
Btw this is better than adding a constraint because:
It is easier
It doesn't pollute routes file
Later you may want to change ids to be hashed or alphabetic and then you'd have to change the constraint

Changing the params in URL on rails

I want to change the :id param on the URL. I added to my routes.rb file something like:
match "articles/:name/edit", :to => 'articles#edit', :as => 'edit_article'
Thinking that :name would be readed by the server as params[:name] later for me in rails. I edited my article controller definition for edit so:
def edit
#article = Article.find(params[:name])
end
I get always the error couldn't find article with id=test and I was wondering why "id" instead of :name? I tried also changing match to get but I got the same.
I have also the default resources :articles still in my routes.rb file, don't know if there's something like a double rule working there.
The whole thing is that instead of ID numbers I would use names in my URL —not just the edit one, with the show method I could handle it, but not with edit/update/delete.
I was reading about routing but I can't figure out what I am doing wrong.
By default, find search by id.
You should replace it with find_by_name.
Advice: use friendly_id

Setting up a dynamic rails route

I have a controller and a view (ctrler)
controller
def index
...
end
def show
#text = params[:text]
end
end
View (show.html.erb)
<%=#text %>
routes.rb
resources :ctrler
match 'ctrler/:text' => 'ctrler#show'
If I fire the rails s server up and load up http://localhost:3000/ctrler/hiiiiiii I get nothing but if I load http://localhost:3000/ctrler?text=hiiiiii I get text!
Im still trying to get the hang of rails I'm used toPHP but can someone give me some guidance here am I on the right track or have I missed something out?
resources :ctrler
creates the following rule
match "ctrler/:id" => "ctrler#show"
This route conflicts with
match 'ctrler/:text' => 'ctrler#show'
In the event of a conflict, the rule that appears first takes precedence, so when you go to 'ctrlr/hiiiii', it is setting the id parameter to hiiiii, not the text parameter. Try to change routes.rb to
match 'ctrler/:text' => 'ctrler#show'
resources :ctrler
and see if that helps.

Problem Sending Value to New Action in Rails 3

I am currently trying to define my first new action in Rails 3, despite various problems I think things are almost done now. However, I as a final hurdle I am struggling to send the correct parameter value to my function...
My function is defined in items_controller:
def clickcountplusone
clickeditem = Item.find(params[:id])
redirect_to clickeditem.externalurl if clickeditem.update_attribute(:click_count, clickeditem.click_count + 1)
end
routes.rb contains:
match '/items/clickcountplusone', :to => 'items#clickcountplusone'
and the view contains:
<%= link_to image_tag( item.picture.to_s + ".gif", send("items_clickcountplusone_path", item.item_name)%>
The view page itself loads correctly (the one with the link on it), but when I click on the link I get an error:
Couldn't find Item with ID=clickcountplusone
{"id"=>"clickcountplusone",
"format"=>"whatever the name is"}
and rather than going to the external page, my browser tries to load:
http://localhost:3000/items/clickcountplusone.whatever the name is
Can anyone tell me how I should be calling the function so that the ID is the item_name and the external URL is visited rather than an incorrect one on my site?
Thanks in advance
It seems like this would be a normal route, instead of a RESTful route (this is fine). There are some places you have to change.
First, in your controller's action, you used params[:id] which is not set actually.
In this particular case, I would suggest you use params[:item_name] instead of id because you are really sending the item_name.
def clickcountplusone
clickeditem = Item.find_by_item_name(params[:item_name])
redirect_to clickeditem.externalurl if clickeditem.update_attribute(:click_count, clickeditem.click_count + 1)
end
Item.find could only be used if the parameter is one of the actual id / :all / :first / :last.
You are finding by the item_name, so you should use Item.find_by_item_name instead.
Second, you have to update you route too (or else you would need something like /you_path..?item_name=blahblahblah which is fine too if you don't mind)
get 'items/:item_name' => 'items#clickcountplusone', :as => :items_clickcountplusone
Third, you view. IMO, most of the time if you are using send but not writing library / really back end code, you probably misusing it.
<%= link_to image_tag("#{item.picture.to_s}.gif"), items_clickcountplusone_path(:item_name => item.item_name) %>
You don't have any variable in your match statement. Try something like
match '/items/clickcountplusone/:id', :to => 'items#clickcountplusone'
and
<%= link_to image_tag(item.picture.to_s + ".gif", items_clickcountplusone_path(:id => item.item_name))%>

Rails url needing posts/:id/the-name-of-post

I would like my rails url to look like:
/posts/345/the-great-concept
when i use the following in my posts model,
def to_param
"#{id}/#{name.parameterize.downcase}"
end
the urls look great upon mousover in the browser. and function correctly. however, once the page is loaded in the browser url it looks like:
/posts/345%2Fthe-great-concept
and to be clear, the "name" is just for good looks - the post is retrieved only by id. also i do not want to use a database slug approach.
how should i better approach this?
ps. don't want "/posts/345-the-great-concept" either ...
Its escaped because its not part of the path, but a param, so it needs to be escaped or you will be on the wrong uri.
def to_param
"#{id}-#{name.parameterize.downcase}"
end
edit: Okay, so the slash is indeed important; Here's how to tackle it:
Create a custom route for that:
# in config/routes.rb
resources :posts
match '/posts/:id/:slug' => 'posts#show', :as => :slug
Then create your slug method:
# in app/models/post.rb
def slug
title.parameterize.downcase
end
Then change your routes to the show action so the link to the fancy url:
# in any link to show; redirect after create, etc..
link_to slug_path(#post, :slug => #post.slug)
I created an app to test all this out, if interested, you can check it out at:
https://github.com/unixmonkey/Pretty-Path

Resources