I try to reproduce in Rails 4 a functionality that was working fine in Rails 3: create a child record from the Show view of its parent using new_parent_child_path(@parent)
.
Unfortunately, when I validate the child creation, I get a message saying that the child.parent_id field cannot be null
. Reading around, it looks that this new_parent_child_path(@parent) functionality is indeed hardly used. Here are parts of my code...
Models:
class BusinessArea < ActiveRecord::Base
... some checks ...
validates :playground_id, presence: true
belongs_to :playground
belongs_to :owner, :class_name => "User", :foreign_key => "owner_id" # helps retrieving the owner name
belongs_to :status, :class_name => "Parameter", :foreign_key => "status_id" # helps retrieving the status name
has_many :business_flows
end
class BusinessFlow < ActiveRecord::Base
... some checks ...
validates :playground_id, presence: true
belongs_to :playground
belongs_to :owner, :class_name => "User", :foreign_key => "owner_id" # helps retrieving the owner name
belongs_to :status, :class_name => "Parameter", :foreign_key => "status_id" # helps retrieving the status name
has_many :business_processes
belongs_to :business_area
end
Routes:
ODQStep1::Application.routes.draw do
#static pages
get '/help', to: "static_pages#help"
get '/about', to: "static_pages#about"
get '/contact', to: "static_pages#contact"
get '/home', to: "static_pages#home"
#root definition
root to: "static_pages#home"
resources :sessions, only: [:new, :create, :destroy]
get '/signin', to: 'sessions#new' , via: :get
match '/signout', to: 'sessions#destroy', via: :delete
resources :parameters
resources :business_areas do
resources :business_flows, :only=>[:new, :create]
end
end
Link to create child business flow is at the end of the Business Area Show view:
<% provide(:title, 'Managing business areas') %>
<h1>Business area: <%= @business_area.name %></h1>
<ul class="mid_menu">
<li><%= link_to 'Edit', edit_business_area_path(@business_area) %> |</li>
<li><%= link_to 'Back to list', business_areas_path %> |</li>
<li><%= link_to 'Destroy', @business_area, confirm: 'Are you sure?', method: :delete %></li>
</ul>
<!-- <p id="notice"><%= notice %></p> -->
<div class="tabbable">
<ul class="nav nav-tabs">
<li class="active"><a href="#tab1" data-toggle="tab">Definition</a></li>
<li><a href="#tab2" data-toggle="tab">Ownership</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tab1">
<!-- Tab content for Definition -->
<div class="row">
<div class="span2 text-right">Name:
</div>
<div class="span6"><%= @business_area.name%>
</div>
<div class="span1 text-right">Code:
</div>
<div class="span1"><%= @business_area.code%>
</div>
<div class="span1 text-right">Status:
</div>
<div class="span1"><%= @business_area.status.name%>
</div>
</div>
<div class="row">
<div class="span2 text-right">Description:
</div>
<div class="span10"><%= @business_area.description%>
</div>
</div>
<div class="row">
<div class="span2 text-right">Hierarchy:
</div>
<div class="span2"><%= @business_area.hierarchy%>
</div>
</div>
<div class="row">
<div class="span2 text-right">PCF index:
</div>
<div class="span2"><%= @business_area.PCF_index%>
</div>
<div class="span2 text-right">PCF reference:
</div>
<div class="span2"><%= @business_area.PCF_reference%>
</div>
</div>
<!-- Tab content -->
</div>
<div class="tab-pane" id="tab2">
<!-- Tab content for Ownership -->
<div class="row">
<div class="span2 text-right">Unique id:
</div>
<div class="span2"><%= @business_area.id%>
</div>
<div class="span2 text-right">Created by:
</div>
<div class="span2"><%= @business_area.created_by%>
</div>
</div>
<div class="row">
<div class="span2 text-right">Playground id:
</div>
<div class="span2"><%= @business_area.playground_id%>
</div>
<div class="span2 text-right">Created at:
</div>
<div class="span2"><%= @business_area.created_at%>
</div>
</div>
<div class="row">
<div class="span2 text-right">Owner:
</div>
<div class="span2"><%= @business_area.owner.login%>
</div>
<div class="span2 text-right">Updated by:
</div>
<div class="span2"><%= @business_area.updated_by%>
</div>
</div>
<div class="row">
<div class="span2 text-right">
</div>
<div class="span2">
</div>
<div class="span2 text-right">updated at:
</div>
<div class="span2"><%= @business_area.updated_at%>
</div>
</div>
<!-- Tab content -->
</div>
</div>
</div>
<table width=100%>
<tr><td><hr /></td></tr>
<tr align="left">
<th>Linked Business Flows</th>
<th></th>
</tr>
<tr>
<td>
<table class="table table-striped table-condensed">
<%@business_area.business_flows.each do |business_flow| %>
<tr align="left">
<td valign="top"> <%=link_to business_flow.code, business_flow%> </td>
<td valign="top"> <%=business_flow.name%> </td>
<td valign="top"> <%=business_flow.description%> </td>
<td> </td>
</tr>
<% end%>
</table>
</td>
</tr>
<tr>
<td>
<%= link_to 'Add business flow', new_business_area_business_flow_path(@business_area) %>
</td>
</tr>
</table>
Business flow form
<%= form_for [@business_area, @business_flow] do |f| %>
<% if @business_flow.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@business_flow.errors.count, "error") %> prohibited this business_flow from being saved:</h2>
<ul>
<% @business_flow.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="row">
<div class="span2 text-right">Name:
</div>
<div class="span10 field"><%= f.text_field :name, :class => "span8" %>
</div>
</div>
<div class="row">
<div class="span2 text-right">Code:
</div>
<div class="span2 field"><%= f.text_field :code %>
</div>
<div class="span2 text-right">Status:
</div>
<div class="span2 field"><%= f.collection_select :status_id, @statuses_list, :id, :name %>
</div>
</div>
<div class="row">
<div class="span2 text-right">Description:
</div>
<div class="span10 field"><%= f.text_area :description, :rows => 5, :class => "span8" %>
</div>
</div>
<div class="row">
<div class="span2 text-right">Hierarchy:
</div>
<div class="span2 field"><%= f.text_field :hierarchy %>
</div>
</div>
<div class="row">
<div class="span2 text-right">PCF index:
</div>
<div class="span2 field"><%= f.text_field :PCF_index %>
</div>
<div class="span2 text-right">PCF reference:
</div>
<div class="span2 field"><%= f.text_field :PCF_reference %>
</div>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Business Flows controller
class BusinessFlowsController < ApplicationController
# Check for active session
before_action :signed_in_user
# Retrieve current business flow
before_action :set_business_flow, only: [:show, :edit, :update, :destroy]
# Create the list of statuses to be used in the form
before_action :set_statuses_list, only: [:new, :edit, :update, :create]
# GET /business_flows
# GET /business_flows.json
def index
@business_flows = BusinessFlow.order("hierarchy ASC")
respond_to do |format|
format.html # index.html.erb
format.json { render json: @business_flows }
end
end
# GET /business_flows/1
# GET /business_flows/1.json
def show
### Retrieved by Callback function
end
# GET /business_flows/new
# GET /business_flows/new.json
def new
@business_flow = BusinessFlow.new
@business_flow.business_area_id = params[:business_area_id]
end
# GET /business_flows/1/edit
def edit
### Retrieved by Callback function
end
# POST /business_flows
# POST /business_flows.json
def create
@business_flow = BusinessFlow.new(business_flow_params)
@business_flow.business_area_id = params[:business_area_id]
@business_flow.updated_by = current_user.login
@business_flow.created_by = current_user.login
@business_flow.playground_id = current_user.current_playground_id
@business_flow.owner_id = current_user.id
respond_to do |format|
if @business_flow.save
format.html { redirect_to @business_flow, notice: 'Business flow was successfully created.' }
format.json { render json: @business_flow, status: :created, location: @business_flow }
else
format.html { render action: "new" }
format.json { render json: @business_flow.errors, status: :unprocessable_entity }
end
end
end
# PUT /business_flows/1
# PUT /business_flows/1.json
def update
### Retrieved by Callback function
@business_flow.updated_by = current_user.login
respond_to do |format|
if @business_flow.update_attributes(business_flow_params)
format.html { redirect_to @business_flow, notice: 'Business flow was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: @business_flow.errors, status: :unprocessable_entity }
end
end
end
# DELETE /business_flows/1
# DELETE /business_flows/1.json
def destroy
### Retrieved by Callback function
@business_flow.destroy
respond_to do |format|
format.html { redirect_to business_flows_url }
format.json { head :no_content }
end
end
### private functions
private
### Use callbacks to share common setup or constraints between actions.
# Retrieve current business flow
def set_business_flow
@business_flow = BusinessFlow.includes(:owner, :status).find(params[:id])
end
### before filters
# Check for active session
def signed_in_user
redirect_to signin_url, notice: "You must log in to access this page." unless signed_in?
end
### strong parameters
def business_flow_params
params.require(:business_flow).permit(:code, :name, :hierarchy, :status_id, :PCF_index, :PCF_reference, :description)
end
end
Log when clicking the New business flow link
--- !ruby/hash:ActionController::Parameters
action: new
controller: business_flows
business_area_id: '2'
Log when validating business flow creation
--- !ruby/hash:ActionController::Parameters
utf8: ✓
authenticity_token: PwhUukDm3f0OIDhOZfeKxwgtH7M5GFoOuy63Mt7Tcw=
business_flow: !ruby/hash:ActionController::Parameters
name: aaaaaaa
code: bbbbbbb
status_id: '8'
description: ''
hierarchy: cccccccc
PCF_index: ''
PCF_reference: ''
commit: Create Business flow
action: create
controller: business_flows
I could imagine hiding a business_area_id
field in the form, but I hope for a smarter way to have the parameter passed to the new() function also available in the create() function even though they have to go through the strong parameters.
Being a beginner, I can't say if my analysis of the problem is correct, and I hope you can help me finding a nice solution.
Thanks a lot for your help,
Best regards,
Fred
I see that your new action does have the
business_area_id: '2'
parameter but, in your controller's new action you aren't instantiating the business_area. It should look like this:Now that you instantiated the
business_area
, theform_for
should be able to correctly build the nested path which should include thebusiness_area_id
. Now your business_flows controller's create action should look like this:To make sure the form's action includes the
business_area_id
check the rendered html. The form's attributes should look like:action="/business_areas/<:business_area_id>/business_flows" method="post"
where
<:business_area_id>
is the integer id of the parent, in your case2
.You're using nested routes so that's good. You didn't post your view code so I'm assuming you aren't POSTing to the create action of your business_flows controller using the nested route. Your business_flow view's form should look something like:
This will make the form post to the nested create action of your business_flows controller. That way the parent's id will be in the route as
params[:business_area_id]
. Then you can simply assign that id to your child model like you do in your create action.