CRUD
This section illustrates how to build a full CRUD interface using Lato features.
Model
class Product < ApplicationRecord enum :status, { created: 0, in_progress: 1, completed: 2, cancelled: 3 }, suffix: true attr_reader :actions # Relations ## belongs_to :lato_user, class_name: 'Lato::User' belongs_to :product_parent, class_name: 'Product', optional: true has_many :product_children, class_name: 'Product', foreign_key: :product_parent_id # Scopes ## scope :lato_index_order, ->(column, order) do return joins(:lato_user).order("lato_users.last_name #{order}, lato_users.first_name #{order}") if column == :lato_user_id order("#{column} #{order}") end scope :lato_index_search, ->(search) do joins(:lato_user).where("lower(code) LIKE :search OR lower(lato_users.first_name) LIKE :search OR lower(lato_users.last_name) LIKE :search", search: "%#{search.downcase.strip}%") end # Hooks ## before_create do self.status ||= :created end # Helpers ## def lifetime Time.now - created_at end def status_color return 'warning' if created_status? return 'primary' if in_progress_status? return 'success' if completed_status? return 'danger' if cancelled_status? 'secondary' end end
Controller
class ProductsController < ApplicationController before_action :authenticate_session before_action { active_sidebar(:products) } def index columns = %i[id code status product_parent_id lato_user_id custom_dynamic_column created_at actions] sortable_columns = %i[id code status lato_user_id] searchable_columns = %i[id code lato_user_id] @products = lato_index_collection( Product.all.includes(:lato_user), columns: columns, sortable_columns: sortable_columns, searchable_columns: searchable_columns, default_sort_by: 'code|ASC', pagination: 10, ) end # NOTE: This endpoint is used for inputs with autocomplete feature to select a product using search. def index_autocomplete unless params[:value].blank? @product = Product.find_by(id: params[:value]) return render json: @product ? { label: @product.code, value: @product.id } : nil end @products = Product.where('code LIKE ?', "#{params[:q]}%").limit(10) render json: @products.map { |product| { label: product.code, value: product.id } } end def create @product = Product.new end def create_action @product = Product.new(product_params.merge(lato_user_id: @session.user_id)) respond_to do |format| if @product.save format.html { redirect_to main_app.products_path, notice: 'Product created successfully' } format.json { render json: @product } else format.html { render :create, status: :unprocessable_entity } format.json { render json: @product.errors, status: :unprocessable_entity } end end end def update @product = Product.find(params[:id]) end def update_action @product = Product.find(params[:id]) respond_to do |format| if @product.update(product_params) format.html { redirect_to main_app.products_path, notice: 'Product updated successfully' } format.json { render json: @product } else format.html { render :update, status: :unprocessable_entity } format.json { render json: @product.errors, status: :unprocessable_entity } end end end def export_action @operation = Lato::Operation.generate('ExportProductsJob', {}, @session.user_id) respond_to do |format| if @operation.start format.html { redirect_to lato.operation_path(@operation) } format.json { render json: @operation } else format.html { render :index, status: :unprocessable_entity } format.json { render json: @operation.errors, status: :unprocessable_entity } end end end private def product_params params.require(:product).permit(:code, :product_parent_id) end end
Views
Index
<%= lato_page_head 'Products', [ { label: 'Products' } ] %> <%= lato_index @products, custom_actions: { create: { path: products_create_path, icon: 'bi bi-plus', title: 'Create product', data: { controller: 'lato-tooltip', lato_action_target: 'trigger', turbo_frame: dom_id(Product.new, 'form'), action_title: 'Create product' } }, export: { path: products_export_action_path, icon: 'bi bi-download', title: 'Export products', data: { controller: 'lato-tooltip', lato_action_target: 'trigger', turbo_method: :post, turbo_frame: 'lato_operation' } }, }, dropdown_actions: { icon: 'bi bi-three-dots', title: 'More actions', data: { controller: 'lato-tooltip', }, actions: [ { path: '#', icon: 'bi bi-trash', title: 'Example 1', data: { custom: 'value', } }, { path: '#', icon: 'bi bi-download', title: 'Example 2', data: { custom: 'value', } } ] }, pagination_options: [10,20,50,100] %>
Create
<%= lato_page_head 'New product', [ { label: 'Products', path: products_path }, { label: 'New product' } ] %> <div class="card mb-4"> <div class="card-header"> <h2 class="fs-4 mb-0">New product registration</h2> </div> <div class="card-body"> <%= render 'products/form', product: @product %> </div> </div>
Update
<%= lato_page_head 'Edit product', [ { label: 'Products', path: products_path }, { label: 'Edit product' } ] %> <div class="card mb-4"> <div class="card-header"> <h2 class="fs-4 mb-0">Edit product</h2> </div> <div class="card-body"> <%= render 'products/form', product: @product %> </div> </div>
Partial Form
<% product ||= Product.new %> <%= turbo_frame_tag dom_id(product, 'form') do %> <%= form_with model: product, url: product.persisted? ? products_update_action_path(product) : products_create_action_path, data: { turbo_frame: '_self', controller: 'lato-form' } do |form| %> <%= lato_form_notices class: %w[mb-3] %> <%= lato_form_errors product, class: %w[mb-3] %> <div class="mb-3"> <%= lato_form_item_label form, :code %> <%= lato_form_item_input_text form, :code, required: true, data: { controller: 'lato-input-autocomplete', lato_input_autocomplete_path_value: products_autocomplete_path } %> </div> <div class="mb-3"> <%= lato_form_item_label form, :product_parent_id %> <%= lato_form_item_input_text form, :product_parent_id, required: true, data: { controller: 'lato-input-autocomplete2', lato_input_autocomplete2_path_value: products_autocomplete_path } %> </div> <div class="d-flex justify-content-end"> <%= lato_form_submit form, product.persisted? ? 'Update' : 'Confirm', class: %w[btn-success] %> </div> <% end %> <% end %>
Helpers
module ProductsHelper def product_status(product) lato_data_badge(Product.human_attribute_name("status.#{product.status}"), product.status_color) end def product_created_at(product) product.created_at.strftime('%d/%m/%Y') end def product_lato_user_id(product) lato_data_user(product.lato_user.full_name, product.lato_user.gravatar_image_url(50)) end def product_product_parent_id(product) return ' - ' if product.product_parent.nil? product.product_parent.code end def product_lifetime(product) Time.at(product.lifetime).utc.strftime('%H h %M m') end def product_actions(product) content_tag(:div, class: 'btn-group btn-group-sm') do concat link_to('Edit', products_update_path(product), class: 'btn btn-primary', data: { lato_action_target: 'trigger', turbo_frame: dom_id(product, 'form'), action_title: 'Edit product' }) concat link_to('Delete', '#', class: 'btn btn-danger', data: { turbo_method: 'POST', turbo_confirm: 'Are you sure to continue?' }) end end end