Railsamples

Practical examples to master web forms in Rails

Other - Two Unrelated Models In One Form

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)