Alexei Darmin
Alexei Darmin

Reputation: 2129

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 %>

Upvotes: 3

Views: 408

Answers (1)

adzdavies
adzdavies

Reputation: 1555

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| %>

Upvotes: 1

Related Questions