Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 5 - how to dynamically add nested fields in the edit form?

Already a month trying to solve the problem at first sight is not very complicated: There are 3 models - team, user and team_user (has_namy: through) In the form of edit and new team to do the ability to dynamically add members of this team.

Scenario:

  • A user comes to the team new form
  • Indicates the name of the team
  • After name field select user (team member)
  • Click the Add member button
  • After validation of a member is added after team's name field in text field + delete button opposite
  • From the selector delete his (member) name (as it is already a team member)
  • In selite selects the next user, and click the Add member button
  • Press the Submit button to save the new team and team's members

Difficulties:

  • I tried to do through the gem Cocoon, but it is impossible to make different parshaly to select the user to be added to it (SELECT) and has added members (full name - text)
  • If done through the <% = from_for ... remote: true%> and a separate controller or new action in the controller teams_controller, it will be two forms of nested (shape and form team team_user) with his 2nd submit button. Attachment forms, as I understand, is not gud.
  • Changes in the form (changing the name of the team and add / remove the team members have the count only after clicking on the save to basically submit the form team)

enter image description here

app/models/user.rb

class User < ApplicationRecord
  has_many :team_users
  has_many :teams, through: :team_users
  accepts_nested_attributes_for :team_users, :teams, allow_destroy: true
end

app/models/team.rb

class Team < ApplicationRecord
  has_many :team_users
  has_many :users, through: :team_users
  accepts_nested_attributes_for :team_users, allow_destroy: true, reject_if: proc { |a| a['user_id'].blank? }
end

app/models/team_user.rb

class TeamUser < ApplicationRecord
  belongs_to :team
  belongs_to :user
  accepts_nested_attributes_for :team, :user, allow_destroy: true
end

app/controllers/teams_controller.rb

class TeamsController < ApplicationController
  before_action :set_team, :set_team_users, only: [:show, :edit, :update, :destroy]
  before_action :set_team_ancestry, only: [:new, :edit, :create, :update, :destroy]
  before_action :set_new_team_user, only: [:new, :edit]
  before_action :logged_in_user

  layout 'sidebar'

  # GET /teams
  def index
    @teams = Team.search(params[:search], :name).sorting(params[:sort], params[:direction]).paginate(page: params[:page])
  end

  # GET /teams/1
  def show
  end

  # GET /teams/new
  def new
    @team = Team.new(parent_id: params[:parent_id])
  end

  # GET /teams/1/edit
  def edit
    @team_users = @team.team_users
  end

  # POST /teams
  def create
    @team = Team.new(team_params)

    respond_to do |format|
      if @team.save
        format.html { redirect_to @team, success: t('.flash.success.message') }
      else
        format.html { render :new, danger: t('.flash.danger.message') }
      end
    end
  end

  # PATCH/PUT /teams/1
  def update
    respond_to do |format|
      if @team.update(team_params)
        format.html { redirect_to @team, success: t('.flash.success.message') }
      else
        format.html { render :edit, danger: t('.flash.danger.message') }
      end
    end
  end

  # DELETE /teams/1
  def destroy
    @team.destroy
    respond_to do |format|
      format.html { redirect_to teams_url, success: t('.flash.success.message') }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_team
      @team = Team.find(params[:id])
    end

    def set_team_ancestry
      @team_collection = Team.where.not(id: params[:id]).all.each { |c| c.ancestry = c.ancestry.to_s + (c.ancestry != nil ? "/" : '') + c.id.to_s 
      }.sort{ |x,y| x.ancestry <=> y.ancestry }.map{ |c| ["-" * (c.depth - 1) + c.name,c.id] }
    end

    def set_team_users
      @team_users_collection = User.all.collect { |p| [ p.name, p.id ] }
    end

    def set_new_team_user
      @team_users_new = @team.team_users.build
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def team_params
      params.require(:team).permit(
        :name,
        :parent_id,
        team_users_attributes: [:_destroy, :id, :user_id]
      )
    end
end
like image 413
Nikolay Lipovtsev Avatar asked Dec 04 '16 15:12

Nikolay Lipovtsev


1 Answers

I followed the solution from a DriftingRuby episode. I was able to implement it in my application as well as customize some features. Note: This applies to both the new and edit forms but you can easily make it for just the edit form.

like image 186
dspencer Avatar answered Sep 18 '22 01:09

dspencer