Practical examples to master web forms in Rails
This example shows how to use a single form and save multiple records simultaneously. Here, each record is validated independently before being saved in a transaction. Note that we wrap the independent models in a higher ActiveModel::Model concept called DailyRecord which uses all the synergy of rails default MVC pattern.
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 "byebug"
gem "sqlite3", "~> 1.7"
end
require "uni_rails"
require "byebug"
require "sqlite3"
# ==== ROUTES ====
UniRails::App.routes.append do
root "daily_records#index"
resources :daily_records, only: [:index, :new, :create]
end
# ==== DB SCHEMA ====
ActiveRecord::Base.establish_connection
ActiveRecord::Schema.define do
create_table :spendings, force: :cascade do |t|
t.string :amount
end
create_table :water_consumptions, force: :cascade do |t|
t.string :amount
end
end
# ==== MODELS ====
class DailyRecord
include ActiveModel::Model
attr_accessor :water_consumption, :spending
def initialize(params = {})
super(params)
@spending ||= Spending.new
@water_consumption ||= WaterConsumption.new
end
def spending_attributes=(attributes)
@spending = Spending.new(attributes)
end
def water_consumption_attributes=(attributes)
@water_consumption = WaterConsumption.new(attributes)
end
def valid?
records.each(&:valid?)
records.map(&:errors).all?(&:empty?) && super
end
def save
return false unless valid?
ActiveRecord::Base.transaction { records.each(&:save!) }
end
def records
[spending, water_consumption]
end
end
class Spending < ActiveRecord::Base
validates :amount, presence: true, numericality: true
end
class WaterConsumption < ActiveRecord::Base
validates :amount, presence: true, numericality: true
end
# ==== CONTROLLERS ====
class DailyRecordsController < ActionController::Base
layout 'application'
def index
@spendings = Spending.all
@water_consumptions = WaterConsumption.all
end
def new
@daily_record = DailyRecord.new
end
def create
@daily_record = DailyRecord.new(daily_record_params)
if @daily_record.save
redirect_to daily_records_path, notice: 'successfully created'
else
render :new
end
end
private
def daily_record_params
params.require(:daily_record).permit(
spending_attributes: [:amount],
water_consumption_attributes: [:amount],
)
end
end
# ==== CSS ====
UniRails.css <<~CSS
html { background-color:#EEE; }
body { width:500px; height:700px; margin:auto;
background-color:white; padding:1rem;
}
.errors { color: red; }
form {
label { display: block; }
input[type="submit"] { display: block; margin-top:1rem; }
.field_with_errors { color:red; display:inline; }
}
CSS
# ==== VIEWS ====
UniRails.register_view "daily_records/index.html.erb", <<~HTML
<h1>DailyRecords</h1>
<%= link_to 'Create a new daily record', new_daily_record_path %>
<h2>Spending</h2>
<ul id="spendings">
<% @spendings.each do |record| %>
<li>ID: <%= record.id %> - <%= number_to_currency record.amount %></li>
<% end%>
</ul>
<h2>Water Consumption</h2>
<ul id="water-consumptions">
<% @water_consumptions.each do |record| %>
<li>ID: <%= record.id %> - <%= number_with_delimiter record.amount %> litres</li>
<% end%>
</ul>
HTML
UniRails.register_view "daily_records/new.html.erb", <<~HTML
<h1>New Daily Records</h1>
<%= form_with(model: @daily_record) do |f| %>
<div id="spending-form">
<p><strong>Spending</strong></p>
<p class="errors"><%= @daily_record.spending.errors.full_messages.to_sentence %></p>
<%= f.fields_for :spending do |ff| %>
<%= ff.label :amount, value: 'in dollars' %>
<%= ff.text_field :amount %>
<% end %>
</div>
<div id="water-consumption-form">
<p><strong>Water Consumption</strong></p>
<p class="errors"><%= @daily_record.water_consumption.errors.full_messages.to_sentence %></p>
<%= f.fields_for :water_consumption do |ff| %>
<%= ff.label :amount, value: 'in litres' %>
<%= ff.text_field :amount %>
<% end %>
</div>
<%= f.submit %>
<% end %>
HTML
UniRails.run(Port: 3000)