Rails STI: implementing child class edit form - ruby-on-rails

There is a lot of discussion around Rails 3 STI and how to use forms, but no definitive answers on StackOverflow. I seem to have run into a similar issue and have attempted the other solutions with no results.
I have two models with the following inheritance set up:
User.rb
class User < ActiveRecord::Base
attr_accessible :email, :first_name, :last_name, #more follows
Waiter.rb
class Waiter < User
On the form at /waiters/users/[:id]/edit, I am attempting to create a form for editing the waiter. However, I am getting the following error upon loading the edit view:
undefined method `waiter_path' for #<#<Class:0x007fbd08cef9d8>:0x007fbd09532fa0>
This is my controller found at /app/controllers/admin/waiters/users_controller.rb:
def edit
form_info
#user = Waiter.find(params[:id])
end
def update
#user = Waiter.find_by_id(params[:id])
if #user.update_attributes(params[:user])
flash[:notice] = "Successfully assigned Waiter."
redirect_to admin_waiters_users_url()
else
form_info
render :action => 'edit'
end
end
And this is the form located in the edit view:
<%= simple_form_for #user do |f| %>
<%= f.input :first_name %>
<%= f.input :last_name %>
<%= f.input :email %>
<%= button_tag(type: 'submit', class: "btn btn-primary") do %>
<i class="icon-ok icon-white"></i> Save
<% end %>
<% end %>
What am I doing wrong here with STI and routing?
UPDATE: here is my rake routes:
admin_waiters_users GET /admin/waiters/users(.:format) admin/waiters/users#index
POST /admin/waiters/users(.:format) admin/waiters/users#create
new_admin_waiters_user GET /admin/waiters/users/new(.:format) admin/waiters/users#new
edit_admin_waiters_user GET /admin/waiters/users/:id/edit(.:format) admin/waiters/users#edit
admin_waiters_user GET /admin/waiters/users/:id(.:format) admin/waiters/users#show
PUT /admin/waiters/users/:id(.:format) admin/waiters/users#update

You should use your routes to see what routes you have defined:
You can run your routes with:
rake routes
I can not see your routes but perhaps waiter_path does not exist.
Perhaps is user_waiter_path(#user) or other router.
Please paste your routes for that the people on stackoverflow can help to you.
I can not see the route waiter_path on your routes, If you have waiter_path inside of your edit view you have remove it.
Also, you can specify what controller and action hit,
<%= simple_form_for #user, :url => { :controller => "admin/waiters/users", :action => "update"} do |f| %>
<%= f.input :first_name %>
<%= f.input :last_name %>
<%= f.input :email %>
<%= f.button :submit, "save", class: "btn btn-primary"%>
<% end %>
You can check with f.button instead button_tag
Regards!

Related

Ruby on rails changes not reflecting after editing user's details

I am Rails newbie. I am creating a section that is pulling existing user's details and when the user click on edit, he can save the changes he has made. However, the changes aren't reflecting once the user saves it. Can you tell me what I am missing in here?
Here's the html/ruby form I am using:
<%= form_tag(html: {:id => 'user_profile_form'}, :url => patient_profile_path(#user), :method => :put) do %>
<%= text_field_tag(:inputFieldName, "#{#user.first_name} #{#user.last_name}", {:disabled => true}) %>
<%= submit_tag 'Save', :id=> 'saveButton' %>
<%= end %>
Here's the routes:
put :patient_profile, to: 'users#patient_profile'
post :dashboard, to: 'dashboard#index'
Here are the controller codes:
def patient_profile
if params[:user]
u = params[:user]
#user.first_name = u[:first_name] unless u[:first_name].nil? || u[:first_name].empty?
#user.last_name = u[:last_name] unless u[:last_name].nil? || u[:last_name].empty?
#user.save!
# index
render :index
end
end
It doesn't look like your form is actually updating anything since your form fields don't match your model. Try simplifying your form action:
View
<%= form_for(#user, html: {:id => 'user_profile_form'}, :url => patient_profile_path(#user), :method => :put) do |f| %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<%= f.submit "Update User" %>
<%= end %>
Controller:
def patient_profile
# TODO: Handle failed validation
#user.update_attributes!(params[:user])
# index
render :index
end
end
def user_params
params.require(:user).permit(:first_name, :last_name)
end

Connecting routes, views and controllers (Rails)

I'm learning Rails so please pardon my amateur mistakes, but I've been stuck for about an hour or two and have made negative progress.
Goal:
From the user profile view, link to a form that allows this user
to change their email. Once the form is submitted, it should trigger
an appropriate method within the user controller.
I can handle the rest, I just haven't managed to connect the parts mentioned above. I have been reading railsTutorial.org and guides.rubyonrails.org but haven't been able to understand routing form_for() sufficiently.
User Profile Link:
<%= #user.email %> <%= link_to "(change)", email_path(#user) %>
Routes
Rails.application.routes.draw do
get 'email' => 'users#email_form'
post 'email' => 'users#changeemail'
end
User Controller
class UsersController < ApplicationController
def email_form
#user = User.find(params[:id])
end
def changeemail
#user = User.find(params[:id])
redirect_to #user
end
end
Currently the error I get once I click the link is Couldn't find User with 'id'= which I assume means user ID is nil because I fail at passing it.
I would greatly appreciate an explanation of what data is being passed through this workflow so I can be a better developer, thank you very much!
EDIT:
The form itself:
<%= form_for(#user, url: user_path(#user)) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :new_email %>
<%= f.text_field :new_email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.submit "Submit New Email", class: "btn btn-primary" %>
<% end %>
You could do this (note :id and "as"):
Rails.application.routes.draw do
get 'email/:id' => 'users#email_form', as :email
post 'email/:id' => 'users#changeemail', as :change_email
end
The :id is then expected to be part of the route.
Alternatively, pass the id directly when generating the url:
<%= #user.email %> <%= link_to "(change)", email_path(id: #user) %>
This will make a call to "UsersController#update"
<%= form_for(#user, url: user_path(#user)) do |f| %>
...instead you would use something like::
<%= form_for(#user, url: change_email_path(#user), method: :put) do |f| %>
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for
...but in terms of best practices, if you want to do separate flow for email updating, you could be more explicit in treating it as a different resource (even though it's still the user record).
For example, you could map these to an explicit 'resource' with a #show and #update action...
Routes:
resources :user_emails, only: [:show, :update]
Controller:
class UserEmailsController < ApplicationController
def show
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
redirect_to #user # goes back to UsersController#show
end
end
Then the route would be:
<%= #user.email %> <%= link_to "(change)", user_email_path(#user) %>
In this case we don't have to say (id: #user) since the 'resource' generates the right urls for you.
...and this would be
<%= form_for(#user, url: user_email_path(#user), method: :post) do |f| %>

I'm getting a NoMethodError on rails, unsure how to access a model through another model

Im writing a game on rails, and am trying to allow a user to create their mine (its a mining game).
I have a table for the users, and a table for mines.
Each user has a ref. ID on their entry, pointing to their mine's ID in the mine table.
I'm getting an error when I try to visit /users/1/mines/new.
undefined method `mines_path'
I can't figure out why.
form in New:
<%= form_for [#mine] do |f| %>
<%= f.label :name %>
<%= f.text_field :name %><br>
<p>Depth: <%= #mine.depth %></p>
<%= f.submit "Submit", id: "submit" %>
<% end %>
Controller:
def new
#user = User.find(params[:user_id])
#mine = #user.mines.new
end
def create
#mine = #user.mines.create(mine_params)
if #mine.save
redirect_to users_mines_path
else
render new_mines_path
end
end
routes:
root 'welcome#index'
resources :sessions, only: [:create]
resources :users do
resources :mines
end
resources :tools, only: [:create]
How can I create a new mine THROUGH the user? Am I doing this correctly in my controller?
Thanks!
In your routes you have mines nested inside users so you need to change your form to something like this:
<%= form_for [#user,#mine] do |f| %>
<%= f.label :name %>
<%= f.text_field :name %><br>
<p>Depth: <%= #mine.depth %></p>
<%= f.submit "Submit", id: "submit" %>
<% end %>
OR
You can specify url option with your path:
<%= form_for #mine, url: user_mines_path(#user) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %><br>
<p>Depth: <%= #mine.depth %></p>
<%= f.submit "Submit", id: "submit" %>
<% end %>
For details on forms refer to Form Helpers
Also as #Vimsha pointed out in your controller you need to use .new instead of .create as create will initialize and save your your mine.
def create
#mine = #user.mines.new(mine_params)
if #mine.save
redirect_to user_mines_path
else
render new_user_mine_path
end
end
#user.mines.create will create the mine. So use #user.mines.new
named route for mine index will be user_mines_path
named route for mine show will be user_mine_path(#mine)
named route for new mine will be new_user_mine_path
Contoller:
def create
#mine = #user.mines.new(mine_params)
if #mine.save
redirect_to user_mines_path
else
render new_user_mine_path
end
end

ActionController::RoutingError: No route matches [POST] "/locations/new"

Routes
resources :locations, :only => [:new, :create]
Controller
class LocationsController < ApplicationController
def new
#location = Location.new
end
def create
#location = Location.new(location_params)
if #location.save
flash[:notice] = 'Created location successfully'
redirect_to new_location_path
else
flash[:notice] = 'Invalid information. Please try again'
render :new
end
end
private
def location_params
params.require(:location).permit(:name, :street, :city, :state)
end
end
Error message when I click save.
ActionController::RoutingError:
No route matches [POST] "/locations/new"
view
<%= simple_form_for :locations do |form| %>
<%= form.input :name %>
<%= form.input :street %>
<%= form.input :city %>
<%= form.input :state %>
<%= form.submit 'Create location' %>
<% end %>
Using capybara to test that when I click on save it creates a new location. I'm not quite sure why it doesn't know what the post route is because I have the new and create routes. If I put a binding.pry right underneath the create method it doesn't get called. So my create method is not being called for some reason.
EDIT:
Rake Routes
locations POST /locations(.:format) locations#
new_location GET /locations/new(.:format) locations#new
A resource normally GETs to new and POSTs to create. So your form is probably submitting back to the new actions instead of submitting to the create action.
Here's the guide for this: http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions
The problem was in the views.
<%= simple_form_for :locations do |form| %>
<%= form.input :name %>
<%= form.input :street %>
<%= form.input :city %>
<%= form.input :state %>
<%= form.submit 'Create location' %>
<% end %>
I was calling on locations symbol when I should have been calling on the instance variable #location from the controller.
The actual problem was that rails 3.2.12 doesn't take private params
Using resources, the new action should be fetched with GET instead of POST. create will be POST. Check your rake routes

Editing has_one objects with form_for in Rails

I have a has_one / belongs_to association between two models -> User and ContactCard. While I am able to create a contact card for a user, whenever I try to edit the card the create action is called from the ContactCardsController rather than update (I can tell because I have different success messages on each). It changes the attributes of the contact card just fine I have to say. I'm mostly happy it's working but would rather patch up any gaps in my understanding of rails paths and associations. What am I missing? Why isn't it using the action I expect? Also if you know of any relevant examples on the web or on github I could study up on, I'm all ears. Thanks!
Contact Cards Controller...
class ContactCardsController < ApplicationController
def create
current_user.build_contact_card(params[:contact_card])
if current_user.contact_card.save
flash[:success] = "Contact Card created!"
redirect_to '/account'
else
flash[:error] = "Fail!"
redirect_to '/account'
end
end
def update
if current_user.contact_card.update_attributes(params[:contact_card])
flash[:success] = "Profile updated."
redirect_to '/account'
else
flash[:error] = "Fail!"
redirect_to '/account'
end
end
Link to edit form...
<%= link_to "Edit Profile", edit_user_contact_card_path(current_user) %>
Edit form...
<%= form_for [current_user, current_user.build_contact_card], :url => user_contact_card_path(current_user) do |f| %>
<%= f.label :first_name %>
<%= f.text_field :first_name %>
<%= f.label :last_name %>
<%= f.text_field :last_name %>
<%= f.submit "Save Contact Details", :class => "btn btn-large btn-primary" %>
<% end %>
Relevant Routes...
resources :users do
resource :contact_card
.....
The User model has_one :contact_card and the ContactCard model belongs_to:user
<%= form_for [current_user, current_user.build_contact_card], :url => user_contact_card_path(current_user) do |f| %>
This is building a new contact card every time you edit! Change it to
<%= form_for [current_user,contact_card], :url => user_contact_card_path(current_user) do |f| %>
You'd want the build_contact_card in the create action of User controller probably

Resources