Rails resources have new params; Update form_for - ruby-on-rails

I changed my config/routes.rb so that a resource uses a different parameter
resources :users, param: :username
That way instead of users/:id, users/:id/edit, etc.
I now use users/:username, users/:username/edit, etc.
Then in my UserController I populate #user like so
#user = User.find_by_username(params[:username])
And for the most part everything works nicely, and I have more meaningful urls.
The problem is I have a really complex form partial that I don't want to split up into two different forms (I still want to use the partial)
but now this:
form_for #user do |f|
....
It routes to its respective update actions but it replaces :username with the id of the #user, effectively causing me to find no user using #user = User.find_by_username(params[:username]) for the update action. I could include form_for #user, params: {username: #user.username} do |f| but this won't work unless #user is populated.
How can I get my form_for to route to my update and create actions respectively, while maintaining my url of /users/:username from my resources?

You can use url_helpers to assist you in creating the url that will route to your respective actions
form_for #user, url: {action: (#user.new_record? ? "create" : "update") } do |f|
Thus, it will be
url: {action: "create"} if #user is a new record
or
url: {action: "update"} if #user is not a new record
Rails will take care of the rest.

Related

Render simple_form from partial view on application.html.erb

I want to create a partial view for my registration form and add it to the application layout file because it will be shown in the navigation bar in a dropdown menu.
How can I create this form in a partial view using simple_form gem and render it on application.html.erb?
<%= simple_form_for(#user, url: account_register_path) do |f| %>
Considering that the code above is the way to create the form and I don't know where I should define #user to be used in the application layout nor if I really need it.
Can someone please clarify this?
Don't put it in a partial, just have the registration view on its own, called with render...
#app/views/layouts/application.html.erb
<%= render "accounts/new" %>
#app/views/accounts/new.html.erb
<%= simple_form_for :user, url: account_register_path do |f| %>
...
Whilst you can use a symbol to populate form_for, it won't include the attributes of the model, or the various hidden methods which give context (such as id etc).
If you wanted to populate the accounts#new view/action with a variable, you'll have to set it in your ApplicationController:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_user
private
def set_user
#user = User.new
end
end
This would allow you to use:
#app/views/accounts/new.html.erb
<%= simple_form_for #user, url: account_register_path do |f| %>
--
The best way to implement this would be with Ajax -- this way, the #user object is only created when you need it (resolves bloat).
We've done this before:
You'd just need the following:
#app/views/layouts/application.html.erb
<%= link_to "New User", account_register_path, remote: true %>
#app/controllers/accounts_controller.rb
class AccountsController < ApplicationController
respond_to :js, :html
def new
#user = User.new
respond_with #user
end
end
#app/views/accounts/new.js.erb
$("<%=j render 'accounts/new' %>").appendTo("body");
You have to define #user in the corresponding controller action (I would assume from your code, that controller is called account and action is register), it would be something like:
#user = User.new
In order to initialize instance being created/updated. Then, attributes used in all fields should match users database table.

How to use form_for with customized route?

The custom routing:
resources :blog, controller: 'posts'
How do I rewrite this line <%= simple_form_for(#post, blog_path) do |f| %> to get rid of the below error?
TypeError in Posts#edit
ActionView::Template::Error (no implicit conversion of Symbol into Integer)
I also tried <%= simple_form_for(blog_path(#post)) do |f| %>, which gets rid of the error, but then if I want to edit the form the inputs are emptied of their saved data.
posts_controller
def new
#post = Post.new
respond_with(#post)
end
def edit
end
def create
#post = Post.new(post_params)
if current_user.admin
#post.save
respond_with(#post)
else
flash[:success] = 'Get out of here.'
redirect_to root_url
end
end
It can take a hash options, including url, so something like this:
Edit: changed blog_path to blogs_path. The blog_path is the show action, not the create action and therefore requires an id (and isn't a post path anyway). Try it out this way.
<%= simple_form_for(#post, url: blogs_path) do |f| %>
Don't know if this applies, but a really cool feature I found the other day was .becomes - where you can change the "class" of your object so that Rails treats it in a different way:
This can be used along with record identification in Action Pack to allow, say, Client < Company to do something like render partial: #client.becomes(Company) to render that instance using the companies/company partial instead of clients/client.
So...
If you had a Blog model, and wanted each #post to be treated as such (again, I don't know if this is your setup at all), you could do the following:
<%= simple_form_for #post.becomes(Blog) do |f| %>
I'll delete if inappropriate; it's come in handy quite a lot for me.
Update
If you'd like your posts_path to be blog (IE url.com/blog/1), you'll want to look at using the path option for the routes generator:
#config/routes.rb
resources :posts, path: "blog", as: :blog # -> url.com/blogs/2

Rails form_for Error | How to Bind Nested ActiveRecord Object to Form

I was developing a Ruby on Rails application. It has a nested route like so:
Rails.application.routes.draw do
root 'trip_plans#index'
resources :trip_plans do
resources :places, except: [:show, :index]
end
end
The trip_plans resource has a TripPlan model and the places resource has a Place model. According to the routes, the new_trip_plan_place_path is a route like /trip_plans/:trip_plan_id/places/new. The views/places/new.html.haml uses a form_for declaration to create a new place within the current trip_plan:
- content_for :title do
%title Add a Place to Your Plan
%header.form-header
.container.form-container
.row
.col-xs-12
%h1 Add a Place
%hr
%article
%section
.container.form-container
= render 'form'
The corresponding edit.html.haml is essentially the same, calling the same _form.html.haml to render the form.
The places_controller's both new and edit action is like:
def new
#trip_plan = TripPlan.find(params[:trip_plan_id])
#place = #trip_plan.places.build
end
def edit
#trip_plan = TripPlan.find(params[:trip_plan_id])
#place = #trip_plan.places.build
end
And the _form.html.haml uses the #place like so:
= form_for #place do |f|
But as #place is a dependent ActiveRecord object, Rails is not able to figure out the correct URLs for new and edit path. It always shows a new form even on edit page.
How can I fix this?
Thanks in advance!
It always shows a new form even on edit page
I guess the problem is this line #place = #trip_plan.places.build in your edit method.
#place = #trip_plan.places.build is nothing but #place = #trip_plan.places.new, so Rails treats #place as a new instance even on edit form.
Changing it to #place = Place.find(params[:id]) should solve your problem.
Update:
You should also change the below
= form_for #place do |f|
to
= form_for [#trip_plan, #place] do |f|

In Rails when a resource create action fails and calls render :new, why must the URL change to the resource's index url?

I have a resource called Books. It's listed as a resource properly in my routes file.
I have a new action, which gives the new view the standard:
#book = Book.new
On the model, there are some attributes which are validated by presence, so if a save action fails, errors will be generated.
In my controller:
#book = Book.create
... # some logic
if #book.save
redirect_to(#book)
else
render :new
end
This is pretty standard; and the rationale for using render:new is so that the object is passed back to the view and errors can be reported, form entries re-filled, etc.
This works, except every time I'm sent back to the form (via render :new), my errors show up, but my URL is the INDEX URL, which is
/books
Rather than
/books/new
Which is where I started out in the first place. I have seen several others posts about this problem, but no answers. At a minimum, one would assume it would land you at /books/create, which I also have a view file for (identical to new in this case).
I can do this:
# if the book isn't saved then
flash[:error] = "Errors!"
redirect_to new_book_path
But then the #book data is lost, along with the error messages, which is the entire point of having the form and the actions, etc.
Why is render :new landing me at /books, my index action, when normally that URL calls the INDEX method, which lists all the books?
It actually is sending you to the create path. It's in the create action, the path for which is /books, using HTTP method POST. This looks the same as the index path /books, but the index path is using HTTP method GET. The rails routing code takes the method into account when determining which action to call. After validation fails, you're still in the create action, but you're rendering the new view. It's a bit confusing, but a line like render :new doesn't actually invoke the new action at all; it's still running the create action and it tells Rails to render the new view.
I just started with the Rails-Tutorial and had the same problem.
The solution is just simple: If you want the same URL after submitting a form (with errors), just combine the new and create action in one action.
Here is the part of my code, which makes this possible (hope it helps someone^^)
routes.rb (Adding the post-route for new-action):
...
resources :books
post "books/new"
...
Controller:
...
def create
#book = Book.new(book_params)
if #book.save
# save was successful
print "Book saved!"
else
# If we have errors render the form again
render 'new'
end
end
def new
if book_params
# If data submitted already by the form we call the create method
create
return
end
#book = Book.new
render 'new' # call it explicit
end
private
def book_params
if params[:book].nil? || params[:book].empty?
return false
else
return params.require(:book).permit(:title, :isbn, :price)
end
end
new.html.erb:
<%= form_for #book, :url => {:action => :new} do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.label :isbn %>
<%= f.text_field :isbn %>
<%= f.label :price %>
<%= f.password_field :price %>
<%= f.submit "Save book" %>
<% end %>
Just had the very same question, so maybe this might help somebody someday. You basically have to make 3 adjustments in order for this thing to work, although my solution is still not ideal.
1) In the create action:
if #book.save
redirect_to(#book)
else
flash[:book] = #book
redirect_to new_book_path
end
2) In the new action:
#book = flash[:book] ? Book.new(flash[:book]): Book.new
3) Wherever you parse the flash hash, be sure to filter out flash[:book].
--> correct URL is displayed, Form data is preserved. Still, I somehow don't like putting the user object into the flash hash, I don't think that's it's purpose. Does anyboy know a better place to put it in?
It doesn't land you at /books/new since you are creating resource by posting to /books/. When your create fails it is just rendering the new action, not redirecting you to the new action. As #MrYoshiji says above you can try redirecting it to the new action, but this is really inefficient as you would be creating another HTTP request and round trip to the server, only to change the url. At that point if it matters you could probably use javascript change it.
It can be fixed by using same url but different methods for new and create action.
In the routes file following code can be used.
resources :books do
get :common_path_string, on: :collection, action: :new
post :common_path_string, on: :collection, action: :create
end
Now you new page will render at url
books/common_path_string
In case any errors comes after validation, still the url will be same.
Also in the form instead using
books_path
use
url: common_path_string_books_path, method: :post
Choose common_path_string of your liking.
If client side validation fails you can still have all the field inputs when the new view is rendered. Along with server side errors output to the client. On re-submission it will still run create action.
In books_controller.rb
def new
#book = current_user.books.build
end
def create
# you will need to have book_params in private
#book = current_user.books.build(book_params)
if #book.save
redirect_to edit_book_path(#book), notice: "Book has been added successfully"
# render edit but you can redirect to dashboard path or root path
else
redirect_to request.referrer, flash: { error: #book.errors.full_messages.join(", ") }
end
end
In new.html.erb
<%= form_for #book, html: {class: 'some-class'} do |f| %>
...
# Book fields
# Can also customize client side validation by adding novalidate:true,
# and make your own JS validations with error outputs for each field.
# In the form or use browser default validation.
<% end %>

Loading the new action creates strange error

I am attempting to create a login system. My UserController has this to control the new action:
def new
#user = User.new
respond_to do |format|
format.json { render :json => #user }
format.html
end
end
and my routes.rb has this to link:
resources :user
The View's form to create a new user is this:
<%= form_for #user do |f| %>
however, I receive an Action Controller error with this:
undefined method `users_path' for #<#<Class
What has baffled me is why it is using users_path. This is a plural reference to my route. Why is it returning a plural error of user_path?? When I route `resources :users' it clears the error, but of course I don't have anything setup for that resource and thus produces other errors.
Inside form_for it creates an appropriate action and method based on whether or not the model is persisted.
If a model is unpersisted it will create an action of :new and method of :post. If it is persisted then it will be :update and :put instead.
For :new the default url is '/users' and for :edit it's '/users/:id'.
The fix is as Jim said. (Beat me to it.) Apply a url option to form_for.
resources :users is actually the correct structure, since it is a collection of users, not a single user. The route construction also expects a plural path (IIRC, it expects a plural path unless it finds a resource, as opposed to resources route), hence the attempt to use users_path.
Passing an explicit url parameter is another option <%= form_for #user, :url => user_path do |f| %>, since you already have things expecting singular routes

Resources