Routes with optional parameters - ruby-on-rails

Following attempt seems to be functional, but is not the 'clean' result that I am trying to archive.
Following route
get "/learn(/:category)", to: "users#index", as: "learn"
Should be useable for something like "/learn/technology" - Which works, if entered manually in the address bar.
If I tough try to achieve similar in my views, I get the following: "/learn?category=technology" - Which well, technically works, but is not what I want.
I'm using the following inside my view:
- Category.promoted.limit(7).each do |category|
%li.category-button
= link_to learn_path(category) do
= button_tag "", :class => "#{category.name}"
= content_tag(:span, category.to_s, :class => 'category-head')
And my Category Model looks the following:
class Category < ActiveRecord::Base
has_many :skills
validates_uniqueness_of :name
scope :promoted, lambda { where(:promoted => true) }
def to_s
read_attribute(:name).titleize
end
def to_param
name.parameterize
end
end
How would I achieve the 'cleaner' solution?
Edit:
Following works - but there must be a better solution than that?
get "/learn", to: "users#index", as: "learn"
get "/learn/:category", to: "users#index", as: "filter_learn"

Try changing your link to the following:
...
= link_to learn_path(category: category.name) do
...

You may use url_for to solve the problem.
Suppose I have UsersController with index action and this in routes.rb:
resources :users, only: [:index] do
collection do
get ':kind', :to => 'users#index'
end
end
Then when I'm on /users page I can use the url_for this way:
= link_to 'Kind1', url_for(kind: :students)
which will produce path:
/users/students
If I'm on some another page (another controller or another action), then I shoud provide more info. For example when I'm on another controller's page then I should provide both controller and action params if target action is not index (if target action is index then it is sufficient to provide only controller):
= link_to 'Kind1', url_for(controller: :users, action: :index, kind: :students)
it produces the same path:
/users/students
While using users_path(kind: :students) you'll get:
/users?kind=students

Related

Add new action to route

I got these actions in users controller
class UsersController < ApplicationController
def index #default action
...
end
def new #default action
...
end
def another_new
...
end
def create
...
end
def another_create
...
end
end
I want to be able to
/users/another_new and call from some sort of link :method => :another_create
to make /users/another_new
I got the following config/routes.rb
get '/users/another_new' :to => 'users#another_new'
resources :users
my question is if this is the correct way to add the get and how I add the another_create method.
in your config/routes.rb file do this
resources :users do
collection do
get 'another_new'
post 'another_create'
end
end
Also have a look HERE for clear understanding of concepts.
Hope this helps you dude :)
try this in routes.rb
match "/users/another_new " => "users#another_new", :as => 'another_new'
then you can do
link_to "MyUrl", another_new_path
this should work. Good luck.
Also note that you shouldn't have :method => :another_new. Your options for :method are :get, :put, :post, and :delete, and the one you use should match how you defined the action in routes.

rails custom rest route with parameter

I have a questions controller and an associated model and a number of rest routes. Here is how it's set up in routes.rb:
resources :questions
I want to add a custom route that has the format /questions/widget/ID (where ID is the id of the question for which I want to generate a widget). I want this to be processed by the "widget" action in my questions controller. I've tried a number of things such as:
resources :questions do
member do
get 'widget/:id'
end
end
But nothing is working. I'm sure I'm missing something simple. Any ideas? Thanks in advance.
You do not have to specify the id since you are inside resources. It should look like:
resources :questions do
member do
get 'widget'
end
end
You can get more information from the Rails Guide. Look at section 2.9.1.
Edit: I just noticed that you are trying to match get /questions/widget/:id. This will set up a route for get /questions/:id/widget. This is more in line with Rails convention. If you really want it the other way, you need to set up a custom match statement:
match "/questions/widget/:id" => "questions#widget"
However, I would stick with convention.
I know it is old, but looking to fix another routing problem I ended here, it is possible, to do what you are asking for, here is an example
resources :articles do
get 'by_tag/:tag' => :by_tag, on: :collection
get 'by_author/:author' => :by_author, on: :collection
resources :comments, except: :show
end
now you have /artices/by_tag/:tag . The trick was to use on:collection.
Obviously don't forget to add the by_tag action and by_author.
class ArticlesController < ApplicationController
.....
def by_tag
...
end
end
Check this route works with
melardev#local~$ rails routes
Why don't you use this routes:
resources :questions do
resources :widgets
end
it will create path like questions/:question_id/widgets/new for you to create new widget for question with specific id of question.
This is what ended up working for me:
resources :post do
get "author/:author", to: "posts#author", on: :collection, as: "author"
end
Which outputs the following route:
author_posts GET /posts/author/:author(.:format) posts#author
Then in your controller, you need to create the author action:
class PostsController < ApplicationController
def author
#roles = Post.where(author: params[:author])
render :index # to reuse the index view
end
end
Then in your view:
<%= link_to post.author, author_posts_path(post.author), data: { turbo_frame: "_top" } %>

Ruby on Rails: How to override the 'show' route of a resource?

