Practical examples to master web forms in Rails
In this example, we show how to use accepts_nested_attributes_for on a has_one association between a User and its Profile. Note that the profile ID does not change once it's created by using the update_only option
ENV["SECRET_KEY_BASE"] = "1212312313"
ENV["DATABASE_URL"] = "sqlite3:///#{__dir__}/database.sqlite"
require "bundler/inline"
gemfile do
source "https://www.rubygems.org"
gem "uni_rails", "0.4.1"
gem "sqlite3", "~> 1.7"
gem "byebug"
end
require "uni_rails"
require "sqlite3"
require "byebug"
# ==== ROUTES ====
UniRails::App.routes.append do
root "users#index"
resources :users
end
# ==== DB SCHEMA ====
ActiveRecord::Base.establish_connection
ActiveRecord::Schema.define do
create_table :users, force: :cascade do |t|
t.string :email, null: false
end
create_table :profiles, force: :cascade do |t|
t.string :name, null: false
t.datetime :dob
t.references :user
end
end
# ==== MODELS ====
class Profile < ActiveRecord::Base
belongs_to :user
validates :name, presence: true
end
class User < ActiveRecord::Base
has_one :profile
accepts_nested_attributes_for :profile, update_only: true
validates :email, presence: true
delegate :id, :name, :dob, to: :profile, prefix: true
after_initialize { self.profile ||= Profile.new }
end
# ==== CONTROLLERS ====
class UsersController < ActionController::Base
layout 'application'
def index
@users = User.all
end
def new
@user = User.new
end
def create
if (@user = User.new(user_params)).save
redirect_to users_path
else
render :new
end
end
def edit
@user = User.find(params[:id])
end
def update
if (@user = User.find(params[:id])).update(user_params)
redirect_to users_path
else
render :edit
end
end
def destroy
User.find(params[:id]).destroy
redirect_to users_path
end
private
def user_params
params.require(:user).permit(:email, profile_attributes: [:name, :dob])
end
end
# ==== CSS ====
UniRails.css <<~CSS
html { background-color:#EEE; }
body { width:500px; height:700px; margin:auto;
background-color:white; padding:1rem;
}
form {
.errors { color:red; }
label { display: block; }
input[type="submit"] { display: block; margin-top:1rem; }
.field_with_errors { color:red; display:inline; }
}
article { margin-top:1rem; border: 1px solid black; padding:0.5rem;
p { margin:0 }
.actions { display:flex; }
}
CSS
# ==== VIEWS ====
UniRails.register_view "users/index.html.erb", <<~HTML
<h1>Users</h1>
<p><%= link_to 'Add new user', new_user_path %></p>
<%= render(@users) || raw("<p>There are no users</p>") %>
HTML
UniRails.register_view "users/new.html.erb", <<~HTML
<h1>Edit <%= @user.profile_name %></h1>
<%= render partial: 'form', locals: { user: @user } %>
HTML
UniRails.register_view "users/edit.html.erb", <<~HTML
<h1>New User</h1>
<%= render partial: 'form', locals: { user: @user } %>
HTML
UniRails.register_view "users/_user.html.erb", <<~HTML
<article>
<p><strong>ID</strong>: <%= user.id %></p>
<p><strong>Email</strong>: <%= user.email %></p>
<p><strong>Profile ID</strong>: <%= user.profile_id %></p>
<p><strong>Profile Name</strong>: <%= user.profile_name %></p>
<p><strong>Profile BDO</strong>: <%= user.profile_dob %></p>
<p class="actions">
Actions:
<%= link_to 'Edit', edit_user_path(user) %>
<%= button_to 'Destroy', user, method: :delete %>
</p>
</article>
HTML
UniRails.register_view "users/_form.html.erb", <<~HTML
<%= form_with model: user do |f| %>
<p class="errors"><%= user.errors.full_messages.to_sentence %></p>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.fields_for :profile do |ff| %>
<%= ff.label :name %>
<%= ff.text_field :name %>
<%= ff.label :dob %>
<%= ff.date_field :dob %>
<% end %>
<%= f.submit %>
<% end %>
HTML
UniRails.run(Port: 3000)