How to display validation errors when using nested forms? - ruby-on-rails

First of all sorry about my English and about my knowledge of Rails, I am beginner who just started.
I have 2 models with relations:
CargoItems:
class CargoItem < ActiveRecord::Base
belongs_to :cargo
attr_accessible :cargo_id, :height, :length, :pieces, :weight, :width
validates :cargo, :height, :length, :width, :weight, :pieces, presence: true
validates :height, :length, :width, :pieces, numericality: { only_integer: true, greater_than_or_equal_to: 1}
validates :weight, numericality: { only_integer: true, greater_than_or_equal_to: 100}
end
Cargos:
class Cargo < ActiveRecord::Base
belongs_to :airport
belongs_to :user
belongs_to :cargo_state
belongs_to :cargo_price
belongs_to :cargo_description
has_many :cargo_items, :inverse_of => :cargo, :dependent => :destroy
attr_accessible :departure_date, :cargo_state_id, :airport_id, :cargo_price_id, :cargo_description_id, :cargo_items_attributes
accepts_nested_attributes_for :cargo_items, :allow_destroy => true, :reject_if => :all_blank
validates_associated :cargo_items
validates :departure_date, :cargo_state, :airport, :cargo_price, :cargo_description, presence: true
validates :departure_date, date: { after: Proc.new { Date.today - 1.day }, before: Proc.new { Time.now + 1.year } }, :on => :create
default_scope :order => 'departure_date DESC'
end
I use following GEMs: simple_forms, nested_forms. This is form to add new Cargo with multiple CargoItems (they have possibility to be added dynamically) belonging to Cargos:
<%= simple_nested_form_for #cargo, :wrapper => false do |f| %>
<%= f.association :airport, :label_method => :full_airport_name, :value_method => :id , :order => :iata_code %>
<%= f.input :departure_date , as: :date, start_year: Date.today.year,
end_year: Date.today.year + 16,
order: [:day, :month, :year] %>
<%= f.association :cargo_description, :label_method => :description, :value_method => :id, :order => :description %>
<%= f.association :cargo_price, :label_method => :price, :value_method => :id %>
<%= f.association :cargo_state, :label_method => :state, :value_method => :id %>
<hr>
<table>
<tr>
<th><%= :length %></th>
<th><%= :width %></th>
<th><%= :height %></th>
<th><%= :weight %></th>
<th><%= :pieces %></th>
</tr>
<%= f.simple_fields_for :cargo_items, #cargo_item do |cargo_items_fields| %>
<tr class="fields">
<td><%= cargo_items_fields.text_field :length %></td>
<td><%= cargo_items_fields.text_field :width %></td>
<td><%= cargo_items_fields.text_field :height %></td>
<td><%= cargo_items_fields.text_field :weight %></td>
<td><%= cargo_items_fields.text_field :pieces %></td>
<td><%= cargo_items_fields.link_to_remove "Remove this item", :confirm => 'Are you sure you want to remove this item?' %></td>
</tr>
<% end %>
</table>
<%= f.link_to_add "Add a item", :cargo_items %>
<div class="actions">
<%= f.button :submit %>
</div>
<% end %>
Cargo controller:
def new
#cargo = Cargo.new
#cargo_item = CargoItem.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #cargo }
end
end
def create
#cargo = Cargo.new(params[:cargo])
#cargo.user_id = current_user[:id]
respond_to do |format|
if #cargo.save
format.html { redirect_to #cargo, notice: 'Cargo was successfully created.' }
format.json { render json: #cargo, status: :created, location: #cargo }
else
format.html { render action: "new" }
format.json { render json: #cargo.errors, status: :unprocessable_entity }
end
end
end
My problem is, validation errors are not shown for CargoItems, model is actually validating. I am not able to save Cargos with CargoItems which does not fulfill validating rules. But in case validations are not met, Cargo is not saved and it just stayed on the same page without any notification that CargoItems fields are invalid. Cargos fields validation errors are shown properly.
Thanx a lot for helping me.

You need to add this errors notication before your form. Take a look at mine for example
<% if #author.errors.any? %>
<div class="alert alert-block">
<ul>
<% #author.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% if #book.errors.any? %>
<div class="alert alert-block">
<ul>
<% #book.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form_for([#author, #book], html: { class: "well" }) do |f| %>
#labels and buttons...
<% end %>
I have an Author who has many Books, like so:
Author:
class Author < ActiveRecord::Base
attr_accessible :name
has_many :books, dependent: :destroy
accepts_nested_attributes_for :books, allow_destroy: true
validates :name, presence: true
validates :name, length: { minimum: 3 }
end
Book:
class Book < ActiveRecord::Base
attr_accessible :name, :year
belongs_to :author
validates :name, :year, presence: true
validates :year, numericality: { only_integer: true, less_than_or_equal_to: Time.now.year }
end

Simple Form also allows you to use label, hint, input_field, error and full_error helpers. for more detail documentation --> https://github.com/plataformatec/simple_form
Example code with error,
<%= simple_form_for #user do |f| %>
<%= f.label :username %>
<%= f.input_field :username %>
<%= f.hint 'No special characters, please!' %>
<%= f.error :username, id: 'user_name_error' %>
<%= f.full_error :token %>
<%= f.submit 'Save' %>
<% end %>

Related

Ruby on Rails. Nested form from field_form only return a hash instead of hash of array

I have an nested form and I want to save a hash into the field_form but this one only saves the last one in the params.
This is my controller
def new
#inventory_products = []
#inventory = Inventory.new
#products = Product.all
#products.each do |p|
#inventory_products << p.inventory_products.build
end
end
This is Form
<%= form_for #inventory do |f| %>
<%= render 'shared/error_messages', object: f.object%>
<%= f.label :description, "Description" %>
<%= f.text_area :description, class: 'form-control' %>
<%= f.label :warehouse, "Warehouse" %>
<%= f.select :warehouse_id, options_for_select(Warehouse.all.map {
|b| [ b.name, b.id ] }),
prompt: "foobar"%>
<%= f.label :products, "Productos" %>
<table class = "table table-bordered">
<thead>
<tr>
<th>+</th>
<th>Codigo</th>
<th>Nombre</th>
<th>Cantidad a Ingresar</th>
</tr>
</thead>
<tbody>
<% #inventory_products.each_with_index do |i, index|%>
<%= f.fields_for "inventory_products[]", i do |iv|%>
<tr>
<td>
<%= iv.check_box :product_id, {class: 'add_product',checked:false},iv.object.product_id.to_s, "0" %>
</td>
<td><%= #products[index].code %></td>
<td><%= #products[index].name %></td>
<td>
<%= iv.number_field :quantity, class:"form-control quantity#{iv.object.product_id.to_s}", readonly: true %>
</td>
</tr>
<% end %>
<% end %>
Inventory model
class Inventory < ApplicationRecord
has_many :inventory_products
has_many :products, through: :inventory_products
belongs_to :warehouse
accepts_nested_attributes_for :inventory_products
validates:description, presence:true, length: {maximum:150}
end
Inventory Product Model
class InventoryProduct < ApplicationRecord
belongs_to :product
belongs_to :inventory
accepts_nested_attributes_for :product
validates:quantity, presence:true, numericality: { greater_than: 0}
end
Product Model
class Product < ApplicationRecord
has_many :inventory_products
has_many :inventories, through: :inventory_products
end
Params
<ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"xwu4sCQCCCWOwXqbJkvVl9MDs2HRmjdT8IL2eMdMi0KHbibzHuQNmIWpot7fVqohvvxDlMIAEBzlDZB0OW3DCQ==", "inventory"=>{"description"=>"", "warehouse_id"=>"", "inventory_products"=>{"product_id"=>"1", "quantity"=>""}}, "commit"=>"Agregar", "controller"=>"inventories", "action"=>"create"}
def new
#inventory = Inventory.new
#products = Product.all
#products.each do |p|
#inventory << p.inventory_products.new(product: p)
end
end
def create
#inventory = Inventory.new(inventory_params)
if #inventory.save
# ...
else
# ...
end
end
private
def inventory_params
params.require(:inventory).permit(inventory_products_attributes: [:product_id, :quantity, :_keep])
end
Instead of manually iterating through the records you just use fields_for on the association and rails will create the proper params:
<%= form_for(#inventory) do |f| %>
<%= f.fields_for :inventory_products do |iv|%>
<tr>
<td>
<%= iv.hidden_field :product_id %>
<%= iv.check_box :_keep, { class: 'add_product', checked:false }%>
</td>
<td><%= iv.object.code %></td>
<td><%= iv.object.name %></td>
<td>
<%= iv.number_field :quantity, class:"form-control quantity#{iv.object.product_id.to_s}", readonly: true %>
</td>
</tr>
<% end %>
<% end %>
We also introduce a virtual attribute named _keep instead of using a checkbox with the product id - which seems like a very overcomplicated and hacky setup.
class InventoryProduct < ApplicationRecord
belongs_to :product
belongs_to :inventory
attr_reader :_keep
accepts_nested_attributes_for :product, reject_if: :not_acceptable?
validates :quantity,
presence: true,
numericality: { greater_than: 0 }
def _keep=(value)
#_keep = typecast_to_boolean(value)
end
def not_acceptable?(attributes)
!typecast_to_boolean( attributes[:_keep] )
end
private
def typecast_to_boolean(value)
ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
end
end

Rails has_many :through, cocoon

Player.rb
class Player < ApplicationRecord
belongs_to :user
has_many :player_games, dependent: :destroy, inverse_of: :player
has_many :games, :through => :player_games
validates :firstname, presence: true, length: { minimum: 3, maximum: 88 }
validates :lastname, presence: true, length: { minimum: 3, maximum: 88 }
validates :user_id, presence: true
accepts_nested_attributes_for :player_games, reject_if: :reject_posts, allow_destroy: true
def reject_posts(attributes)
attributes['game_id'].to_i == 0
attributes['score'].blank?
attributes['time'].blank?
end
def initialized_player_games # this is the key method
[].tap do |o|
Game.all.each do |game|
if g = player_games.find { |g| g.game_id == game.id }
o << g.tap { |g| g.enable ||= true }
else
o << PlayerGame.new(game: game)
end
end
end
end
end
players_controller.rb
class PlayersController < ApplicationController
before_action :set_player, only: [:edit, :update, :show, :destroy]
before_action :require_user, except: [:index, :show]
before_action :require_same_user, only: [:edit, :update, :destroy]
before_filter :process_player_games_attrs, only: [:create, :update]
def process_player_games_attrs
params[:player][:player_games_attributes].values.each do |game_attr|
game_attr[:_destroy] = true if game_attr[:enable] != '1'
end
end
.......
private
# Use callbacks to share common setup or constraints between actions.
def set_player
#player = Player.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def player_params
params.require(:player).permit(:id, :firstname, :lastname, player_games_attributes: [:id, :game_id, :score, :time, :enable, :_destroy] )
end
def require_same_user
if current_user != #player.user and !current_user.admin?
flash[:danger] = "You can edit or delete only your own player"
redirect_to root_path
end
end
end
_form of 'PLAYER'
<%= form_for(#player, :html => {class: "az-form", role: "form"}) do |player_form| %>
<%= player_form.label :firstname, class: "az-form__label" %> <br/>
<%= player_form.text_field :firstname, class: "az-form__input", placeholder: "Firstname of player", autofocus: true %>
<%= player_form.label :lastname, class: "az-form__label" %> </br>
<%= player_form.text_field :lastname, class: "az-form__input", placeholder: "Lastname of player" %>
<%= player_form.fields_for :player_games, #player.initialized_player_games do |builder| %>
<% #game = builder.object.game %>
<%= render 'result_fields', f: builder %>
<div class="links">
<%= link_to_add_association 'add result', player_form, :player_games, :partial => 'players/result_fields' %>
</div>
<hr>
<% end %>
<div class="text-center">
<%= button_tag(type: "submit", class: "az-form__submit") do %>
<%= player_form.object.new_record? ? "Create player" : "Update player" %>
<% end %>
</div>
<% end %>
_result_fields.html.erb
<div class="nested-fields">
<%= f.hidden_field :game_id, :value => #game.id%>
<div class="row">
<div class="col-md-12">
<label class="az-form__label az-form__label--unable js-az-form__checkbox" data-check="<%= #game.id %>">
<%= f.check_box :enable %>
<%= #game.title %>
</label>
</div>
</div>
<div class="row">
<div class="col-md-6">
<%= f.label :score,
class: "az-form__label", :data => {:check => #game.id } %> </br>
<%= f.number_field :score, step: :any, :data => {:check => #game.id },
class: "az-form__input az-form__input--disabled",
placeholder: "Score for '#{#game.title}'", disabled: true %>
</div>
<div class="col-md-6">
<%= f.label :time,
class: "az-form__label", :data => {:check => #game.id } %> </br>
<%= f.number_field :time, step: :any, :data => {:check => #game.id },
class: "az-form__input az-form__input--disabled",
placeholder: "Time for '#{#game.title}'", disabled: true %>
</div>
</div>
<div class="row">
<div class="col-md-12">
<%= link_to_remove_association "remove result", f %>
</div>
</div>
</div>
Question:
When edit 'player' have access to only one result per game, can not change others, conflict with 'initialized_player_games' method, but if i remove this method from form work well, but cannot create another game if dont create in new action, how can i change this method properly?
If I understand correctly, you want to add an entry for all games (which works), but adding a new Result does not. The problem is the #game which is probable either undefined or set to the last game.
I also do not really like the approach (giving the fields_for a specific set). Instead I would adapt the approach slightly. Instead of using initialized_player_games I would use a method, to be called in the controller, e.g. add_default_player_games, something like
def add_default_player_games
Game.all.each do |game|
if g = player_games.find { |g| g.game_id == game.id }
g.enable ||= true
else
player_games.build(game: game)
end
end
end
effectively adding new instances to the collection, without saving them.
So in your controller you would write
#player = Player.new
#player.add_default_player_games
or in edit
#player = Player.find(params[:id])
#player.add_default_player_games
Your view would then just iterate over player_games
<%= player_form.fields_for :player_games do |builder| %>
<%= render 'result_fields', f: builder %>
<% end %>
And then, if you would use simple-form it would be very easy to select a game if not yet selected, and there is no need for the ugly #game.
So do something like in _result_fields (in haml because I am a lazy typist)
.nested-fields
- game_id = f.object.game_id
- if game_id.present?
= f.hidden_field :game_id
- else
= f.collection_select :game_id, Game.all, :id, :name
...
So in short: if there is a game_id, do not allow to change it (would be useful to show the title of the game or something), but if not use a dropdown-select to choose the game.
And the rest stays the same (only use game_id instead of #game.id).

Save successful on nested attributes on has many through but doesn't add to database

I and order and item system with Rails 4 with a has many through association. When I select create order the webpage says that the order was created successfully however the link is not made in the OrderItems linking tables meaning that the items relating to that order do not appear on the show page or edit page for an order.
The order is also linked to an employee. The current employee ID is linked to that order. I have just not been able to figure out how to add each item to the database.
p.s. I am using a gem called nested_form to handle all of the jQuery on the front end of dynamically adding and removing new items on the _form.html.erb for Orders.
orders_controller.rb
class OrdersController < ApplicationController
before_action :logged_in_employee, only:[:new, :show, :create, :edit, :update, :index]
before_action :admin_employee, only:[:destroy]
before_action :set_order, only: [:show, :edit, :update, :destroy]
def new
#order = Order.new
#items = Item.all
end
def edit
#items = Item.all
end
def create
#order = current_employee.orders.build(order_params)
if #order.save
flash[:success] = "Order successfully created"
redirect_to #order
else
render 'new'
end
end
def update
if #order.update_attributes(order_params)
flash[:success] = "Order updated!"
redirect_to current_employee
else
render 'edit'
end
end
....
private
def set_order
#order = Order.find(params[:id])
end
def order_params
params.require(:order).permit(:table_number, :number_of_customers, :status, :comment, order_items_attributes: [:id, :order_id, :item_id, :_destroy])
end
end
order.rb
class Order < ActiveRecord::Base
belongs_to :employee
has_many :order_items
has_many :items, :through => :order_items
default_scope { order('status DESC') }
validates :employee_id, presence: true
validates :table_number, numericality: { only_integer: true, greater_than: 0, less_than_or_equal_to: 20 }, presence: true
validates :number_of_customers, numericality: { only_integer: true, greater_than: 0, less_than_or_equal_to: 50 }, presence: true
validates :status, inclusion: { in: %w(Waiting Ready Closed), message: "%{value} is not a status" }
accepts_nested_attributes_for :order_items, :reject_if => lambda { |a| a[:item_id].blank? }
end
item.rb
class Item < ActiveRecord::Base
belongs_to :menu
has_many :order_items
has_many :orders, :through => :order_items
before_save { name.capitalize! }
VALID_PRICE_REGEX = /\A\d+(?:\.\d{0,2})?\z/
validates :name, presence: true, length: { maximum: 100 }
validates :price, format: { with: VALID_PRICE_REGEX }, numericality: { greater_than: 0, less_than_or_equal_to: 100}, presence: true
validates :course, inclusion: { in: %w(Starter Main Dessert Drink), message: "%{value} is not a course" }
validates :menu_id, presence: true
end
order_item.rb
class OrderItem < ActiveRecord::Base
belongs_to :item
belongs_to :order
validates :order_id, presence: true
validates :item_id, presence: true
end
orders/_form.html.erb
<% provide(:title, "#{header(#order)} #{#order.new_record? ? "order" : #order.id}") %>
<%= link_to "<< Back", :back, data: { confirm: back_message } %>
<h1><%= header(#order) %> <%= #order.new_record? ? "order" : #order.id %></h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= nested_form_for #order do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="row">
<div class="col-xs-6">
<%= f.label :table_number %>
<%= f.number_field :table_number, class: 'form-control' %>
</div>
<div class="col-xs-6">
<%= f.label :number_of_customers %>
<%= f.number_field :number_of_customers, class: 'form-control' %>
</div>
</div>
<%= f.fields_for :order_items do |oi| %>
<%= oi.grouped_collection_select :item_id, Menu.where(active: true).order(:name), :items, :name, :id, :name, { include_blank: 'Select Item' }, class: 'items_dropdown' %>
<%= oi.hidden_field :item_id %>
<%= oi.hidden_field :order_id %>
<%= oi.link_to_remove "Remove item" %>
<% end %>
<p><%= f.link_to_add "Add an item", :order_items %></p>
<br>
<% if !#order.new_record? %>
<%= f.label "Status" %>
<%= f.select(:status, options_for_select([['Waiting', 'Waiting'], ['Ready', 'Ready'], ['Closed', 'Closed']], #order.status), class: 'form-control') %>
<% end %>
<%= f.label "Comments - how would you like your steak cooked? Or feedback" %>
<%= f.text_area :comment, size: "20x5", class: 'form-control' %>
<%= f.submit submit_label(#order), class: "btn btn-success col-md-6" %>
<% end %>
<% if !#order.new_record? && current_employee.try(:admin?) %>
<%= link_to "Cancel", :back, data: { confirm: back_message }, class: "btn btn-warning col-md-offset-1 col-md-2" %>
<%= link_to "Delete", #order, method: :delete, data: { confirm: "Are you sure? The employee will be deleted! "}, class: "btn btn-danger col-md-offset-1 col-md-2" %>
<% else %>
<%= link_to "Cancel", :back, data: { confirm: back_message }, class: "btn btn-warning col-md-offset-1 col-md-5" %>
<% end %>
</div>
</div>
Issue: Let's say your order has got many items and you want the items to be saved just when the order is created.
order = Order.new(order_params)
item = Item.new(name: 'item-name', product_info: 'etc etc')
if order.save
item.create
order.items << item
end
You need to follow similar approach in your case. just get the item_params properly and apply above rule.
Try below approach, hope that helps. :)
def create
#order = current_employee.orders.build(order_params)
if #order.save
item = params["order"]["order_items_attributes"]
#please debug above one and try to get your items from params.
order_item = Item.create(item)
#makes sure above item hold all item attributes then create them first
#order.items << item
redirect_to #order
end
end

rails 3.2 ice_cube and recurring_select

I'm trying to save a recurring_select to a serialized attribute to handle recurring events on a rails app.
Using Jayson's post I manage to get the schedule saved but now I can't get the saved
attribute shown in index view or update the recurring_select in _form view
This is my model
class Todo < ActiveRecord::Base
attr_accessible :item, :completed, :schedule, :start_date
after_initialize :default_values
validates :item, presence: true
belongs_to :list
belongs_to :tasklib,
:foreign_key=>"item"
#recuring model
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :schedule
serialize :schedule, Hash
def schedule=(new_schedule)
write_attribute(:schedule,RecurringSelect.dirty_hash_to_rule(new_schedule).to_hash)
end
def converted_schedule
the_schedule = Schedule.new(self.start_date)
the_schedule.add_recurrence_rule(RecurringSelect.dirty_hash_to_rule(self.schedule))
the_schedule
end
end
This is my index view :
h1><%= #list.name %></h1>
<table class="table table-striped">
<thead>
<tr>
<th><%= model_class.human_attribute_name(:item) %></th>
<th><%= model_class.human_attribute_name(:start_date) %></th>
<th><%= model_class.human_attribute_name(:schedule) %></th>
<th><%=t '.actions', :default => t("helpers.actions") %></th>
</tr>
</thead>
<tbody>
<% #list.todos.each do |todo| %>
<tr>
<td><%= todo.item %></td>
<td><%= todo.start_date %></td>
<td><%= todo.schedule %></td>
<td>
<%= link_to t('.edit', :default => t("helpers.links.edit")),
edit_list_todo_path(#list, todo), :class => 'btn btn-mini' %>
<%= link_to t('.destroy', :default => t("helpers.links.destroy")),
list_todo_path(#list, todo),
:method => :delete,
:confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')),
:class => 'btn btn-mini btn-danger' %>
</td>
</tr>
<% end %>
</tbody>
</table>
and this is my _form view:
<%= simple_form_for [#list, if #todo.nil? then #list.todos.build else #todo end], :html => { :class => 'form-horizontal' } do |f| %>
<%-# f.input :item, input_html: {class: "span6", rows: 3} -%>
<%= f.collection_select :item, Tasklib.order(:name),:name,:name, include_blank: true %>
<%= f.label :start_date, "date" %>
<%= f.input :start_date %>
<%= f.label :schedule %>
<%= f.select_recurring :schedule, nil, :allow_blank => true %>
<div class="form-actions">
<%= f.submit 'Save', :class => 'btn btn-primary' %>
<%= link_to t('.cancel', :default => t("helpers.links.cancel")),
lists_path, :class => 'btn' %>
</div>
<% end %>
OK I found it !
The model should be :
def schedule=(new_schedule)
if new_schedule == nil
new_schedule = IceCube::Schedule.new( self.start_date )
end
write_attribute(:schedule, RecurringSelect.dirty_hash_to_rule(new_schedule).to_hash)
end
def converted_schedule
if !self.read_attribute(:schedule).empty?
the_schedule = IceCube::Schedule.new( self.start_date )
the_rule = RecurringSelect.dirty_hash_to_rule( self.read_attribute(:schedule) )
if RecurringSelect.is_valid_rule?(the_rule)
the_schedule.add_recurrence_rule( the_rule)
end
the_schedule
end
end
and the form view should only set a new recurring select like:
<%= f.select_recurring :schedule, [ ["No recurring", "{}"] ], :allow_blank => true %>

param is missing or the value is empty error while rendering edit form in Rails4.x

Gurus,
I am making a simple edit form and am getting this error in Rails 4.x. The interesting thing is that the edit.html.erb is basically a copy of new.html.erb for the most part and the new.html.erb is working just fine.
The error is:
param is missing or the value is empty: teacher
def teacher_params
params.require(:teacher).permit(:firstname,
:lastname,
:email,
:cellphone,
The application trace is:
app/controllers/teacher_controller.rb:39:in `teacher_params'
app/controllers/teacher_controller.rb:23:in `edit'
The edit form is:
<h1>Add A teacher</h1>
<%= form_for #teacher, :url => { :action => 'edit'}, :id => #teacher.id, :html => { :multipart => true } do |f| %>
<table summary="teacher form fields">
<tr>
<th>First Name*</th>
<td><%= f.text_field :firstname %></td>
</tr>
<tr>
<th>Last Name*</th>
<td><%= f.text_field :lastname %></td>
</tr>
<tr>
<th>Email*</th>
<td><%= f.email_field :email %></td>
</tr>
<tr>
<th>Cellphone</th>
<td><%= f.telephone_field :cellphone %></td>
</tr>
<tr>
<th>Username*</th>
<td><%= f.text_field :username %></td>
</tr>
<tr>
<tr>
<th>Password*</th>
<td><%= f.password_field :password %></td>
</tr>
<tr>
<th>Confirm Password*</th>
<td><%= f.password_field :password_confirmation %></td>
</tr>
<tr>
<th>Address Street#</th>
<td><%= f.text_field :addr_streetno %></td>
<th>Apt #</th>
<td><%= f.number_field :addr_aptno %></td>
</tr>
<tr>
<th>City</th>
<td><%= f.text_field :addr_city %></td>
<th>State</th>
<td><%= f.text_field :addr_state %></td>
<th>Zip</th>
<td><%= f.number_field :addr_zip %></td>
</tr>
<tr>
<th>Photo</th>
<td><%= f.file_field :photo %></td>
</tr>
</table>
<%= f.submit 'Update teacher' %>
<% end %>
<% if #teacher.errors.any? %>
<ul class="Signup_Errors">
<% for message_error in #teacher.errors.full_messages %>
<li>* <%= message_error %></li>
<% end %>
</ul>
<% end %>
</div>
The model is:
class Teacher < ActiveRecord::Base
has_many :students
has_secure_password
EMAIL_REGEX = /\A[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\z/i
CELLPHONE_REGEX = /\A([0-9]( |-)?)?(\(?[0-9]{3}\)?|[0-9]{3})( |-)?([0-9]{3}( |-)?[0-9]{4}|[a-zA-Z0-9]{7})\z/i
validates :firstname, :presence => true
validates :lastname, :presence => true
validates :username, :presence => true, :uniqueness => true, :length => { :in => 3..20 }
validates :email, :presence => true, :uniqueness => true, :format => EMAIL_REGEX
validates :cellphone, :presence => true, :format => CELLPHONE_REGEX
validates :addr_aptno, numericality: { only_integer: true, :greater_than => 0 }
validates :addr_zip, numericality: { only_integer: true, :greater_than => 0 }
end
Here is the teacher controller:
class TeacherController < ApplicationController
def index
#teachers= Teacher.all
end
def new
#teacher = Teacher.new
end
def create
#teacher = Teacher.new(teacher_params)
if #teacher.save
flash[:notice] = "Teacher created."
redirect_to :action => 'index'
else
render :action => 'new'
end
end
def edit
#teacher = Teacher.find(params[:id])
# Update the object
if #teacher.update_attributes(teacher_params)
# If update succeeds, redirect to the list action
flash[:notice] = "Teacher updated."
redirect_to :action => 'index'
else
# If save fails, redisplay the form so user can fix problems
render :action => 'edit'
end
end
def show
end
private
def teacher_params
params.require(:teacher).permit(:firstname,
:lastname,
:email,
:cellphone,
:username,
:password,
:password_confirmation,
:addr_streetno,
:addr_aptno,
:addr_city,
:addr_state,
:addr_zip,
:photo)
end
end
Here is the migration file:
create_table :teachers do |t|
t.string :firstname, null: false
t.string :lastname, null: false
t.string :email, null: false
t.string :cellphone
t.string :username, null: false
t.string :password_digest, null: false
t.string :addr_streetno
t.integer :addr_aptno
t.string :addr_city
t.string :addr_state
t.integer :addr_zip
t.binary :photo, :limit => 0.5.megabyte
t.timestamps
end
You have to split the edit action into 2 actions since edit is default just a get request:
def edit
#teacher = Teacher.find(params[:id])
respond_to { |format| format.html }
end
def update
#teacher = Teacher.find(params[:id])
if #teacher.update_attributes(teacher_params)
...
end
Also remove :url => { :action => 'edit'} from your form, since it's routing to the update action. This happens automatically if you use <%= form_for #teacher do |f|%>
Essentially, I had to split the edit action in the controller into udpate and edit. I saw a working example on lynda.com and followed it. Also other people said the same thing. Now its working.
I had to add the :patch keywords in routes.rb
match ':controller(/:action/(:id))', :via => [:get, :post, :patch]
The edit form was a replica of the new form except that the action was update instead of create and that the id had to be passed.
<%= form_for #teacher, :url => { :action => 'update', :id => #teacher.id}, :html => { :multipart => true } do |f| %>
This was the new code in the controller:
def edit
#teacher = Teacher.find(params[:id])
end
def update
#teacher = Teacher.find(params[:id])
# Update the object
if #teacher.update_attributes(teacher_params)
# If update succeeds, redirect to the list action
flash[:notice] = "Teacher updated."
redirect_to :action => 'show', :id => #teacher.id
else
# If update fails, redisplay the form so user can fix problems
render :action => 'edit'
end
end

Resources