Currently I have a route that looks like this:
resources :posts
I want to override the 'show' action so that I can display a url like this:
posts/:id/:slug
I am currently able to do this by adding a custom match route:
resources :posts
match 'posts/:id/:slug' => 'posts#show'
However, when I use the link_to helper, it does not use my custom show route.
<%= link_to 'show', post %> # renders /posts/123
How can I define my show route so that I can still use the link_to helper?
Update: As you can read in the following answers, you can override the route to the 'show' action, but it's probably more work than it's worth. It's easier to just create a custom route:
# config/routes.rb
match 'posts/:id/:slug' => 'posts#show', as: 'post_seo'
# app/views/posts/index.html.erb
<%= link_to post.title, post_seo_path(post.id, post.slug) %>
You have two routes which point to posts#show (you should be able to confirm this by running rake routes), and your link is using the wrong one.
When you call link_to('show', post) the URL of the link is generated by calling url_for(post) which (eventually, after passing through several other methods on the way) calls post_path(post). Since the route to posts#show that was created by your call to resources(:posts) is named post, that is the route that post_path generates.
You also currently have inconsistent routes for the show, update and destroy actions which will probably cause you problems later on.
You can fix this by changing your routes to the following:
resources :posts, :except => ['show', 'update', 'destroy']
get 'posts/:id/:slug' => 'posts#show', :as => 'post'
put 'posts/:id/:slug' => 'posts#update'
delete 'posts/:id/:slug' => 'posts#destroy'
Unfortunately you still can't use link_to('show', post) just yet, because it relies on being able to use post.to_param as the single argument needed to build a path to a post. Your custom route requires two arguments, an id and a slug. So now your link code will need to look like this:
link_to 'show', post_path(post.id, post.slug)
You can get around that problem by defining your own post_path and post_url helpers in app/helpers/posts_helper.rb:
module PostsHelper
def post_path(post, options={})
post_url(post, options.merge(:only_path => true))
end
def post_url(post, options={})
url_for(options.merge(:controller => 'posts', :action => 'show',
:id => post.id, :slug => post.slug))
end
end
Which means we're finally able to use:
link_to 'show', post
If that all seems like too much work, a common alternative is to use URLs that look more like posts/:id-:slug, in which case you can stick with the standard RESTful routes and just override the to_param method in your Post class:
def to_param
"#{id}-#{slug}"
end
You'll also need to do a little bit of work splitting up params[:id] into an ID and a slug before you can look up the relevant instance in your show, edit, update and destroy controller actions.
resources :posts, except: :show do
get ":slug" => :show, as: "", on: :member
end
and define helper
def post_path post
"/posts/#{post.id}/#{post.slug}"
end
db/migrate/add_slug_to_articles.rb
add_column :articles, :slug, :string
add_index :articles, :slug
models/article.rb
class Article < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: :slugged
def should_generate_new_friendly_id?
new_record?
end
end
Or...
class Article < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: :history
end
http://railscasts.com/episodes/314-pretty-urls-with-friendlyid
https://github.com/norman/friendly_id

Routing Error - custom controller

I have a has many through association.
Firms have many Users through Follows.
I want Users to be able to Follow Firms. - I am using Devise for the users.
I have the following action in my firms controller.
def follow
#firm.users << current_user
end
in my routes.rb
resources :firms do
post :follow, on: :member
end
and in my firms view
<%= link_to "Follow", follow_firm_path(#firm), method: :post %>
However when I keep getting the following Routing Error in the browser
No route matches {:action=>"follow", :controller=>"firms"}
Rake Routes confirms the following
follow_firm POST /firms/:id/follow(.:format) firms#follow
Any ideas what the problem may be?
Many thanks
Edit: Controller code
class FirmsController < ApplicationController
before_filter :authenticate_user!, :except => [:show, :index]
def index
#firm_names = Firm.all.map &:name
direction = params[:direction]
direction ||= "ASC"
#firms = Firm.order("name #{direction}")
respond_to do |format|
format.html # index.html.erb
format.js
end
end
def follow
#firm.users << current_user
end
I am using the follow action in a partial in the index view.
everything looks good and this should work perfectly. Except that I see a typo in the following line
<%= link_to "Follow", follow_firm_path(#firm), method: :post %>
after the :method there should an => not a : . this will make the link a get request not a post request, that might be the issue, try using a simple link and replace post will get in your routes.rb just to test if the issue is arising due to this.
you can also test route methods from the console
rails c
app.follow_firm_path(2)
I noticed you also have an error in your routes, there should be an => not a : after :on
resources :firms do
post :follow, :on => member
end
You should define methods like this...
resources :firms do
collection
post :follow, on: :member
end
end
I think if this method does not create anything its type should be get.
Try it

Rails: model_url for custom action

Let's say I have the simple rails blog app.
And I have a custom action, like page_views which shows the number of views of the post.
class PostsController < ApplicationController
def page_views
#show some page views
end
end
And there is also an associated view in the app/views/Posts folder.
Now, in the routes.rb I have:
map.resources :posts
map.resources :posts, :collection => {
:page_views=> :get
}
in my posts show.html.erb file I have a link to the page_views view:
link_to("View Page Views",page_views_posts_path + "/" + post.id.to_s)
Another paths:
page_views_posts_path(post)
page_views_path(post)
page_views_posts(post)
Have either resulted in method not found or an incorrect url, like:
http://localhost:3000/posts/page_views.#<posts:0xabcdef00>
I would assume the url should be:
http://localhost:3000/posts/page_views/1
So, what I am missing here?
If you want to provide page_views page for each view you should declare extra action not as collection method but as a member method:
map.resources :posts, :member => { :page_views=> :get }
Also if you want this for all posts as well (show the ranking table of some sorts) add the same parameter as collection action:
map.resources :posts, :member => { :page_views=> :get }, :collection => { :page_views => :get }
This way you'll have following routes generated:
page_views_post_path(post) # for single post
page_views_posts_path # for all posts
You can check new routes by running following command:
$ rake routes | grep page_views
You will get only those associated views considering the fact that you haven't declared them for other controllers.
You can also add custom methods to resources in routes.rb like this:
resources :posts do
collection do
get :page_views
end
end
And use page_views_posts_path to access to the custom method.

Resources