I have the following in my routes.rb
map.resources :novels do |novel|
novel.resources :chapters
end
With the above defined route, I can access the chapters by using xxxxx.com/novels/:id/chapters/:id.
But this is not what I want, the Chapter model has another field called number (which corresponds to chapter number). I want to access each chapter through an URL which is something like
xxxx.com/novels/:novel_id/chapters/:chapter_number. How can I accomplish this without explicitly defining a named route?
Right now I'm doing this by using the following named route defined ABOVE map.resources :novels
map.chapter_no 'novels/:novel_id/chapters/:chapter_no', :controller => 'chapters', :action => 'show'
Thanks.
:id can be almost anything you want. So, leave the routing config untouched and change your action from
class ChaptersControllers
def show
#chapter = Chapter.find(params[:id])
end
end
to (assuming the field you want to search for is called :chapter_no)
class ChaptersControllers
def show
#chapter = Chapter.find_by_chapter_no!(params[:id])
end
end
Also note:
I'm using the bang! finder version (find_by_chapter_no! instead of find_by_chapter_no) to simulate the default find behavior
The field you are searching should have a database index for better performances
Related
I have a controller, clients_controller, with corresponding index, show, edit, delete, new & form views. Is there a way to create a new view like clients/prospects.html.erb that acts the same way as clients/index.html.erb, except is routed at clients/prospects/?
I've tried this:
match '/clients/prospects' => 'clients#prospects'
And some other things in routes.rb, but of course get the error "Couldn't find Client with id=prospects".
The goal here is basically to have a prospects view and a clients view, and by simply switching the hidden field to a 1, it (in the user's mind) turns a prospect into a client (it's a CRM-like app).
There's a couple of things you need to do. First you need to put the your custom route before any generic route. Otherwise Rails assumes the word "prospects" is an id for the show action. Example:
get '/clients/prospects' => 'clients#prospects' # or match for older Rails versions
resources :clients
Also you need to copy / paste the index method in your ClientsController and name it prospects. Example:
class ClientsController < ApplicationController
def index
#clients = Client.where(prospect: false)
end
def prospects
#prospects = Client.where(prospect: true)
end
end
Lastly, you need to copy the index.html.erb view and name the copy prospects.html.erb. In the example above you would have to work with the #prospects instance variable.
Create a new action in clients controller named prospects. And then define a collection route in routes.rb for it as either resource full way. Or u directly use match as you were doing.
What you're doing is not wrong (although I'd change match to get, otherwise POST and DELETE requests to that url will also render your prospects view). Presumably you have
resources :clients
in your routes file? If so, what you have will probably work if you just move the line you quoted above the resources declaration -- the problem is that /clients/prospects matches the show route for the clients resource, so if it's defined first then that's the route that gets matched.
However, there's a more idiomatic way to define this route
resources :clients do
collection do
get :prospects
end
end
See Rails Routing documentation for more
Also see migu's answer for what else needs to be done once the url is being routed correctly (though there are other things you can do -- if you the two views are similar enough, you can reuse the view template, for example).
I am trying to follow http://railscasts.com/episodes/63-model-name-in-url to achieve URLs that look like:
/dog/<custom field of dog>
instead of
/dog/1
Where "1" is the internal primary key of the Dogs table. The custom field I want happens to be another integer in the Dogs field.
My code:
dogs_controller.rb:
load_and_authorize_resource :except => [:index]
def show
Rails.logger.info("Hello")
#dog = Dog.find_by_custom_field(params[:id])
end
dog.rb:
def to_param
custom_field
end
In particular, when I try to load /dogs/<custom_field>, it still insists on using that integer as the primary key lookup, instead of looking up on the custom field. So I get a Couldn't find Dog with id=<custom_field>. error
Interestingly, the logger line also never gets printed when I try to do this. However, when I remove the load_and_authorize_resource (CanCan) line, then it works. What is going on here?
for using a different attribute other than id pass :find_by option
load_and_authorize_resource :except => [:index] , :find_by => :custom # will use find_by_custom!(params[:id])
for more info read cancan manual for controller methods
http://rdoc.info/github/ryanb/cancan/master/CanCan/ControllerAdditions/ClassMethods
I think I'm running across a conflict due to names:
Two models: store coupon
Url needed that will display coupons: http://localhost/coupons/:store_name ('coupons' is written in the url, not replaced with anything)
Controller name: coupons_controller
Here is what I have in my routes right now:
match '/coupons/:store_name' => 'coupons#index', :as => :stores
When I try to do redirect stores_path(store) in another controller, I get this error:
No route matches {:controller=>"coupons"}
Any clues? I'm new to rails so I bet it's a silly mistake.
UPDATE
Is there a central place to tell the dynamic _path() functions to use a specific url structure? i.e. Instead of having to do the following everywhere:
redirect_to stores_path(:store_name => store.store_name)
Instead using just:
redirect_to stores_path(store)
yes you can, redefine to_param in your model:
class Store < ...
def to_param
store_name
end
end
redirect_to stores_path(:store_name => store)
should work if it doesn't (cannot confirm right now), you should be able to do the (little hacky)
redirect_to stores_path+"?store_name=yourstorename"
Doing it the restful way, you should probably have something like this (in your routes):
resources :stores do
resources :coupons # this will give you e.g. /stores/:store_id/coupons for the coupons#index action
end
If you want to use the store name instead of the ID, just search SO for using "slug" or have a look here: getting a 'name' based URL in RESTful routes instead of an id based url or ID + Slug name in URL in Rails (like in StackOverflow)
In my config > routes I have:
#Service Routes
match "services" => "services#index"
match "startsingleservice" => "services#start_single_service"
match "stopsingleservice" => "services#stop_single_service"
match "zookeeperreindex" => "services#show_zookeeper"
The first 3 work, no issues no problems. And all four are in the same file def/functions whatever you wanna call them. Are in the same file. Where again first 3, work awesome. Adding that new guy there, zookeeper just doesn't wanna work I get
Unknown action
The action 'show_zookeeper' could not be found for ServicesController
the function zookeeperreindex is almost a mirror of the actual index def in the same file, changed for the needs of redisplay as I only want a JSON output for that one. But bottom line is I changed the routes to match, I know the function is working for the most part, and I am not seeing where I could be messing this simplicity up, I've also restarted the server itself to ensure it wasn't that
Edit
In replying with code from the controller which by the way did "show_zookeeper" defined right.. I realized I had a misplaced "end" tag.. So, in moving that the route worked.
It looks like in your ServicesController (app/controllers/services_controller.rb)
You never define a method show_zookeeper. My guess is that you define a method zookeeperreindex instead of show_zookeeper.
Why don't you link the contents of that file? You should see something along the lines of,
class ServicesController < ActionController::Base
def index
...
end
def start_single_service
...
end
def stop_single_service
...
end
def show_zookeeper # <---- This one is missing
end
end
The way the routes work the part after the => determines the controller and action. For example "services#start_single_service" will be mapped to :controller => ServicesController, and :action => start_single_service.
Thus the final call will be ServicesController.start_single_service
Look at http://guides.rubyonrails.org/routing.html for more info
I understand how to create a vanity URL in Rails in order to translate
http://mysite.com/forum/1 into http://mysite.com/some-forum-name
But I'd like to take it a step further and get the following working (if it is possible at all):
Instead of:
http://mysite.com/forum/1/board/99/thread/321
I'd like in the first step to get to something like this: http://mysite.com/1/99/321
and ultimately have it like http://mysite.com/some-forum-name/some-board-name/this-is-the-thread-subject.
Is this possible?
To have this work "nicely" with the Rails URL helpers you have to override to_param in your model:
def to_param
permalink
end
Where permalink is generated by perhaps a before_save
before_save :set_permalink
def set_permalink
self.permalink = title.parameterize
end
The reason you create a permalink is because, eventually, maybe, potentially, you'll have a title that is not URL friendly. That is where parameterize comes in.
Now, as for finding those posts based on what permalink is you can either go the easy route or the hard route.
Easy route
Define to_param slightly differently:
def to_param
id.to_s + permalink
end
Continue using Forum.find(params[:id]) where params[:id] would be something such as 1-my-awesome-forum. Why does this still work? Well, Rails will call to_i on the argument passed to find, and calling to_i on that string will return simply 1.
Hard route
Leave to_param the same. Resort to using find_by_permalink in your controllers, using params[:id] which is passed in form the routes:
Model.find_by_permalink(params[:id])
Now for the fun part
Now you want to take the resource out of the URL. Well, it's a Sisyphean approach. Sure you could stop using the routing helpers Ruby on Rails provides such as map.resources and define them using map.connect but is it really worth that much gain? What "special super powers" does it grant you? None, I'm afraid.
But still if you wanted to do that, here's a great place to start from:
get ':forum_id/:board_id/:topic_id', :to => "topics#show", :as => "forum_board_topic"
Take a look at the Rails Routing from the Outside In guide.
maybe try something like
map.my_thread ':forum_id/:board_od/:thread_id.:format', :controller => 'threads', :action => 'show'
And then in your controller have
#forum = Forum.find(params[:forum_id])
#board = #forum.find(params[:board_id])
#thread = #board.find(params[:thread_id])
Notice that you can have that model_id be anything (the name in this case)
In your view, you can use
<%= link_to my_thread_path(#forum, #board, #thread) %>
I hope this helps