Railsamples

Practical examples to master web forms in Rails

Nested Forms - has one association

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)