Saving a list of names to a reusable template

I am working on a Staffing app for managers at Fedex. Currently you can choose a date and then fill that date with employees’ records which include the attributes “Name, Employee Number, and Comments”. The employee record is an association to (belongs to) StaffDate and Template.

What I would like to do is be able to save the employee records that are currently on the screen to a template, name that template. Then after choosing a new date you have the option of picking a saved list (template) of employees to populate the list and then be edited if needed. Just so the manager’s dont have to type in 40+ names night after night.

Below are the controllers, model, and routes for my StaffDate, Employee, and Template. Also worth mentioning is that the Template record as an attribute called Employees which is an array. Whereas the StaffDate, which holds the exact same information about the employees, does not.

Currently I do not get any errors but when I click the ‘save template’ button, a new Template is not created. I can provide more information and a repo if needed. It seems so simple but I have been stuck for days and I can’t find ANYTHING on this subject so it will be a good address.

class TemplatesController < ApplicationController
  before_action :set_staff_date

  def index
    @templates = Templates.all
  end

  def save_temp
    employees = @staff_date.employees
    @template = current_user.templates.create!(template_params)
    
    
    @template.employees << ([employees: employees])
    @template.save
  end 

  private

  def set_staff_date
    @staff_date = current_user.staff_dates.find(params[:id])
  end

  def template_params
    params.permit(:name, :employees)
  end 
end
class EmployeesController < ApplicationController
  before_action :set_staff_date
  before_action :set_employee, only: %i[ show edit update destroy ]


  def new
    @employee = @staff_date.employees.build
  end

  def create
    @employee = @staff_date.employees.build(employee_params)

    if @employee.save
      respond_to do |format|
        format.html { redirect_to staff_date(@staff_date), notice: "Item was successfully created." }
        format.turbo_stream { flash.now[:notice] = "Item was successfully created." }
      end
    else
      render :new, status: :unprocessable_entity
    end
  end

  def edit
  end

  def update
    if @employee.update(employee_params)
      respond_to do |format|
        format.html { redirect_to line_staff_date_path(@employee), notice: "Item was successfully updated." }
        format.turbo_stream { flash.now[:notice] = "Item was successfully updated." }
      end
    else
      render :edit, status: :unprocessable_entity
    end
  end

  def destroy
    @employee.destroy
  
    respond_to do |format|
      format.html { redirect_to staff_date_path(@staff_date), notice: "Date was successfully destroyed." }
      format.turbo_stream { flash.now[:notice] = "Date was successfully destroyed." }
    end
  end

  private

  def set_employee
    @employee = @staff_date.employees.find(params[:id])
  end

  def employee_params
    params.require(:employee).permit(:name, :employee_number, :comment)
  end

  def set_staff_date
    @staff_date = current_user.staff_dates.find(params[:staff_date_id])
  end

  def set_template
    @template = current_user.templates.find(params[:template_id])
  end
end
class StaffDatesController < ApplicationController
  before_action :set_staff_date, only: %i[ show edit update destroy send_email]

  def send_email
    StaffMailer.with(employees: @staff_date.employees).send_staff(current_user).deliver_now
    # flash.now[:notice] = "Staff was successfully sent"
    head :ok
  end

  # GET /staff_dates or /staff_dates.json
  def index
    @staff_dates = current_user.staff_dates
  end

  def show
  end

  # GET /staff_dates/new
  def new
    @staff_date = current_user.staff_dates.new
  end

  # GET /staff_dates/1/edit
  def edit
  end

  # POST /staff_dates or /staff_dates.json
  def create
    @staff_date = current_user.staff_dates.new(staff_date_params)

    if @staff_date.save
      respond_to do |format|
        format.html { redirect_to staff_dates_path, notice: "Date was successfully created." }
        format.turbo_stream { flash.now[:notice] = "Date was successfully created." }
      end
    else
      render :new, status: :unprocessable_entity
    end
  end

  def update
    if @staff_date.update(staff_date_params)
      respond_to do |format|
        format.html { redirect_to staff_dates_path, notice: "Date was successfully updated." }
        format.turbo_stream { flash.now[:notice] = "Date was successfully updated." }
      end
    else
      render :edit, status: :unprocessable_entity
    end
  end

  # DELETE /staff_dates/1 or /staff_dates/1.json
  def destroy
    @staff_date.destroy
  
    respond_to do |format|
      format.html { redirect_to @staff_date, notice: "Date was successfully destroyed." }
      format.turbo_stream { flash.now[:notice] = "Date was successfully destroyed." }
    end
  end

  private
    

    def set_staff_date
      @staff_date = current_user.staff_dates.find(params[:id])
    end

    def staff_date_params
      params.require(:staff_date).permit(:date)
    end

    def employee_params
      params.require(:employee).permit(:name, :employee_number, :comment)
    end
