Customizing map.resources in Rails - ruby-on-rails

Suppose I have a Book model, which contains many Page models.
The routing for this would be as so:
map.resources :books do |book|
book.resources :pages
end
Following the Rails default on this quickly leads to problems. Suppose Book #1 has 10 pages. The first Page in Book #2 will have this route:
/books/2/pages/11
This is a pretty bad route, what would make more sense is this:
/books/2/pages/1
Or even this:
/books/2/1
Is there a way to still use map.resources, but get a result like this:
/books/{book.id}/pages/{page.page_number}

No. You have to use custom routing for that.
Feel free to get inspiration from http://github.com/augustl/kii/blob/master/config/routes.rb

As August says you need to use custom routing for that.
But for the pages, you don't need the full resources routes. Only show will be necessary.
So something like :
map.resources :books do |book|
book.page ':page_id', :action => 'index'
end
Will map the default books url for displaying the index, one book and adding/editing them.
But also a page
/books/{book.id}/{page_id}
Which maps to the index action with the parameter "page_id". You only have to display the appropriate books page ;)

You could also try the shallow-option for your routing!

Related

Best practice for adding a non standard view and controller action?

My situation I have a "Parent" model and controller. I want to know the best practice for adding independent pages such as a dashboard for users. My thought is that I can create a view dashboard.html.erb and inside the parent controller create a method of:
Parent controller
def dashboard
end
Routes.rb
get 'parents/dashboard'
I've done this once and it worked fine, but is was for a 'child' model.
When I run this same situation in the parent model I get the error
ActiveRecord::RecordNotFound in ParentsController#show
Couldn't find Parent with 'id'=dashboard
1.) All I've done is add a view, added the dashboard model to the controller, and placed get 'parents/dashboard' into the routes.rb and it tries to reference the show method??? Why?
2.) And is this the wrong way to add pages/actions to a rails application?
Do this:
#config/routes.rb
resources :parents do
get :dashboard, on: :collection #-> url.com/parents/dashboard
end
And is this the wrong way to add pages/actions to a rails application?
It's not "wrong", it's just ineffective, as demonstrated by your problem.
The problem you have is you've included your custom route below the resources :parents route. Because resources creates a /:id url which captures any requests sent to parents/:id, your "dashboard" request is being sent to the show action of the parents controller:
There are two remedies to your issue:
Put get 'parents/dashboard' above the resources :parents directive
Include an additional route to resources :parents (above)
You must remember that Rails matches your request with a route. That means the first route to match your request is processed.
So if you have...
#config/routes.rb
resources :parents
get "parents/dashboard"
... Rails will assume the dashboard is the :id in url.com/parents/:id, thus sending the request to show.
Apart from the very top code (the recommended answer), you could have the following:
#config/routes.rb
get "parents/dashboard", to: :dashboard
resources :parents
If you want to add an additional route with an :id then the syntax is different.
get 'parent_dashboard/:id', to: 'parents#dashboard'
Notice that the person string after get. This is used as the address of the website, when the user hits this, it would go to localhost:3000/parent_dashboard/1 if it is the first dashboard. You can exclude :id if you'd like. Of course, this would be different from the use case.
The second part of the route syntax is the :to, this method tells your app which controller and method to look at.
Hope this helps!
If you want to add a new view to the parents folder. Just do this:
parents_controller.rb
def dashboard
#parent = Parent.find(params[:id])
end
routes.rb
get '/parents/:id/dashboard', to: 'parents#dashboard', as: :parents_dashboards
resources :parents
Then in your parents/dashboard.html.erb view you can do everything that you can do in the parents/show view.
The link to your dashboard view would be parents_dashboards_path and you might have to use parents_dashboards_path(#parent) or parents_dashboards_path(parent) in certain circumstances.
This is an example of a custom path that works without using nested resources to accomplish access to the parent's dashboard.
I am using this approach in a project so I would like to hear any critique or comments on this approach. PEACE I'M OUTTA HERE!

Including attributes in custom Rails routes

I hope the title is not to misleading, as I don't know a better title for the problem I'm working on:
I have a doctor which belongs to location and specialty. I'd like to route to show action of the doc controller like this:
/dentist/berlin/7
I defined my routes like this:
get ':specialty/:location/:id', to: 'docs#show'
And in my views create the following url to link to the show action of the doc controller:
<%= link_to doc.name, "#{doc.specialty.name}/#{doc.location.name}/#{doc.id}" %>
Is this a good solution to the problem? If not, is there a cleaner way to construct urls like this possibly using resources? What the heck is the name for a this problem?
Thank your very much for your help in advance.
For references, you should have a look at this page (especially the end of section 2.6)
If it is only for a single route, it's okay as you did. But then if you want to have more than one route (like /dentist/berlin/7, /dentist/berlin/7/make_appointment, etc.) you might want to structure a bit more your routes so as to take advantage of rails resources.
For example, instead of
get ':specialty/:location/:id', to: 'doctors#show'
get ':specialty/:location/:id/appointment', to: 'doctors#new_appointment'
post ':specialty/:location/:id/appointment', to: 'doctors#post_appointment'
You could have something like this (the code is almost equivalent, see explanation below)
resources :doctors, path: '/:specialty/:location', only: [:show] do
member do
get 'new_appointment'
post 'create_appointment'
end
end
Explanation
resources will generate the RESTful routes (index, show, edit, new, create, destroy) for the specified controller (doctors_controller I assume)
The 'only' means you don't want to add all the RESTful routes, just the ones specified
Then you want to add member actions, ie. actions that can be executed on a particular item of the collection. You can chose different syntaxes
resources :doctors do
member do
# Everything here will have the prefix /:id so the action applies to a particular item
end
end
# OR
resources :doctors do
get 'new_appointement', on: :member
end
By default, the controller action is the same as the path name you give, but you can also override it
member do
get 'appointment', action: 'new_appointment'
post 'appointment', action: 'post_appointment'
end
Rails has some wonderful helpers when it comes to routing !
The correct approach is to give your route a name, like this:
get ':specialty/:location/:id', to: 'docs#show', as: 'docs_show'
Then you can use it like this:
<%= link_to doc.name, docs_show_path(doc.specialty.name, doc.location.name, doc.id) %>
Note 1:
Rails appends _path at the end of the route names you define.
Note 2:
You can see all the available named routes by executing rake routes.

Custom urls and paths in rails

I have two models: Books and pages in a typical one_to_many relationship.
How can I make the following
page_path(#page)
output this path:
bookname/page/pageid
instead of
page/pageid
If I override the to_param, all I can do is make a path like localhost/page/bookid/pageid but that's not what I want.
Not sure if it's possible to get exactly what you want, but you can make the Page a nested resource under book like this:
resources :books do
resources :pages
end
Then you will get:
localhost/book/bookid/page/pageid
Then you can override `to_param' to get:
localhost/book/bookid-bookname/page/pageid
I'm assuming you mean to have path as /:book_name/page/:id
In routes.rb:
match '/:book_name/page/:id' => "page#show", :as => :page
In the controller you would access params[:id] to get page.id and params[:book_name] to get the name of the book.
I discovered that to have full control over path helpers, you have to override those inside the application_helper.erb file. The following code worked for me:
def pages_path(#page)
#bookpath = Book.find(#page.book_id)
#bookpath + '/page/' + #page.id
end
The helper only creates the path. You still need to link it to a particular action in routes.rb. You may even nest the pages resource inside the books resource. The only important thing is that the path generated by the above helper must be recognizable by the rails application.

Rails 3 Routing resources scoped to a username

I have a basic understanding of rails routing, but nothing too advanced. So far I've gotten by using the RESTful resource based routes and a few custom named routes.
I am nearly done my app now though and I wanted to make some pretty urls.
In my app, each user has many pages. What's the best way to make the URL's look like www.sitename.com/username/page_name?
This will route to the pages controller's show action. Params hash includes :username and :page_name.
match "/:username/:page_name" => "pages#show"
Remember to put it last or it will match pretty much everything.
I'm not quite sure what you're using this for, but something like this might work in your routes file:
resources :users do
get 'page_name'
end
Which will produce: users/:id/page_name
You might want to check out the Railsguide on routing.
What you are looking for is a member route (section 2.9.1).
resources :users do
member do
get :cool_page
end
end
Will result in /users/:id/cool_page

Ruby on Rails Actions help

I have my index page which posts a single entry instead of the usual scaffold default of all of the entries. I told it to link to an action and it just responds to "Couldn't find Post with ID=all". It is the same as the default index method and index view. I assume this has something to do with routing but being no I have no clue. Any ideas?
The "all" name is misleading. If you want a page to display all the posts then the index page is perfect for that. If you want to show a subset of the posts then I recommend adding another action to your controller with a better name and then this to the routes.rb file:
map.resources :posts, :collection => { :some => :get }
Which you then can reference by using some_posts_path or some_posts_url.
For more information read the Official Ruby on Rails Routing guide.

Resources