Reputation: 479
I have revised the question yet again to include the controller files.
In our app we have three models. A User model, Scoreboard model, Team model. A User has_many scoreboards and a scoreboard belongs_to a User. The code in the scoreboards controller for the create action that associated the two is "@scoreboard = current_user.scoreboards.build". This code works perfectly fine.
Now, the problem arises with the third model. The scoreboard model has_many teams and each team belongs_to a scoreboard. It is a has_many, belongs_to relationship. Therefore, the foreign key is on the teams table. The scoreboard and team migration and model files are given below respectively.
Scoreboard Model
class Scoreboard < ActiveRecord::Base
belongs_to :user
has_many :teams, dependent: :destroy
default_scope -> { order(created_at: :desc) }
end
Scoreboard Migration
class CreateScoreboards < ActiveRecord::Migration
def change
create_table :scoreboards do |t|
t.string :name_of_scoreboard
t.string :name_of_organization
t.string :name_of_activity
t.references :user, index: true
t.timestamps null: false
end
add_foreign_key :scoreboards, :users
add_index :scoreboards, [:user_id, :created_at]
end
end
Team Model
class Team < ActiveRecord::Base
belongs_to :scoreboard
end
Team Migration
class CreateTeams < ActiveRecord::Migration
def change
create_table :teams do |t|
t.string :name
t.integer :win
t.integer :loss
t.integer :tie
t.references :scoreboard, index:true
t.timestamps null: false
end
add_foreign_key :teams, :scoreboards
end
end
I think I have associated the models correctly. Therefore, the code in my Teams controller for the create action should create the associations correctly. The controllers are as follows:
Scoreboard controller:
class ScoreboardsController < ApplicationController
before_action :logged_in_user, only: [:new, :create, :show, :index]
before_action :correct_user, only: [:destroy, :edit, :update]
def new
@scoreboard = Scoreboard.new
end
def create
@scoreboard = current_user.scoreboards.build(scoreboard_params)
if @scoreboard.save
flash[:scoreboard] = "Scoreboard created successfully"
redirect_to scoreboard_path(@scoreboard)
else
render 'new'
end
end
def show
@scoreboard = Scoreboard.find_by_id(params[:id])
end
def index
if params[:search]
@scoreboards = Scoreboard.all.search(params[:search])
else
@scoreboards = current_user.scoreboards
end
end
def edit
@scoreboard = Scoreboard.find_by_id(params[:id])
end
def update
@scoreboard = Scoreboard.find_by_id(params[:id])
if @scoreboard.update_attributes(scoreboard_params)
flash[:success] = "Updated Successfully"
redirect_to scoreboard_path(@scoreboard)
else
render 'edit'
end
end
def destroy
@scoreboard = Scoreboard.find_by_id(params[:id])
@scoreboard.destroy
flash[:success] = "Deleted Successfully."
redirect_to scoreboards_path
end
private
def scoreboard_params
params.require(:scoreboard).permit(:name_of_scoreboard, :name_of_organization,
:name_of_activity, :starts_at, :ends_at, :cities, :states, :country, :picture )
end
def correct_user
@user = Scoreboard.find(params[:id]).user
redirect_to scoreboards_path unless current_user?(@user)
end
end
And this is the teams controller:
class TeamsController < ApplicationController
def new
@team = Team.new
end
def create
@scoreboard= current_user.scoreboards.build
@team = @scoreboards.teams.build(team_params)
if @team.save
flash[:success] = "Saved Successfully"
redirect_to scoreboard_path
else
render 'new'
end
end
def index
end
def show
end
private
def team_params
params.require(:team).permit(:name, :win, :loss, :tie)
end
end
However, I get an error "undefined method `teams' for nil:NilClass" when I submit my form which applies the create action. I am not sure why this is happening because I did the exact same thing with the Users and Scoreboard model.
Upvotes: 0
Views: 2994
Reputation: 102249
There are many ways in Rails to create associated records via user input. Let's assume in this case that you only want to create teams in the context of a scoreboard.
The first thing we want to do is nest the teams routes under a scoreboard:
# config/routes.rb
Rails.application.routes.draw do
# ...
resources :scoreboards do
resources :teams, shallow: true
end
end
This will give us routes to create teams which are nested:
Prefix Verb URI Pattern Controller#Action
scoreboard_teams GET /scoreboards/:scoreboard_id/teams(.:format) teams#index
POST /scoreboards/:scoreboard_id/teams(.:format) teams#create
new_scoreboard_team GET /scoreboards/:scoreboard_id/teams/new(.:format) teams#new
edit_team GET /teams/:id/edit(.:format) teams#edit
team GET /teams/:id(.:format) teams#show
PATCH /teams/:id(.:format) teams#update
PUT /teams/:id(.:format) teams#update
DELETE /teams/:id(.:format) teams#destroy
Why?, because this gives a RESTful design where it is very obvious that you are creating a nested resource. Nesting the index route is really optional.
If you want a non nested index which displays all the teams regardless of scoreboard you would define the routes as such:
# config/routes.rb
Rails.application.routes.draw do
# ...
resources :teams, only: :index
resources :scoreboards do
resources :teams, shallow: true, except: :index
end
end
As you alread have guessed we want to build a Team
instance in Scoreboards#show
# GET /scoreboards/1
def show
@team = @scoreboard.teams.build
end
We also need to add a team form to the scoreboard. Let's use a reusable partial for this:
# app/views/scoreboards/show.html.erb
<%= render partial: 'teams/form' %>
# app/views/teams/_form.html.erb
<%= form_for(@team.new_record? ? [@scoreboard, @team] : @team ) do |f| %>
<% if @team.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@team.errors.count, "error") %> prohibited this team from being saved:</h2>
<ul>
<% @team.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :wins %><br>
<%= f.number_field :wins %>
</div>
<div class="field">
<%= f.label :loss %><br>
<%= f.number_field :loss %>
</div>
<div class="field">
<%= f.label :tie %><br>
<%= f.number_field :tie %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Notice @team.new_record? ? [@scoreboard, @team] : @team
. We use the ternary operator (a compact if statement) so that the form routes correctly both for a new record and when editing a record.
So in our TeamsController
we want to setup a callback to load the Scoreboard from params[:schoolboard_id]
so that we can use it our new and create action.
class TeamsController < ApplicationController
before_action :set_scoreboard, only: [:new, :create]
before_action :set_team, only: [:show, :edit, :update, :destroy]
# GET /scoreboard/:scoreboard_id/teams
def index
@scoreboard = Scoreboard.eager_load(:teams)
.find(params[:scoreboard_id])
@teams = @scoreboard.teams
end
# GET /teams/1
def show
end
# GET /scoreboard/:scoreboard_id/teams/new
def new
@team = @scoreboard.teams.new
end
# POST /scoreboard/:scoreboard_id/teams
def create
@team = @scoreboard.teams.new(team_params)
if @team.save
redirect_to @team, notice: 'Team was successfully created.'
else
render :new
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_team
@team = Team.find(params[:id])
end
def set_scoreboard
@scoreboard = Scoreboard.find(params[:scoreboard_id])
end
# Only allow a trusted parameter "white list" through.
def team_params
params.require(:team).permit(:name, :wins, :loss, :tie, :scoreboard_id)
end
end
Upvotes: 2
Reputation: 460
I fixed up my answer in response to your question:
I think that your associations are correct and maybe try using @scoreboard instead of scoreboard. So something like this maybe?
def blah
.... some code ....
@scoreboard = current_user.scoreboards.build
@team = @scoreboard.teams.build
.... more code ....
end
Upvotes: 0