end
class Template < ApplicationRecord
  belongs_to :user

end
require 'sidekiq/web'

Rails.application.routes.draw do
  get 'templates/index'
  draw :madmin
  get '/privacy', to: 'home#privacy'
  get '/terms', to: 'home#terms'
authenticate :user, lambda { |u| u.admin? } do
  mount Sidekiq::Web => '/sidekiq'

  namespace :madmin do
    resources :impersonates do
      post :impersonate, on: :member
      post :stop_impersonating, on: :collection
    end
  end
end

  resources :notifications, only: [:index]
  resources :announcements, only: [:index]
  devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks" }
  root to: 'home#index'

  resources :staff_dates do
    resources :employees, except: [:index, :show]
    post "send_email", on: :member
  end

  resources :templates do 
    post "save_temp", on: :member
  end

end
class Employee < ApplicationRecord
  belongs_to :staff_date, optional: true
  belongs_to :template, optional: true

  validates :name, presence: true
  validates :employee_number, presence: true, numericality: { only_integer: true, greater_than: 0 }
end
class StaffDate < ApplicationRecord
  has_many :employees, dependent: :destroy
  belongs_to :user

  validates :date, presence: true

  scope :ordered, -> { order(date: asc) }

  def previous_date
    staff_dates.ordered.where("date < ?", date).last
  end

  broadcasts_to ->(staff_date) { [staff_date, "staff_dates"] }, inserts_by: :prepend
end

What’s in the logs? Where’s your view that’s rendering the button?

<li class="dropdown-item"> <%= button_to "Save...", save_temp_template_path(@staff_date.id), data: {"turbo-method": :post} %></li>

The logs when “save_temp” is clicked.

12:03:09 web.1    | Started POST "/templates/1/save_temp" for ::1 at 2022-08-03 12:03:09 -0500
12:03:09 web.1    | Processing by TemplatesController#save_temp as TURBO_STREAM
12:03:09 web.1    |   Parameters: {"authenticity_token"=>"[FILTERED]", "id"=>"1"}
12:03:09 web.1    |   User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 1], ["LIMIT", 1]]
12:03:09 web.1    |   ↳ app/controllers/templates_controller.rb:20:in `set_staff_date'
12:03:09 web.1    |   StaffDate Load (0.2ms)  SELECT "staff_dates".* FROM "staff_dates" WHERE "staff_dates"."user_id" = $1 AND "staff_dates"."id" = $2 LIMIT $3  [["user_id", 1], ["id", 1], ["LIMIT", 1]]
12:03:09 web.1    |   ↳ app/controllers/templates_controller.rb:20:in `set_staff_date'
12:03:09 web.1    | Unpermitted parameters: :authenticity_token, :id. Context: { controller: TemplatesController, action: save_temp, request: #<ActionDispatch::Request:0x00007f4e7dcc3670>, params: {"authenticity_token"=>"[FILTERED]", "controller"=>"templates", "action"=>"save_temp", "id"=>"1"} }
12:03:09 web.1    |   TRANSACTION (0.1ms)  BEGIN
12:03:09 web.1    |   ↳ app/controllers/templates_controller.rb:10:in `save_temp'
12:03:09 web.1    |   Template Create (0.8ms)  INSERT INTO "templates" ("name", "created_at", "updated_at", "employees", "user_id") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["name", nil], ["created_at", "2022-08-03 17:03:09.425028"], ["updated_at", "2022-08-03 17:03:09.425028"], ["employees", "{}"], ["user_id", 1]]
12:03:09 web.1    |   ↳ app/controllers/templates_controller.rb:10:in `save_temp'
12:03:09 web.1    |   TRANSACTION (0.9ms)  COMMIT
12:03:09 web.1    |   ↳ app/controllers/templates_controller.rb:10:in `save_temp'
12:03:09 web.1    |   TRANSACTION (0.1ms)  BEGIN
12:03:09 web.1    |   ↳ app/controllers/templates_controller.rb:14:in `save_temp'
12:03:09 web.1    |   TRANSACTION (0.1ms)  ROLLBACK
12:03:09 web.1    |   ↳ app/controllers/templates_controller.rb:14:in `save_temp'
12:03:09 web.1    | Completed 500 Internal Server Error in 13ms (ActiveRecord: 2.5ms | Allocations: 6244)
12:03:09 web.1    | 
12:03:09 web.1    | 
12:03:09 web.1    |   
12:03:09 web.1    | TypeError (can't cast Hash):
12:03:09 web.1    |   
12:03:09 web.1    | app/controllers/templates_controller.rb:14:in `save_temp'
15:31:58 web.1    | [ActionCable] [User 1] Finished "/cable/" [WebSocket] for ::1 at 2022-08-03 15:31:58 -0500

I listed the additional information, also updated the above code as it looks now. UPDATE I am able to create a new Template record but it is empty and the employees are not being pushed to the array attribute called “employees” inside the Template record.

Getting closer!

Where’s your view that has the form? There’s nothing about employees in the params that are being sent over:

params: {"authenticity_token"=>"[FILTERED]", "controller"=>"templates", "action"=>"save_temp", "id"=>"1"} }

