Tim Greider
Tim Greider

Reputation: 418

How can I use "Form_tag" as opposed to "Form_for" in this file

I am new to Ruby on Rails and have been helped immensely by Michael Hartl's excellent book: Ruby on Rails Tutorial. I have gotten to Chapter 8 and am now on the exercises in that chapter. I am having ( I assume a typical "newbie") problem with exercise 1. In this exercise it is asked "1.Refactor the signin form to use form_tag in place of form_for." I have tried to searching for assistance with this in Stackoverflow, Google, Railscast, and many other "web searches" for two days now and I do not seem to find the assistance that I need to answer this problem. The file I am trying to modify with form_tag is below:

<% provide(:title, "Sign in") %>
<h1>Sign in</h1>

<div class="row">
  <div class="span6 offset3">
    <%= form_for(:session, url: sessions_path) do |f| %>

      <%= f.label :email %>
      <%= f.text_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.submit "Sign in", class: "btn btn-large btn-primary" %>
     <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>

I am using Rails 3.2.3 in this application. Can anybody point me in the correct direction? Can anyone help me with this problem? I would be most appreciative.

This is the implementation that uses form_tag:

<% provide(:title, "Sign in") %>
<h1>Sign in</h1>

<div class="row">
  <div class="span6 offset3">
    <%= form_tag( url: sessions_path ) do  %>

      <%= label_tag :email %>
      <%= text_field_tag :email %>

      <%= label_tag :password %>
      <%= password_field_tag :password %>

      <%= submit_tag "Sign in", class: "btn btn-large btn-primary" %>
    <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>

I am using Rspec 2.9.0 and below are the failing tests:

describe "signin page" do
    before { visit signin_path }

    it { should have_selector('h1',    text: 'Sign in') }
    it { should have_selector('title', text: 'Sign in') }
  end 

and

describe "with invalid information" do
            before { click_button "Sign in" }

            it { should have_selector('title', text: 'Sign in') }
            it { should have_selector('div.alert.alert-error', text: 'Invalid') }

            describe "after visiting another page" do
              before { click_link "Home" }
              it { should_not have_selector('div.alert.alert-error') }
            end
      end

and

describe "with valid information" do
            let(:user) { FactoryGirl.create(:user) }
            before do
              fill_in "Email",    with: user.email
              fill_in "Password", with: user.password
              click_button "Sign in"
            end

            it { should have_selector('title', text: user.name) }
            it { should have_link('Profile', href: user_path(user)) }
            it { should have_link('Sign out', href: signout_path) }
            it { should_not have_link('Sign in', href: signin_path) }

            describe "followed by signout" do
                    before { click_link "Sign out" }
                    it { should have_link('Sign in') }
            end
      end

Here's my routes file:

SampleApp::Application.routes.draw do
  resources :users
  resources :sessions, only: [:new, :create, :destroy]


  get "users/new"

  root to: 'static_pages#home'

  match '/signup',  to: 'users#new'
  match '/signin',  to: 'sessions#new'
  match '/signout', to: 'sessions#destroy', via: :delete

  match '/help',    to: 'static_pages#help'
  match '/about',   to: 'static_pages#about'
  match '/contact', to: 'static_pages#contact'
end

Upvotes: 29

Views: 40807

Answers (5)

user1556480
user1556480

Reputation: 41

You have a Session_Helper that you aren't using in your implementation.

EDIT your helper methods.

  def sign_in(user)
    cookies.permanent[:remember_token] = user.remember_token
    session[:user_id] = user.id
  end

  def sign_out
    cookies.delete(:remember_token)
    session[:user_id] = nil    
  end

Then you can simply use the appropriate methods in your session controller. This is a neater implementation that follows the modular approach to design. There also seems to be issues with rspec.

describe "with valid information" do
      let(:user) { FactoryGirl.create(:user) }
      before do
        fill_in "Email",    with: user.email.upcase
        fill_in "Password", with: user.password
        click_button "Sign in"
      end

      it { should have_selector('title', text: user.name) }
      it { should have_link('Profile', href: user_path(user)) }
      it { should have_link('Sign out', href: signout_path) }
      it { should_not have_link('Sign in', href: signin_path) }

