Practical examples to master web forms in Rails
This is an answer for the reddit post: https://www.reddit.com/r/rails/comments/1ly7e3m/struggles_with_nested_associations/ The main thing to consider is to deal with cells directly and not columns nor records (rows). Another simpler way is to probably delete columns and records and store the reference on the cell instead. Especially true if the tables do not hold any other values.
Disclaimer: Always double check the code you're copying is safe.
Check out our About section to learn more about UniRails gem
.
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.0"
gem "sqlite3", "~> 1.7"
gem "byebug"
gem "puma"
end
require "uni_rails"
require "sqlite3"
require "byebug"
require 'rack/handler/puma'
UniRails.rackup_handler = Rack::Handler::Puma
# ==== ROUTES ====
UniRails::App.routes.append do
root "tables#new"
resources :tables
end
# ==== DB SCHEMA ====
ActiveRecord::Base.establish_connection
ActiveRecord::Schema.define do
create_table :tables, force: :cascade do |t|
t.string :name, null: false
end
create_table :cells, force: :cascade do |t|
t.belongs_to :table
t.belongs_to :column
t.belongs_to :record
t.string :content
end
create_table :columns, force: :cascade do |t|
t.belongs_to :table
t.integer :reference
end
create_table :records, force: :cascade do |t|
t.belongs_to :table
t.integer :reference
end
end
# ==== MODELS ====
class Table < ActiveRecord::Base
validates :name, presence: true
has_many :columns
has_many :records
has_many :cells, autosave: true
accepts_nested_attributes_for :cells
def cells_attributes=(params)
records.load
columns.load
params.each do |i, attr|
column = columns.find { |c| c.reference == attr['column_reference'].to_i }
unless column
column = columns.build(table: self, reference: attr['column_reference'])
end
record = records.find { |c| c.reference == attr['record_reference'].to_i }
unless record
record = records.build(table: self, reference: attr['record_reference'])
end
super(attr.merge(column: column, record: record))
end
end
end
class Column < ActiveRecord::Base
belongs_to :table
has_many :cells
end
class Record < ActiveRecord::Base
belongs_to :table
has_many :cells
end
class Cell < ActiveRecord::Base
belongs_to :table
belongs_to :column
belongs_to :record
attribute :column_reference
attribute :record_reference
def record_reference
record_id || super
end
def column_reference
column_id || super
end
end
# ==== SEEDS ====
# ==== CONTROLLERS ====
class TablesController < ActionController::Base
layout 'application'
def new
@table = Table.new(name: "Hello")
3.times do |i|
@table.columns.build(reference: i)
3.times do |j|
@table.records.build(reference: j)
@table.cells.build(column_reference: i, record_reference: j)
end
end
end
def create
@table = Table.new(table_params)
if @table.save
redirect_to table_path(@table)
else
render :new
end
end
def show
@table = Table.find(params[:id])
end
def edit
@table = Table.find(params[:id])
end
def update
@table = Table.find(params[:id])
if @table.update(table_params)
redirect_to table_path(@table)
else
render :edit
end
end
private
def table_params
params.require(:table).permit(:name,
cells_attributes: [:id, :column_reference, :record_reference, :content]
)
end
end
# ==== IMPORT MAPS ====
# ==== JAVASCRTIP ====
# ==== CSS ====
UniRails.css <<~CSS
html { background-color:#EEE; }
body { width:500px; height:700px; margin:auto;
background-color:white; padding:1rem;
}
form {
label { display: block; }
input[type="submit"] { display: block; margin-top:1rem; }
.field_with_errors { color:red; display:inline;
.without-label { background-color: rgba(255, 0, 0, 0.5); }
}
}
CSS
# ==== VIEWS ====
UniRails.register_view "tables/_form.html.erb", <<~'HTML'
<%= form_with model: table do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<fieldset>
<legend>Table</legend>
<table>
<thead>
<tr>
<th>1</th>
<th>2</th>
<th>3</th>
</tr>
</thead>
<tbody>
<% table.cells.group_by(&:record_reference).each do |i, cells| %>
<tr>
<% cells.group_by(&:column_reference).each do |j, records| %>
<%= f.fields_for :cells, records.first do |ff| %>
<td>
<%= ff.hidden_field :record_reference, value: i %>
<%= ff.hidden_field :column_reference, value: j %>
<%= ff.hidden_field :id %>
<%= ff.text_field :content %>
</td>
<% end %>
<% end %>
</tr>
<% end %>
</tbody>
</table>
</fieldset>
<%= f.submit %>
<% end %>
HTML
UniRails.register_view "tables/new.html.erb", <<~'HTML'
<h1>New Table</h1>
<%= render partial: 'form', locals: { table: @table } %>
HTML
UniRails.register_view "tables/edit.html.erb", <<~'HTML'
<h1>Edit Table</h1>
<%= render partial: 'form', locals: { table: @table } %>
HTML
UniRails.register_view "tables/show.html.erb", <<~'HTML'
<h1><%= @table.name.capitalize %></h1>
<p><%= link_to 'Edit', edit_table_path(@table) %></p>
<h2>Cells</h2>
<ul>
<% @table.cells.find_each do |cell| %>
<li><%= "column id: #{cell.column.id} - record id: #{cell.record.id} - content: #{cell.content}" %></li>
<% end %>
</ul>
HTML
UniRails.run(Port: 3000)