Followed the getting started Rails guide (blog with comments) and have adjusted for my models (releases/products)
As the nested form was getting a bit cumbersome (products for me / comments in the guide), I decided to move it to it's own view. I can render the form correctly and view/update existing records without issue. However, when I try to submit a new product I get an error saying "Couldn't find Release without an ID".
I think the answer lies in the product controller, it seems to me that it's not receiving the release ID that I can see in the URL when the form is rendered, i.e. /releases/18/products/new but on submit the URL showing the error just: /products
# ProductsController
def create
#release = Release.find(params[:release_id])
#product = #release.products.create(params[:product])
redirect_to release_path(#release)
end
Ideally i'd like it to sumbit and the redirect back to the release show view as it was when nested.
Any ideas?
Related
I'm using Rails 4 and Chrome. The following results in the situation:
Post a form that causes a validation error (ie. "Name cannot be empty")
Post that same form successfully by correcting the input
Hit the browser back button and the validation error from step 1 is shown on the input field even though it has a value that is not empty
Why does the validation error from the step 1 pop back and how to fix this behaviour? Note: Turbolinks is in use, could that be the reason?
Here's the way to replicate:
rails g scaffold Page name:string
class Page < ActiveRecord::Base
validates :name, presence: true
end
Navigate to /pages/new
Submit (errors appear on the form)
Fillout the name
Submit again (redirected to successfully created model)
Hit the browser back button (the validation errors are there, and the field is filled with the last supplied value)
I guess you are using something like the following code to send back the errors related to the record being created/updated:
def update
#post = Post.find(params[:id])
if #post.update_attributes(post_params)
# your logic when successfull
else
render :edit, flash[:errors] = #post.errors
end
end
Or something similar. My point here is that you should use the following syntax for setting the errors in the flash :
flash.now[:errors] = #post.errors
(1) This should set the flash[:errors] available only for the current page and delete it right after you leave this page.
(2) You could also use flash.clear at the end of your view, but this is not how it is supposed to be done, and seems a little bit "hacky".
Resources:
(1) Rails' documentation about flash
(2) flash.clear method
(1) & (2) Why flash message won't disappear?
I am following the Michael Hartl tutorial and after Listing 10.14, he states that
There is one subtlety, though: on failed micropost submission, the
Home page expects an #feed_items instance variable, so failed
submissions currently break
which is true. But I do not understand how a failed micropost is related to #feed_items. The feed is being pulled out from the database right? from Micropost.where("user_id = ?", id) in the user model. So even if the a micropost is empty and fails to be saved, the feed should pull the other posts already saved. Why would we need to add #feed_items = [] in Listing 10.42 ?
The page being rendered for StaticPagesController.home expects a non-nil #feed_items.
The submission code renders the template directly, it does not redirect to the static controller which retrieves them itself: it must create something for the template to use.
I can't seem to figure out how to get my routes setup properly.
In my app, I have a view that lets site owners update their address information. The new and create actions are part of the signup process and are located in the signups_controller. The edit and update actions are in the settings_controller.
When the user goes into the settings area, he/she sees only the edit form. When filled out, the user is then returned to the same form with a flash message, or error message. Here is what the controller looks like:
class SettingsController < ApplicationController
def edit
#account = current_account
#account.companies.first
#account.companies.first.addresses.first
#account.companies.first.phones.first
end
def update
#account = current_account
if #account.update_attributes(params[:account])
redirect_to edit_setting_path
flash[:notice] = "Success!"
else
render :edit
end
end
end
In my routes, I simply have:
resources :settings
The link to this area of the site is a basic RESTful named linke, with the parameter options:
edit_setting_path(:id => current_account.id)
When the user arrives to this page, they see the following URL:
http://domainname.com/settings/1/edit
When they submit the form and get errors, the URL changes to:
http://domainname.com/settings/1
Why is the URL changing -- I'd rather it not? Is there a way to make it stay the same as the initial edit view? I've tried doing a redirect on a failed update, but then I don't get the error messages.
Any ideas?
To answer your "why" question: The URL is changing because it's reflecting the URL of the failed request - which in this case is a PUT request to that URL (/settings/1). You've submitted the form and the submission of that form (correctly) points to that URL. This is a result of the RESTful routes that the helper gives you. Since the logic in your action, falls through to the render :action, there is no redirect and the form simply re-renders on the page using the same data available in this action (which is why you can see the errors).
If you want to redirect back to the edit page, yes, you will lose the errors that have been set in the #account instance variable since the redirect will reset (re-query for) the account.
You could add a route that matches a PUT to /settings/1/edit and point it to your update action and change your form etc. In short, I wouldn't recommend this, but it should work.
completely untested but attemptable:
routes.rb
put "/settings/:id/edit", :to=>"settings#update", :as=>"update_setting"
resources :settings, :except=>:update
your form would also have to submit to the update_setting_path (which also means it's not reusable for a new object... ew)
First you should read up on The Rails Guides for Routing. They will help a lot to understand why its working like that.
Secondly, to accomplish what you are trying to do, you will need to add manual routes via the match call. You'll need something like this.
match '/settings/:id/edit' => "settings#edit"
I feel like this should be an easy thing to figure out, but I'm stumped.
I have a value in a Project's instance variable called ID. I want to pass that value to a new Photos page to associate each photo that is created with that specific project, but I don't want the Project's ID to show up in the visible query string.
I've tried using link_to and button_to, but (I suspect) since I'm using "resources :photos" in my routes, all of the requests that come to photo#new are being interpreted as GET instead of POST.
Helllllllllllllllp!
Thanks to anyone that can give me some insight, I'v been killing myself over this for the past hour or two already.
--Mark
The usual way to do this in Rails is to create a route that matches urls like this: /projects/4/photos/new. Doing something else is up to you, but Rails makes it really easy to do stuff like this. See more on routes in Rails 3.
Your entry in routes.rb should look something like this:
resources :projects do
resources :photos
end
Then in app/controllers/photos_controller.rb you'd have this for the "New Photo" form page:
def new
#project = Project.find_by_id(params[:project_id])
end
and this for the action that the form in app/views/photos/new.html.erb submits to:
def create
#project = Project.find_by_id(params[:project_id])
#photo = #project.photos.create(params[:photo])
end
Of course you'll want to have error handling and validation in here, but this is the gist of it. And remember, use GET for idempotent (non state-changing) actions (e.g. GET /projects/4/photos), POST for creating a new thing (e.g. POST /projects/4/photos), and PUT for updating an existing thing (e.g. PUT /projects/4/photos/8).
I am trying to use this redirect_to
redirect_to :controller => :note_categories, :action => :destroy, :note_id => params[:id]
This is the URL that results
http://localhost:3000/note_categories/272?note_id=272
and this is the error message
Unknown action
No action responded to show. Actions: destroy
The reason I am redirecting to the note_categories destroy action, and passing in the note id, is that in the destroy action, I am finding all the note_categories related to note, running some code on them, then destroying them. I know this isn't a great way to be doing this, but I couldn't use :dependant => :destroy because the code I have to run on the note_category before I delete it needs access to current_user, which can't happen in the note_category model.
So yeah, can someone please tell me what am I doing wrong in my redirect_to? Thanks for reading.
The redirect_to method is essentially the Rails implementation of the Post/Redirect/Get (PRG) web design pattern. It's used to prevent duplicate form submissions caused by the user clicking the browser's Refresh button after submitting a form.
The typical Rails usage is like this for creating an object:
A form for creating an object is displayed (new action/HTTP GET)
The user fills in the form
The form is submitted (create action/HTTP POST)
The object is created and saved
A redirect_to is performed with an HTTP 301/302 status to the object's show view or perhaps index
—for editing an object it's:
A form for edit an existing object is displayed (edit action/HTTP GET)
The user fills in the form
The form is submitted (update action/HTTP PUT)
The object is updated and saved
A redirect_to is performed with an HTTP 301/302 status to the object's show view or perhaps index
You can't redirect directly to the destroy action because in RESTful Rails that's intended to be invoked as a result of an HTTP DELETE request and doesn't render a template when it's invoked. The redirect_to method always redirects to a template.
You haven't shown us the code for destroying notes, but I suspect that what you're trying to achieve can be done with a before filter and by having the controller passing the current user to a model method.