Railsamples

Practical examples to master web forms in Rails

Table with double nesting

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)