Rails: как можно постепенно добавлять элементы в отсортированный список в одном представлении

Я пытаюсь создать сет-лист для музыкантов. Пользователи могут загружать свой репертуар песен и выбирать песни из списка этих песен, чтобы заполнить сет-лист для определенного исполнения.

Смотрите изображение: setlist_screenshot. Нажав значок стрелки на песнях слева, вы добавите песню в сортируемый список справа.

Модели в вопросе:

class Setlist < ActiveRecord::Base
  belongs_to :organization
  # join between Set Lists & Arrangements(songs)
  has_many :items, -> { order(position: :asc) }
  has_many :arrangements, through: :items
end

-

class Item < ActiveRecord::Base
  # join between Set Lists & Arrangements(songs)
  belongs_to :arrangement
  belongs_to :setlist
  acts_as_list scope: :setlist
end

-

class Arrangement < ActiveRecord::Base
  belongs_to :song
  belongs_to :organization
  has_many :attachments     
  # join between Set Lists & Arrangements(songs)
  has_many :items
  has_many :setlists, through: :items
end

В основном в сет-листе есть много аранжировок (которые являются песнями) через элементы (присоединение к таблице), и аранжировки (песни), конечно, могут быть во многих сет-листах.

Код, который я имею в виду для кнопки, чтобы добавить песню:

<%= link_to(organization_setlists_path(:arrangement_id => song.arrangements.first.id, setlist_id: @new_set.id, remote: true), method: "post", class: "secondary-content" )

который является пост-запросом (включая параметры для идентификатора аранжировки песни и идентификатора Setlist.new, выполненного в новом действии) к действию create контроллера setlists, как показано ниже:

class SetlistsController < ApplicationController
before_action :authenticate_user!

def new
    if @new_set.nil?
        @new_set = Setlist.create
        @new_item = Item.new 
    end
end

def create
    @arrangement = Arrangement.find(params[:arrangement_id])
    @new_item = Item.new 
    @new_item.setlist_id = Setlist.find(params[:setlist_id])
    @new_item.arrangement_id = @arrangement.id
    if @new_item.save
        flash[:message] = "Song Sucessfully Added!"
    else
        flash[:message] = "Something Went Wrong"
    end
end  

В идеале я хотел бы, чтобы новый элемент создавался (удаленно) для одного и того же сет-листа после каждого нажатия кнопки добавления / стрелки, пока пользователь не закончит создание этого сет-листа. Чтобы отобразить это на правой стороне, у меня есть:

<% if @new_set && @new_set.items.count >=1 %>
    <ul>
    <% @new_set.items.each do |item| %>
        <li class="center-align"><%= item.arrangement.title %></li>
    <% end %>
    </ul>
<% else %>
    <p class="center-align">Add Songs From The List On The Left</p>
<% end %>

В конце концов, пользователь должен иметь возможность завершить его и перейти к созданию другого сет-листа, если он хочет.

Я действительно понятия не имею, как это сделать, кроме того, что я сделал здесь, что не работает. Вопросы:

Например, действие create фактически создает элемент и присваивает новому элементу внешний ключ для договоренностей, но не для списков заданий по какой-то причине, даже если идентификатор заданного списка правильно передается из параметров link_to.

Кроме того, каждый раз, когда страница обновляется, создается новый сет-лист из того, что есть в новом действии, поэтому каждый добавленный элемент попадает в сет-лист, затем генерируется пустой сет-лист, поэтому справа ничего не появится,

И, наконец, все это кажется беспорядочным. У кого-нибудь есть лучшая стратегия дизайна для достижения этой цели? Любая помощь будет принята с благодарностью!!!

1 ответ

Решение

Основная проблема заключается в том, что ваш контроллер нарушает соглашения.

Ваш SetlistController должен создать Setlist когда пользователь отправляет /setlists,

Если вы хотите создать новую дочернюю запись, вы должны POST: /setlists/:setlist_id/items, Это будет обработано ItemsController,

# routes.rb
resources :setlists, shallow: true do
  resources :items
end

class ItemsController < ApplicationController

  respond_to :html
  before_action :find_setlist!, only: [:create, :index]

  def create
    @item = @setlist.items.build(item_params)
    @item.save
    respond_with @item
  end

  # ...

  private
    def find_setlist!
      @setlist = Setlist.includes(:items).find!(params[:setlist_id])
    end

    def items_params
       params.require(:item)
             .permit(
                :arrangement_id
             )
    end
end

<% @arrangements.each do |a| %>
  <%= button_to 'Add to set', setlist_items_path(@setlist), 
      arrangement_id: a.id, remote: true 
  %>
<% end %>

Альтернативой является использование accepts_nested_attibutes_for :items чтобы позволить Setlist принять параметры для предметов, а также.

Ключевое отличие здесь заключается в том, что при добавлении дополнительных элементов в существующий сет-лист он, например, будет использовать:

PATCH /setlist/:id
params: {
  item_attributes: [
    {
      arrangement_id: 2
    },
    {
      arrangement_id: 3
    }
  ]
} 

Реальное преимущество заключается в том, что пользователь может выполнить несколько операций, таких как добавление / удаление, и вам просто нужно отправить один набор в базу данных, чтобы обновить состояние, вместо использования нескольких вызовов POST / DELETE для каждой дочерней записи.

class Setlist < ActiveRecord::Base
  belongs_to :organization
  # join between Set Lists & Arrangements(songs)
  has_many :items, -> { order(position: :asc) }
  has_many :arrangements, through: :items
  accepts_nested_attributes_for :items, allow_destroy: true
end

class SetlistsController < ApplicationController

  respond_to :html
  before_action :set_setlist, only: [:show, :edit, :update, :destroy]

  def new
    @setlist = Setlist.new
    @setlist.items.build 
  end

  def create
    @setlist = Setlist.new(setlist_params)
    @setlist.save
    respond_with @setlist
  end

  def update
    @setlist.update(setlist_params)
    @setlist.items.build
    respond_with @setlist
  end

  # ...

  private
    def set_setlist
      @setlist = Setlist.includes(:items).find(params[:id])
    end

    def setlist_params
      params.require(:setlist)
            .permit(
              :foo, :bar # attributes for the list
              items_attributes: [ :position, :arrangement_id, :_destroy ]
            )
    end
end

Базовая настройка формы для поддержки этого будет выглядеть примерно так:

<%= form_for(@setlist) do |f| %>
  <%= fields_for :items do |item| %>
    <%= item.number_field :postition %>
    <%= item.collection_select(:arrangement_id, @setlist.organization.arrangements, :id, :name) %>
    <%= item.checkbox :_destroy, label: 'Delete?' %>
  <% end %>
<% end %>

Конечно, это просто старая старая синхронная HTML-форма - добавление функциональности AJAX, переупорядочение drag'n'drop и т. Д. Немного выходит за рамки.

Обратите внимание, что эти решения не являются эксклюзивными. Вы можете легко иметь обе опции (вложенные атрибуты и назначенный контроллер) в своем приложении в зависимости от того, нужны ли вам ваши требования, чтобы вы могли также манипулировать дочерними записями по отдельности или если вы хотите предоставить их как часть вашего API.

Другие вопросы по тегам