Search code examples
ruby-on-railsformsmodelscontrollers

Rails 4: nested ressource's plural is an undefined method


I've been working on this issue during the whole week, and despite many blogs that deal with this issue, like this, I'm having an error:

undefined method `employeurs'... 

Let's imagine that you have two specific kinds of users, the employeurs and prestataires. First you fill in the user form with general information (name, email, etc). Then, you choose between the prestataire or employeur form, which will save the user form and redirect you to the second part of the inscription. Both employers and prestataires models have a belong_to relation with users.

With the code below, I've been able to create and save the user form, be redirected to the employer or prestataire form, create an employeur. Yet, when I use @user.employeurs in the employeur controller, like in my index method, I get an error:

undefined method `employeurs'

despite my nested routes:

resources :users do
  resources :employeurs
  resources :prestataires
end 

User model:

class User < ActiveRecord::Base
  has_one :prestataire
  has_one :employeur

  has_secure_password
end

Employeur model:

class Employeur < ActiveRecord::Base
  belongs_to :user
  has_many :projets, as: :projetable
  has_many :prestataires, through: :projets
  has_many :offres, through: :projets
  has_many :feedbacks, through: :projets
  validates :siren, :societe, :code_postal, presence: true
end

User controller:

class UsersController < ApplicationController

  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  # GET /users/1/edit
  def edit
    @user = User.find(params[:id])
  end

  # POST /users
  # POST /users.json
  def create
    @user = User.new(user_params)

    respond_to do |format|
      if @user.save
        if params[:commit] == 'Prestataire'
        format.html { redirect_to new_user_prestataire_path(user_id: @user), notice: "Renseignez vos informations d'employeur" }
        format.json { render action: 'show', status: :created, location: @user }
        else 
        format.html { redirect_to new_user_employeur_path(user_id: @user), notice: "Renseignez vos informations de prestataire" }
        format.json { render action: 'show', status: :created, location: @user }
        end
      else
        format.html { render action: 'new' }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /users/1
  # PATCH/PUT /users/1.json
  def update
    @user = User.find(params[:id])
    respond_to do |format|
      if @user.update(user_params)
        if params[:commit] == 'Prestataire'
        format.html { redirect_to new_user_prestataire_path(user_id: @user), notice: 'User was successfully updated.' }
        format.json { head :no_content }
        else 
        format.html { redirect_to new_user_employeur_path(user_id: @user), notice: "User was successfully updated." }
        format.json { head :no_content }
        end
      else
        format.html { render action: 'edit' }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /users/1
  # DELETE /users/1.json
  def destroy
    @user = User.find(params[:id])
    @user.destroy
    respond_to do |format|
      format.html { redirect_to users_url }
      format.json { head :no_content }
    end
  end

private
  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation, :surname, :forename, :civility, :phone)
  end

end

Employeur controller:

class EmployeursController < ApplicationController
  before_filter :load_user

  def index
    @employeurs = @user.employeurs.all
  end


  def show
    @employeur = Employeur.find(params[:id])
  end

  def new
    @employeur = @user.build_employeur
  end

  def edit
    @employeur = Employeur.find(params[:id])
  end


  def create
    @employeur = @user.build_employeur(employeur_params)
    respond_to do |format|
      if @employeur.save
        format.html { redirect_to [@user, @employeur], notice: 'Employeur was successfully created.' }
        format.json { render action: 'show', status: :created, location: @employeur }
      else
        format.html { render action: 'new' }
        format.json { render json: @employeur.errors, status: :unprocessable_entity }
      end
    end
  end

  def update
  @employeur = Employeur.find(params[:id])
    respond_to do |format|
      if @employeur.update_attributes(employeur_params)
        format.html { redirect_to [@user, @employeur], notice: 'Employeur was successfully created.' }
        format.json { render action: 'show', status: :created, location: @employeur }
      else
        format.html { render action: 'new' }
        format.json { render json: @employeur.errors, status: :unprocessable_entity }
      end
    end
  end


def destroy
  @employeur = Employeur.find(params[:id])
  @employeur.destroy
  respond_to do |format|
    format.html { redirect_to @user }
    format.json { head :no_content }
  end
end

private
  def load_user
    @user = User.find(params[:user_id])
  end

  def employeur_params
    params.require(:employeur).permit(:siren, :societe, :code_postal)
  end

end

User form:

<%= form_for(@user) do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% @user.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :civility, 'Titre de civilité: ' %><br>
    <%= f.text_field :civility %>
  </div>

  <div class="field">
    <%= f.label :forename, 'Prénom: ' %><br>
    <%= f.text_field :forename %>
  </div>
  <div class="field">
    <%= f.label :surname, 'Nom de famille: ' %><br>
    <%= f.text_field :surname %>
  </div>
  <div class="field">
    <%= f.label :email, 'Email: ' %><br>
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :password, 'Mot de passe: ' %><br>
    <%= f.password_field :password, size: 40 %>
  </div>
  <div class="field">
    <%= f.label :password_confirmation, 'Confirmation de mot de passe: ' %><br>
    <%= f.password_field :password_confirmation, size: 40 %>
  </div>
  <div class="field">
    <%= f.label :phone, 'Numéro de téléphone: ' %><br>
    <%= f.text_field :phone %>
  </div>
  <div class="actions">
    <%= f.submit "Employeur" %>
    <%= f.submit "Prestataire" %>
  </div>
<% end %>

Employeur form:

<%= form_for [@user, @employeur] do |f| %>
  <% if @employeur.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@employeur.errors.count, "error") %> prohibited this employeur from being saved:</h2>

      <ul>
      <% @employeur.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :siren, 'Siren: ' %><br>
    <%= f.text_field :siren %>
  </div>
  <div class="field">
    <%= f.label :societe, 'Société: ' %><br>
    <%= f.text_field :societe %>
  </div>
  <div class="field">
    <%= f.label :code_postal, 'Code Postal: ' %><br>
    <%= f.text_field :code_postal %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Employeur index view:

<h1>Liste des employeurs</h1>
<ul>
<% @employeurs.each do |employeur| %>
  <li><%= @user.employeur.siren %> | <%= @user.employeur.societe %> <%= @user.employeur.code_postal %> <%= link_to "++", user_employeur_path(employeur) %></li>
<% end %>
</ul>

Since I don't understand where this mistake can come from, I might have forgotten to add some useful parts of my code. Don't hesitate asking. Thank you in advance.


Solution

  • You’re confusing routes and models. Nesting routes doesn’t have any effect on the organisation of your models.

    resources :users do
      resources :employeurs
      resources :prestataires
    end
    

    These routes have the effect of wiring particular URL requests to controller methods. They also set up helper methods like users_path and such.

    You declare your model like this:

    class User < ActiveRecord::Base
      has_one :employeur
    end
    

    So a User has one Employeur, why do you expect it to have more than one?

    I’d recommend that you reconsider your entire model structure. It sounds to me that Employeur and Prestataire are actually just subtypes of User, not their own types.