Ruby on Rails how does this route not work - ruby-on-rails

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

Related

In Rails 6, how can I use a route parameter as the controller method name?

I have a reports controller with several actions, one for each kind of report.
I was planning to have them route like this:
/reports/:report_type
I'd like the report_type string fragment to be used as the controller name so that I can have a single route to handle all of them, something like this:
get 'reports/:rpt_type' => "reports#:rpt_type"
...that would resolve to this, as an example:
get 'reports/song_performers' => 'reports#song_performers'
Is this possible, and if so, how?
Yes, it is. You could handle this in the controller, rather than the routes.rb file:
# reports_controller.rb
def show
send(params[:rpt_type])
end
private
def song_performers
# do stuff
end
def other_type
# do other stuff
end
# in routes.rb
get 'reports/:rpt_type', to: 'reports#show'

Rails 4.1.2 - to_param escapes slashes (and breaks app)

I use in my app to_param to create custom URL (this custom path contains slashes):
class Machine < ActiveRecord::Base
def to_param
MachinePrettyPath.show_path(self, cut_model_text: true)
end
end
The thing is, that since Rails 4.1.2 behaviour changed and Rails doesn't allow to use slashes in the URL (when use custom URL), so it escapes slashes.
I had such routes:
Rails.application.routes.draw do
scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do
resources :machines, except: :destroy do
collection do
get :search
get 'search/:ad_type(/:machine_type(/:machine_subtype(/:brand)))', action: 'search', as: :pretty_search
get ':subcategory/:brand(/:model)/:id', action: 'show', as: :pretty
patch ':subcategory/:brand(/:model)/:id', action: 'update' # To be able to update machines with new rich paths.
end
end
end
end
I tried by recommendation in the thread to use glob param just for show method to make sure it works:
resources :machines, except: :destroy do
#...
end
scope format: false do
get '/machines/*id', to: "machines#show"
end
But it absolutely doesn't work. I still have such broken links:
http://localhost:3000/machines/tractor%2Fminitractor%2Fmodel1%2F405
Of course, if I replace escaped slashes on myself:
http://localhost:3000/machines/tractor/minitractor/model1/405
And try to visit path, then page'll be opened.
Any ideas how can I fix that?
I've been having the same problem when using the auto-generated url helpers. I used a debugger to trace the new behavior to its source (somewhere around ActionDispatch::Journey::Visitors::Formatter), but didn't find any promising solutions. It looks like the parameterized model is now strictly treated as a single slash-delimited segment of the path and escaped accordingly, with no options to tell the formatter otherwise.
As far as I can tell, the only way to get the url helper to produce the old result is to use your original routes file and pass each segment separately, something like:
pretty_machine_path(machine.subcategory, machine.brand, machine.model, machine.id)
This is ugly as hell and obviously not something you'll want to do over and over. You could add a method to MachinePrettyPath to generate the segments as an array and explode the result for the helper (say, pretty_machine_path(*MachinePrettyPath.show_path_segments(machine))) but that's still pretty verbose.
Between the above headaches and the "You're Doing it Wrong" attitude from the devs in that Rails ticket you linked to, the simplest option for me was to bite the bullet and write a custom URL helper instead of using to_param. I've yet to find a good example of the "right" way to do that, but something like this bare-bones example should serve the purpose:
#app/helpers/urls_helper.rb
module UrlsHelper
def machine_path(machine, options = {})
pretty_machine_path(*MachinePrettyPath.show_path_segments(machine), options)
end
end
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
helper :urls #for the views
include UrlsHelper #for controllers
#...
end
If you're sure the returned url is safe you should add .html_safe to the returned string:
MachinePrettyPath.show_path(self, cut_model_text: true).html_safe
(didn't see anywhere else where it can be escaped but check all the flow in your app, maybe manually testing method by by method)
How about defining the url yourself?
def to_param
"#{ subcategory.title.parameterize }/#{ brand.name.parameterize }/#{ model.name.parameterize) }/#{ id }"
end
And then in your routes something like this:
get 'machines/*id', to: "machines#show"
You also have to split your params[:id] when you do a find on your model.
Machine.find( params[:id].split("/").last )

Add new view to a controller in Rails

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).

Accessing a resource in routes.rb by using attributes other than Id

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

Get resource name from URL when using a custom controller in Rails

I have a set of routes that are generated dynamically at runtime, but that all point to the same controller i.e.
map.resources :authors, :controller => 'main'
map.resources :books, :controller => 'main'
These all work fine, producing routes like /authors/1, /books, /books/55, etc and then all end up being processed by the 'main' controller.
However, I can't seem to find how to get the name of the resource in the controller i.e. in the index action when the URL is /authors or /books I'd like to be able to determine which resource it is, i.e. Author or Book
I cannot use separate controllers for this.
Is this at all possible ?
EDIT: complete change of answer because it was waaay off.
So because it changes the params that you see in your action you'll have to get at the actual uri. It is really just as simple as what Terry suggested.
def index
if request.request_uri =~ /books/
#...
else
# if it is a author
end
end
This compares the request uri (the part that would be after localhost:3000) to books and so you can see what the user has requested.
I don't think there's anything like a .resource method, but you could look at request.request_uri, which in your case would return things like /authors or /books, and could act accordingly.
See the "Defaults routes and default parameters" section of the ActionController::Routing documentation. You can program into your routes arbitrary extra parameters you would like sent to your controller.
Looking at the request URI will force you to keep routes and controllers in sync, which will make your code more fragile and less easily re-used. Avoid if you possibly can.

Resources