Even though everything is correct when you visit the page these tests are flagged. My guess is that you need to change before do because if it was doing what you want it to do the tests would pass.

Anyways, I'm curious to what the problem is with rspec if anyone has an idea!

I think that problem is with user.email.upcase. You don't need .upcase, when you remove it tests will pass.

Upvotes: 1

surrenity
surrenity

Reputation: 11

I got caught not naming the the fields right. When you view source of the original, it shows you the naming scheme.

<%= form_tag(sessions_path) do %>

  <%= label_tag 'session_email', 'Email' %>
  <%= text_field_tag 'session[email]' %>

  <%= label_tag 'session_password', 'Password' %>
  <%= password_field_tag 'session[password]' %>

  <%= submit_tag "Sign in", class: "btn btn-large btn-primary" %>
<% end -%>

Upvotes: 1

Tom
Tom

Reputation: 2075

I have just completed this exercise as well, so I am by no means an expert; however, this is the code that has worked for me and passed all the tests:

../app/views/sessions/new.html.erb

<% provide(:title, "Sign in") %>
<h1>Sign in</h1>

<div class="row">
  <div class="span 6 offset 3">
    <%= form_tag sessions_path do %>

      <%= label_tag :email %>
      <%= text_field_tag :email, params[:email] %>

      <%= label_tag :password %>
      <%= password_field_tag :password %>

      <%= submit_tag "Sign in", class: "btn btn-large btn-primary" %>
    <% end %>

    <p>New User?<%= link_to "Sign Up Now", signup_path %> </p>
  </div>
</div>

I also needed to change the ../app/controllers/sessions_contoller

class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by_email(params[:email])
    if user && user.authenticate(params[:password])
      session[:user] = user.id
      sign_in user
      redirect_to user
    else
      flash.now[:error] = 'Invalid email/password combination'
      render 'new'
    end  
  end

  def destroy
    sign_out
    redirect_to root_path
  end
end

Whilst this works, I'm not entirely sure why it does; if someone could explain why the changes in the controller are required it would be much appreciated. I know that this could be posed a s a separate question but it is closely related to OP and I'm sure we would both find it extremely useful in understanding not just how to get this to work but why it works this way. The following are the original view and controller files:

Original 'form_for' new.html.erb:

<% provide(:title, "Sign in") %>
<h1>Sign in</h1>

<div class="row">
  <div class="span6 offset3">
    <%= form_for(:session, url: sessions_path) do |f| %>

      <%= f.label :email %>
      <%= f.text_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.submit "Sign in", class: "btn btn-large btn-primary" %>
    <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>

and the original sessions_controller:

class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by_email(params[:session][:email])
    if user && user.authenticate(params[:session][:password])
      sign_in user
      redirect_to user
    else
      flash.now[:error] = 'Invalid email/password combination'
      render 'new'
    end  
  end

  def destroy
    sign_out
    redirect_to root_path
  end
end

Upvotes: 31

Kood
Kood

Reputation: 131

I am working on the same step of the tutorial, and your question helped me finding my way.

The use of label and text_field, instead of label_tag and text_field_tag is working fine, and you don't have to change the controller code (this produce the same HTML code as with the original form_for method).

<%= form_tag(sessions_path) do %>
  <%= label :session, :email %>
  <%= text_field :session, :email %>

  <%= label :session, :password %>
  <%= password_field :session, :password %>

  <%= submit_tag("Sign in", class: "btn btn-large btn-primary") %>
<% end %>

You can read details in http://guides.rubyonrails.org/form_helpers.html#dealing-with-model-objects

Upvotes: 15

DVG
DVG

Reputation: 17480

The RoR guides go over how form_tag works.

http://guides.rubyonrails.org/form_helpers.html

Upvotes: 2

Related Questions