Reputation: 906
I've been battling this for a while now and I can't figure it out. I have a user model using devise. Users can upload songs, and add youtube videos etc..
I'm trying to let users add/delete songs and videos from the devise edit registrations
view.
Videos upload fine, but as songs are a nested resource of playlists, which belongs to user, I think I'm getting muddle up.
Music uploads with the same form on it's corresponding page, but not from the devise registration edit
view.
routes:
devise_for :users, controllers: { registrations: "users/registrations", sessions: "users/sessions" }
resources :videos
resources :playlists do
resources :songs
end
Devise registrations controller:
def edit
@song = Song.new
@video = Video.new
end
Form in devise edit registrations:
<div id="user-music-box">
<p class="p-details-title"> Upload Music </p>
<%= simple_form_for [@user.playlist, @song] do |f| %>
<%= f.file_field :audio %>
<%= f.button :submit %>
<% end %>
</div>
<div id="user-video-box">
<p class="p-details-title"> Add videos </p>
<%= simple_form_for @video do |f| %>
<%= f.input :youtubeurl %>
<%= f.button :submit %>
<% end %>
</div>
As I said, videos (Which is a youtube url string) create and save no problem. The exact same form for songs, basically seems to just update the user registration
. The song information is shown in the server logs, but no playlist_id is present and nothing gets saved.
Songs controller:
def new
if user_signed_in?
@song = Song.new
if current_user.playlist.songs.count >= 5
redirect_to user_path(current_user)
flash[:danger] = "You can only upload 5 songs."
end
else
redirect_to(root_url)
flash[:danger] = "You must sign in to upload songs"
end
end
def create
@song = current_user.playlist.songs.new song_params
@song.playlist_id = @playlist.id
if @song.save
respond_to do |format|
format.html {redirect_to user_path(current_user)}
format.js
end
else
render 'new'
end
end
Playlist.rb
class Playlist < ActiveRecord::Base
belongs_to :user
has_many :songs, :dependent => :destroy
accepts_nested_attributes_for :songs
end
song.rb
class Song < ActiveRecord::Base
belongs_to :user
belongs_to :playlist
has_attached_file :audio
validates_attachment_presence :audio
validates_attachment_content_type :audio, :content_type => ['audio/mp3','audio/mpeg']
end
Upvotes: 1
Views: 232
Reputation: 76774
Unless you're passing songs
/playlists
through accepts_nested_attributes_for
you shouldn't be using registrations#edit
. I'll detail both ways to achieve what you want below:
Nested Attributes
#app/models/user.rb
class User < ActiveRecord::Base
has_many :videos
has_many :playlists
has_many :songs, through: :playlists
accepts_nested_attributes_for :videos
end
#app/models/playlist.rb
class PlayList < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :songs
end
#app/models/song.rb
class Song < ActiveRecord::Base
has_and_belongs_to_many :playlists
end
The importance of this is that to use it properly, you're able to edit the @user
object directly, passing the nested attributes through the fields_for
helper:
#config/routes.rb
devise_for :users, controllers: { registrations: "users/registrations", sessions: "users/sessions" }
#app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < ApplicationController
before_action :authenticate_user!, only: [:edit, :update]
def edit
@user = current_user
@user.playlists.build.build_song
@user.videos.build
end
def update
@user = current_user.update user_params
end
private
def user_params
params.require(:user).permit(:user, :attributes, videos_attributes: [:youtubeurl], playlists_attributes: [song_ids: [], song_attributes: [:title, :artist, :etc]])
end
end
This will allow you to use:
#app/views/users/registrations/edit.html.erb
<%= form_for @user do |f| %>
<%= f.fields_for :videos do |v| %>
<%= v.text_field :youtubeurl %>
<% end %>
<%= f.fields_for :playlists do |p| %>
<%= p.collection_select :song_ids, Song.all, :id, :name %>
<%= p.fields_for :song do |s| %>
<%= f.text_field :title %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
This will give you a single form, from which you'll be able to create videos
, playlists
and songs
for the @user
.
Separate
The other option is to create the object separately.
There is no technical reason for preferring this way over nested attributes
; you'd do it to make sure you have the routes in the correct order etc.
As a note, you need to remember that routes != model structure. You can have any routes you want, so long as they define a good pattern for your models:
# config/routes.rb
authenticated :user do #-> user has to be logged in
resources :videos, :playlists, :songs #-> url.com/videos/new
end
# app/controllers/videos_controller.rb
class VideosController < ApplicationController
def new
@video = current_user.videos.new
end
def create
@video = current_user.videos.new video_params
@video.save
end
private
def video_params
params.require(:video).permit(:youtubeurl)
end
end
# app/views/videos/new.html.erb
<%= form_for @video do |f| %>
<%= f.text_field :youtubeurl %>
<%= f.submit %>
<% end %>
The above will require the duplication of the VideosController
for Playlists
and Songs
Upvotes: 1