Rails 3 - Nested Resources - Routing - ruby-on-rails

I'm having issues with my destroy method on a nested source Product, that is tied to Orders.
After attempting to destroy an item, I'm redirecting users to my order_products_url. I receive the following routing error:
No route matches "/orders/1/products"
My destroy method looks like this:
def destroy
#product = Product.find(params[:id])
#order = Order.find(params[:order_id])
#product.destroy
respond_to do |format|
format.html { redirect_to(order_products_url) }
format.xml { head :ok }
end
end
And in routes.rb:
resources :orders do
resources :products, :controller => "products"
end
The reason why this is confusing me, is for my update method for products, I'm properly redirecting users to the order_products_url without issue. I don't understand why it works there but not here.
Thanks

order_products_url expects a parameter to be passed - either the order id, or the order object itself. Without this, it won't work properly. So using your code above:
def destroy
#product = Product.find(params[:id])
#order = Order.find(params[:order_id])
#product.destroy
respond_to do |format|
format.html { redirect_to(order_products_url(#order) }
format.xml { head :ok }
end
end
As a side note, you can shorten your routes a little:
resources :orders do
resources :products
end
Specifying the controller is redundant when it's named as Rails expects. I hope this helps!
UPDATE: I've added a link to my article about routing in Rails 3, with downloadable code samples. I updated it with a paragraph that explains named routes, in the "Things You Should Know" section:
Routing in Ruby on Rails 3

Don't you need to redirect to order_products_url(#order)?

you should be using orer_products_path (not url). If you go to the root of your app and type,
rake routes
that will give you a list of all named routes. You need to append _path to them though (returns the string representation). This is a handy little trick for figuring out named routes.
Now to your real question - of course it doesn't exist! You just deleted it! You are destroying the product instead of the product from the order!

Related

URL create and new in Rails

I created a new Rails project. After all, I have a question that I cannot find anywhere or answer by myself so I need your help.
When you create a new object (like Person, Book), you need 2 action: NEW and CREATE.
When I create new, I have link: localhost:3000/admin/books/new
And then when I create fails it will return ERROR MESSAGE anf this link: localhost:3000/admin/books/create
If I click in url and `ENTER`. It will wrong.
I'm trying to use redirect_to or render if creation is failed. But nothing happen, sometimes it go to new page but it don't show error message.
I think is a rule in Rails. But I still want to ask that anyone have any idea to resolve this problem??? Go tonewlink witherror messageif they're failed
More details: I'm using Typus gem to create view for admin. So I can't find Routes file. I run rake routes and get:
GET /admin/books/(:/action(/:id)) (.:format)
POST /admin/books/(:/action(/:id)) (.:format)
PATCH /admin/books/(:/action(/:id)) (.:format)
DELETE /admin/books/(:/action(/:id)) (.:format)
And controller when create book:
if result
format.html { redirect_on_success }
format.json { render json: #item }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
Thanks for your helping :)
That is the normal way that Rails works. To understand what is happening, you need to understand what is HTTP verbs and how they works.
When you visit http://localhost:3000/book/new, you are making a request to the server to get (GET Verb) some information. In this case, a form to submit a new Book.
When You click submit, you are Sending (POST verb) data to the Server. On Rails, the link http://localhost:3000/book/create is available only by POST request. That is why, when you visit this link directly, it says that the route was not found.
This line:
# ...
else
format.html { render :new, status: :unprocessable_entity
end
means that, if something wrong happens, it need to render the view of new action again without redirect. This way you are able to find the errors on the object you are trying to save.
If you redirect, you will lose the actual (at the create stage) object. New object without data and error will be created on the new action:
def new
#book = Book.new
end
For this reason you are unable to access the error mensagens when you redirect. Only you can do on redirection, is setting a flash message:
if #book.save
redirect_to #book
else
flash[:error] = "An error occurred while saving Book."
redirect_to :new
end
2 resources that will hep you with that:
https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods
http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions
On your rake routes, you could notice that it is prefixed with admin.
GET /admin/books/(:/action(/:id)) (.:format)
POST /admin/books/(:/action(/:id)) (.:format)
PATCH /admin/books/(:/action(/:id)) (.:format)
DELETE /admin/books/(:/action(/:id)) (.:format)
Did you try it to prefixed with admin/books/new? And admin/books/create? Then notice your url: you use only book since on your routes it is books.
Try:
http://localhost:3000/admin/books/new
http://localhost:3000/admin/books/create
You shouldn't be getting that error, there (by default) is no /create path, especially with a GET verb.
Whilst you can create your own /create path, your functionality is conventional:
#config/routes.rb
scope :admin do
resources :books, :people, only: [:new, :create] #-> url.com/admin/books/new
end
#app/controllers/books_controller.rb
class BooksController < ApplicationController
respond_to :json, :html, only: :create #-> needs responders gem
def new
#book = Book.new
end
def create
#book = Book.new book_params
respond_with #book if #book.save
end
end
The above is the standardized (working) way to achieve what you want.
--
As per the routes, there is no /create path:
POST /photos photos#create create a new photo

Change ruby on rails controller to respond differently based on route nesting

Is it possible to have a controller that interacts in a standard way at both the top level and also the nested level? Or will static routes need to be configured?
When I visit the first address /list/:list_id/items I want it to follow the nested_index method to display only a subset of the listed items (The items that belong to the list).
http://localhost:3000/list/:list_id/items
When I visit the below (/items) address I want it to show the whole list of items.
http://localhost:3000/items
/app/controllers/items_controller.rb
def index
#Item = Item.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #Item }
end
end
def nested_index
#list = List.find(params[:list_id])
#items = #list.items.paginate(page: params[:page], per_page: 5)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #list }
end
end
/config/routes.rb
AppName::Application.routes.draw do
resources :list do
resources :items
end
end
# Do I need to add further routes here?
Personally, I think you should split this out into two separate controllers.
The index method of your controller should be designed to do just one thing. In the case of your nested route it should be fetching all the items appropriate for the selected list and passing them to the appropriate view. In the other instance it is fetching all items and (probably) passing them to a completely different view.
It seems you're trying to get one controller to do the job of two, simply for the sake of the controller's name.
I'd suggest creating an apps_controller and use that to collect all your items and display them, and leave your items_controller for its nested use.
Remember you don't need to name a controller after the model it interacts with ... rather, you should name it after the function it is responsible for. A controller which receives an activation code for a user account might update an is_active boolean on a User model, but you would call this controller Activations since that is what it does.
If you have lots of overlap between controllers you can move their code into modules and then include those modules in both controllers. This way you can DRY up your code whilst keeping the logic separate where necessary.
Take a look at these links for some ideas on code extraction:
http://railscasts.com/episodes/398-service-objects
http://railscasts.com/episodes/416-form-objects
But before you start refactoring all of your code into modules ... consider whether it adds anything to your codebase. Does it make things simpler? Does it make things more readable? Does it save you anything other than typing out a few more lines? If there's no benefit to refactoring ... just don't do it.
#Jon is right. This should be split into several different controllers:
# app/controllers/items_controller.rb
class ItemsController < ApplicationController
# default RESTful actions to operate on lists, for example #index
def index
#Item = Item.all
respond_to do |format|
format.html
format.json { render json: #item }
end
end
end
# app/controllers/lists_controller.rb
class ListsController < ApplicationController
# default RESTful actions to operate on lists
end
# app/controllers/lists/items_controllers.rb
class Lists::ItemsController < ApplicationController
def show
#list = List.find(params[:list_id])
#items = #list.items.paginate(page: params[:page], per_page: 5)
respond_to do |format|
format.html
format.json { render json: #items }
end
end
end
Routes:
AppName::Application.routes.draw do
resources :items
resources :lists do
resources :items
end
end

Allow method to accept an :id param

I have created a method called verify in a controller (events_controller.rb), and I want to allow that page (verify.html.erb) to accept an object (#event), and show of that objects parameters. I'm creating a show page in essence, but I need to build some special logic into this page that I don't want to build into the show page. I have created the route, but I still get an error when I tell it to find an Event by params[:id]. The actual url it is going to is /verify.(event :id) and I believe it should be routing to events/verify/(event :id).
My error
Couldn't find Event without an ID.
routes.rb
get "verify", to: 'events#verify'
resources :events
events_controller.rb
def verify
#event = Event.find(params[:id])
respond_to do |format|
format.html # verify.html.erb
format.json { render json: #event }
end
end
Thanks Stack!
get "verify/:id", to: 'events#verify', as: "verify"
in browser go to url for example:
localhost:3000/verify/1

Rails and rendering nested resources

For example: can I render '/tags/:id/posts'?
If I try to render tag_posts_path(#tag) or some other ways, I get this or similar error:
Missing partial /tags/1/posts...
In my routes.rb I have this:
resources :tags do
resources :posts
end
I've stucked a bit with thing like yours
I achieved rendering correct page
def create
#project = Project.find(params[:project_id])
#comment = #project.comments.build(comment_params)
if #comment.save
flash[:success] = "Chingon!"
redirect_to #project
else
render 'projects/show'
end
end
My routes looks like:
resources :projects do
resources :comments, only: [:create, :destroy]
end
Rails expecting you to render not path but some page(like 'new' or 'show')
I got trouble only with my ELSE where I'm rendering 'projects/show'. It seem to be not quite right template, coz it renders in raw, without any CSS but with errors I expect to appear. It's the only way for now that I can render error messages for empty comment.
I walk over that issue simply by bypassing default way to display errors and place it in simple flash like this:
def create
#project = Project.find(params[:project_id])
#comment = #project.comments.build(comment_params)
if #comment.save
flash[:success] = "New comment here, bro"
redirect_to #project
else
redirect_to #project
flash[:error] = flash_error_message(#comment)
end
end
def flash_error_message(arg)
"The form contains #{arg.errors.count} error: #{arg.errors.full_messages.join(', ')}"
end
If I'm correct, I think what you are looking for is something like this
resources :tags do
resources :posts
end
You can see what kind of urls or routes this generates by typing in rake routes
I'm not really sure but, re-reading your question I think you mean rendering by saying
render 'some nested route'
The above routes I provided would allow you to do something like this...
render tag_posts_path(#tag) that will in term look for a index action since its the posts path. Again, rake routes shows all this.
A quick search on google for nested routes would've led you to this.
Nested Routes

How do I use redirect_to if I've got multiple controllers in different subdirectories?

I've created a small application to learn RoR. (Book database) It consists of a read-only area and a read-write admin area.
After I've got the admin functionality working first, I've moved the controller into a subdirectory and created the read-only controller.
Now when I'm updating a book in the admin area, the redirect_to function redirects to the read-only area.
What am I missing?
Here's the code I'm using:
class Admin::BooksController < ApplicationController
<snip>
def update
#book = Book.find params[:id]
respond_to do |format|
if #book.update_attributes params[:book]
flash[:notice] = "Book updated"
format.html { redirect_to #book }
format.xml { head :ok }
else
<snip>
end
end
end
<snip>
end
This update itself works but it redirects me to /books/1, but I'd want it to redirect to /admin/books/1. I could just hardcode the correct path, but I guess that's not very good style.
What would be the proper way?
PS: Please comment if you need further information.
You are telling it to redirect to book because you are using rails' built in magical recognition of what it should do with the #book object (which is build a url to show the book using the book controller.
format.html { redirect_to #book }
If you want it to go elsewhere you need to be explicit about where you want it to go using a hash for url_for
format.html { redirect_to :controller => 'admin/book', :action => 'show', :id => #book }
or use the paths like klew points out.
so for more detail -
redirect_to (#book) or
redirect_to book_path(#book)
are both shortcuts for this:
redirect_to :controller => book, :action => 'show', :id => #book.id
Rails creates for you url helpers based on your routes.rb. If you have namespace then you can use this:
admin_book_path(#book) # admin/books/2
admin_books_path # admin/books
edit_admin_book_path(#book) # admin/books/2/edit
and so on.
The other way is to use resource_controller it creates for you controller automaticaly and provides some ways to modify it if it's needed. It also gives you some useful url helpers
collection_path # admin/books
object_path # admin/books/2
When you use above helpers in views, than it generates url with namespace if you are in one, or without namespace otherwise.
resource_controller isn't perfect, but in most cases it works good and saves a lot of work.
You can also pass an array to redirect where the first element is a symbol representing the namespace, and the second the element the object.
redirect_to [:admin_book, #book]
You can also use this for form_for, link_to and any other helpers that require a path.

Resources