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.