You’re also probably going to need this: ActiveRecord::NestedAttributes::ClassMethods

And I don’t see a reference to employees within the Template model, how should those employees be saved?

Thank you for your interest! I’ve been so stuck on this. So here is the weird thing to me as a newbie. The process is create new ‘staff date’, add ‘employees’ one at a time to that ‘staff date’. Then have the choice to email that list to someone or save that list as a template to be used later. Because the ‘staff date’ has many: employees I thought well maybe I should just put all those existing employees, each with it’s own employee record, into an array.

By doing it this way the Template wouldn’t have the employee association because they are only being stored in one array, not multiple arrays. Maybe I should use has_one: employees? Here is the ENTIRE view that contains the button. Sidenote, I am using hotwire on everything I can, so that is turbo and stimulus.

<main class="container">

  <%= link_to sanitize("&larr; Back to dates"), staff_dates_path %>
    <div class="header">
      <h1>Staffing</h1>

      <div class="line-item-date__header">
        <h2 class="line-item-date__title">
          <%= l(@staff_date.date, format: :long) %>
        </h2>

        </div>
      </div>
    </div>

  <%= turbo_frame_tag @staff_date do %>

      <div class="line-item-date__body">
        <div class="line-item line-item--header">
          <div class="line-item__name">Name</div>
          <div class="line-item__quantity">EE Number</div>
          <div class="line-item__actions"></div>
        </div>

        <%= turbo_frame_tag nested_dom_id(@staff_date, "employees") do %>
        <%= render @staff_date.employees %>
        <% end %>

        <%= turbo_frame_tag nested_dom_id(@staff_date, Employee.new) %>

        <div class="line-item-date__footer" >
          <%= link_to "<i class='bi bi-person-plus-fill' 
                      style='font-size: 2.0rem; color: 
                      primary;'></i>".html_safe,
                      [:new, @staff_date, :employee],
                      data: { turbo_frame: 
                      nested_dom_id(@staff_date, Employee.new) } %>             
        </div>
      </div>
    </div>
  <% end %>
  <div class="container">
    <div class="header">
      <%= button_to 'Send Staffing', send_email_staff_date_path(@staff_date.id), data: {"turbo-method": :post}, class: 'btn btn-secondary' %>

      <div class="btn-group dropup">
        <button type="button" class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
          Save Staff
        </button>
        <ul class="dropdown-menu">
          <li><a class="dropdown-item" href="#">Template 1</a></li>
          <li><a class="dropdown-item" href="#">Template 2</a></li>
          <li><a class="dropdown-item" href="#">Template 3</a></li>
          <li class="dropdown-divider"></li>
          <li class="dropdown-item"> <%= button_to "Save...", save_temp_template_path(@staff_date.id), data: {"turbo-method": :post} %></li>
        </ul>
      </div>
    </div>
 </div>
</main>

UPDATE, I removed require: params and no longer get the cast hash error. Now I get this: "can’t write unknown attribute template_id "

class TemplatesController < ApplicationController
  before_action :set_staff_date

  def index
    @templates = Templates.all
  end

  def save_temp
    employees = @staff_date.employees
    @template = current_user.templates.create!(template_params)
    
    
    @template.employees << employees
    redirect_to staff_date_path(@staff_date)
  end 

  private

  def set_staff_date
    @staff_date = current_user.staff_dates.find(params[:id])
  end

  def template_params
    params.permit(:name, :employees)
  end 
end

UPDATE 3 So then if I do this below, that Template id error goes away and I’m back to this new hash error " ArgumentError in TemplatesController#save_temp When assigning attributes, you must pass a hash as an argument, String passed."

class TemplatesController < ApplicationController
  before_action :set_staff_date

  def index
    @templates = Templates.all
  end

  def save_temp
    employees = @staff_date.employees
    @template = current_user.templates.create!(params[:id])
    
    
    @template.employees << employees
    redirect_to staff_date_path(@staff_date)
  end 

  private

  def set_staff_date
    @staff_date = current_user.staff_dates.find(params[:id])
  end

end

On this line you’re passing a string ID from params, create takes a hash:

    @template = current_user.templates.create!(params[:id])

I’m guessing you probably want to be passing other params with that as well instead, you don’t usually set an ID on creation and instead let the database set an auto-incrementing ID or UUID.

Ah yes I see, that makes sense.