What I'm trying to do is add songs that artists have already uploaded to a user library (I have already set up my app so that artists can upload songs). Also, I have set up my code so that an empty user library is created after a user signs up (using the after_create Active Record Callback).
To be more clear, I would like for the user to be able to add songs they see within the site to their library.
However, this is escaping me. I am familiar with CRUD, and have an idea how I would create a library and add existing songs to it, but I am not quite sure how I could add a song to a user library by clicking a button/link saying "Add Song To Library" which would be next to a song, and having it add to the user's existing empty library.
My existing code is below.
User.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :meta, polymorphic: true
before_create :create_empty_profile
after_create :create_empty_library #may not be the best way to do it ¯\_(ツ)_/¯
acts_as_messageable
has_many :playlists
has_many :user_friendships, dependent: :destroy
has_many :friends, -> { where(user_friendships: { state: 'accepted'}) }, through: :user_friendships
has_many :pending_user_friendships, -> { where ({ state: 'pending' }) }, class_name: 'UserFriendship', foreign_key: :user_id
has_many :pending_friends, through: :pending_user_friendships, source: :friend
has_many :chat_rooms, dependent: :destroy
has_many :chat_messages, dependent: :destroy
has_many :votes, dependent: :destroy
mount_uploader :profile_pic, ProfilePicUploader
def mailboxer_name
self.name
end
def mailboxer_email(object)
self.email
end
def admin?
role == 'admin'
end
def moderator?
role == 'moderator'
end
def create_empty_profile
if is_artist?
profile = ArtistProfile.new
else
profile = UserProfile.new
end
profile.save(validate: false)
self.meta_id = profile.id
self.meta_type = profile.class.name
end
def create_empty_library
library = Library.new
library.user_id = self.id
library.save(validate: false)
end
end
Library.rb:
class Library < ActiveRecord::Base
belongs_to :user
has_many :library_songs
has_many :songs, through: :library_songs
has_many :library_albums
has_many :albums, through: :library_albums
end
library_song.rb
class LibrarySong < ActiveRecord::Base
belongs_to :library
belongs_to :song
end
library_album.rb
class LibraryAlbum < ActiveRecord::Base
belongs_to :library
belongs_to :album
end
libraries_controller.rb
class LibrariesController < ApplicationController
def index
#libraries = Library.all
end
def show
#library = Library.find(params[:id])
end
end
I was able to create playlists and add songs to them using the form/controller below.
playlists/new.html.erb:
<h1>New Playlist</h1>
<%= form_for(#playlist) do |f| %>
<%= f.text_field :name %>
<% Song.all.each do |song| -%>
<div>
<%= check_box_tag :song_ids, song.id, false, :name => 'playlist[song_ids][]', id: "song-#{song.id}" %>
<%= song.name %>
</div>
<% end %>
<%= f.submit %>
<% end %>
playlists_controller.rb:
class PlaylistsController < ApplicationController
def index
#playlists = Playlist.all
end
def show
#playlist = Playlist.find(params[:id])
end
def new
#playlist = Playlist.new
end
def create
#playlist = Playlist.create(playlist_params)
redirect_to #playlist
end
private
def playlist_params
params.require(:playlist).permit(:name, song_ids: [])
end
end
However, the main issue is that in the form above, the playlist is being created along with the existing songs. In this case, I would need to add existing songs to an existing library that is empty.
Any ideas, guys? This would be very helpful. I would be happy to upload any code needed.
It looks to me like you don't actually have has_many :libraries set in your user model. Judging by your Library model, I think this was what you had intended. You should really just create the 'new' models before you save the User model. You could use something similar to this and do it all in one action.
class User < ActiveRecord::Base
def self.build_full_user(params, songs)
# Assign all normal attributes here
new_user = User.new
new_user.name = params[:name]
# If you want to assign new songs, just make a new Library model and associate them.
new_library = Library.new
# Build the song models if you haven't found/created or passed them in already.
new_songs = Songs.build_songs_from_list(songs)
new_library.songs << new_songs
new_user.libraries << new_library
# You can do the save check here or up one level if you'd like.
return new_user
end
end
Related
I'm working on an app where users can search for games (data pulled from an API), and add them to a library. I managed to get the adding part working, but I'm having some issues with deleting a game from the user's library.
Here are my create and destroy functions.
def create
#library_game = Game.new
#library_game.game_id = params[:game_id]
#library_game.fetch_data
#library_game.save!
current_user.build_library
current_user.library.games << #library_game
redirect_to library_path
end
def destroy
current_user.games.destroy(game_id: params[:id])
redirect_to library_path
end
With the current code, when I try to delete something I get the following error:
ActiveRecord::AssociationTypeMismatch in GamesController#destroy
Game(#70255379902700) expected, got {:game=>"52921"} which is an instance of Hash(#70255376633020)
So it seems to be getting the id but it is expecting the entire game? I have tried editing my params to include just :game or :game_id but I am still getting errors.
Here is the link_to to delete the game.
<%= link_to 'Remove from Library', user_game_path(game.id), method: :delete %>
Here are my models showing associations.
Game
class Game < ApplicationRecord
has_many :library_games
has_many :libraries, through: :library_games
has_many :users, through: :libraries
serialize :data
attr_accessor :game_id
def fetch_data
game = GiantBomb::Game.detail(game_id)
self.data = Hash[game.instance_variables.map { |var| [var.to_s[1..-1], game.instance_variable_get(var)] } ]
end
def to_giant_bomb_game
GiantBomb::Game.new(data)
end
end
Library
class Library < ApplicationRecord
belongs_to :user
has_many :library_games
has_many :games, through: :library_games
end
User
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one :library
has_many :games, through: :library
def has_game?(game)
games.where(id: game.id).exist?
end
def build_library
return if library.present?
self.library = Library.new
end
end
Library_Game
class LibraryGame < ApplicationRecord
belongs_to :library
belongs_to :game
has_one :user, through: :library
end
What am I getting wrong with my destroy method?
Here's my understanding of your domain model,
Users (1 to 1)-> Library (1 to n)-> Library Games (1 to 1)-> Games
If this understanding is correct, then I see a problem with your create and destroy methods. You should be creating and destroying instances of LibraryGames, and not Games. Below is a rough idea of how you can implement things,
def create
# current_user.build_library # Wouldn't all your users already have libraries?
#library_game = LibraryGame.new
#library_game.game_id = params[:game_id]
#library_game.library = current_user.library
# #library_game.fetch_data # What does this do? This might not be needed here
#library_game.save!
redirect_to library_path
end
def destroy
current_user.library_games.destroy(params[:id])
redirect_to library_path
end
I have a "userinfos_controller" where user stores their information. They also can upload a video to their profile. I am showing this video under the show method in the userinfos_controller as shown below. I want the '#myvideo" to get the video associated with the user. When a user deletes their video and re-upload, the video_id changes. So I can't call that video using the video_id. Is there a way to call Video.userinfo.last? I don't want to call Video.last, because it'll give me the last video uploaded across all users, not just the last video uploaded by that particular user. You can also see in my rails console, the video has a user_id, userinfo_id and a video_id. So right now, under my show method, when I do:
def show
#myvideo = Video.find(params[:userinfo_id])
end
It looks for Video_id that is equal to userinfo_id, while I want it to look for the last video uploaded by that particular user/userinfo.
My id relationships(please right click and open in new tab to see more clearly):
My userinfos controller:
class UserinfosController < ApplicationController
before_action :find_userinfo, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
def index
#userinfors = Userinfo.all
#myvideo = Video.all
end
def show
#myvideo = Video.find(params[:userinfo_id])
end
def new
#userinformation = current_user.userinfos.build
end
def create
#userinformation = current_user.userinfos.build(userinfo_params)
if #userinformation.save
redirect_to root_path
else
render 'new'
end
end
def edit
end
def update
end
def destroy
#userinformation.destroy
redirect_to userinfo_path
end
private
def userinfo_params
params.require(:userinfo).permit(:name, :email, :college, :gpa, :major)
end
def find_userinfo
#userinformation = Userinfo.find(params[:id])
end
end
Show video view:
<div>
<%= video_tag #myvideo.introvideo_url.to_s, :size => "480x320", :controls =>true %>
</div>
Video model:
class Video < ActiveRecord::Base
belongs_to :userinfo
belongs_to :user
mount_uploader :introvideo, VideoUploader
end
Userinfo model:
class Userinfo < ActiveRecord::Base
belongs_to :user
has_many :videos
end
User model:
class User < ActiveRecord::Base
has_many :vidoes
has_many :userinfos
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
My migrations that show all the id's added:
My routes:
Either create a direct association between User and Video - or make it indirect through a join model. Don't do both.
Direct 1-n:
class User < ApplicationRecord
has_many :videos
has_many :user_infos
# ...
end
class Video < ApplicationRecord
belongs_to :user
# ...
end
class UserInfo < ApplicationRecord
belongs_to :user
has_many :videos, through: :user
end
Indirect 1-n:
class User < ApplicationRecord
has_many :user_infos
has_many :videos, through: :user_infos
# ...
end
class UserInfo < ApplicationRecord
belongs_to :user
has_many :videos
end
class Video < ApplicationRecord
belongs_to :user_info
has_one :user, through: :user_info
# ...
end
Both will let you do:
#user.videos.last
#user_info.videos.last
def show
#my_video = #user_info.videos.order(created_at: :desc).last
end
Try with ActiveRecord#last:
def show
#myvideo = Video.where('userinfo_id = ?', params[:userinfo_id]).last
end
That will give you the videos uploaded to userinfo with id equal to params[:userinfo_id] taking the last record.
Not sure if I understood you correctly, but I think you want to access the Video that your user previously uploaded, after it uploaded the new one.
Assuming you have a has_many :videos relationship set up on the User model, you can do this:
#user.videos.order(created_at: :desc).second
Or if you don't have the user instance and just have user_id.
Video.where(userinfo_id: params[:userinfo_id]).order(created_at: :desc).second
Hope this helps.
EDIT:
Or maybe you just want to access the latest users video. Again, I don't know how you set up your relations. I am assuming user can have many videos.
Video.where(userinfo_id: params[:userinfo_id]).order(created_at: :desc).first
Or shorter
Video.where(userinfo_id: params[:userinfo_id]).last
I have to build a simple app that allows users to loan and borrow books. Simply put a User can create books, and they can pick another user to loan the book to.
I have three models User, Book and Loan:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :books
has_many :loans, through: :books
has_many :borrowings, class_name: "Loan"
validates :username, uniqueness: true
validates :username, presence: true
end
class Book < ActiveRecord::Base
belongs_to :user
has_many :loans
validates :title, :author, presence: true
end
class Loan < ActiveRecord::Base
belongs_to :user
belongs_to :book
validates :user, :book, :status, presence: true
end
The LoansController looks like this:
class LoansController < ApplicationController
before_action :find_book, only: [:new, :create]
def new
#users = User.all
#loan = Loan.new
authorize #loan
end
def create
#loan = Loan.new
#loan.book = #book
#loan.user = User.find(loan_params[:user_id])
#loan.status = "loaned"
authorize #loan
if #loan.save
redirect_to :root
else
render :new
end
end
private
def loan_params
params.require(:loan).permit(:user_id)
end
def find_book
#book = Book.find(params[:book_id])
end
end
My form looks like:
<%= simple_form_for([#book, #loan]) do |f| %>
<%= f.input :user_id, collection: #users.map { |user| [user.username, user.id] }, prompt: "Select a User" %>
<%= f.submit %>
<% end %>
If I submit the form without selecting a user, and keep the "Select a User" prompt option, the form is submitted and the app crash because it can't find a user with id=
I don't know why the user presence validation in the form does not work...
you will change your Create method
def create
#loan = Loan.new
#loan.book = #book
#loan.user = User.find_by_id(loan_params[:user_id])
#loan.status = "loaned"
authorize #loan
if #loan.save
redirect_to :root
else
render :new
end
end
I am using rails4 and also new to ROR.
I am using devise to create User. There is "workspace" model which is related to user though "user_workspace" model.
user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :user_workspaces
has_many :workspaces, through: :user_workspaces
end
workspace.rb
class Workspace < ActiveRecord::Base
has_many :users, through: :user_workspaces
has_many :user_workspaces
end
user_workspace.rb
class UserWorkspace < ActiveRecord::Base
belongs_to :user
belongs_to :workspace
end
I want to create a workspace and show it in the index page
workspace_controller
def create
#user=User.find(params[:id])
#user_worspace = #user.user_workspaces.build(params[:user_worspace])
#workspace = #user_worspace.build_workspace(params[:workspace].permit(:name))
if #workspace.save
flash[:notice] = "workspace created"
redirect_to workspaces_path
else
flash[:error] = "Name cant be blank"
redirect_to workspaces_path
end
end
def index
#user=User.find(params[:id])
#user_worspace = #user.user_workspaces.build(params[:user_worspace])
#workspace = #user_worspace.build_workspace
#users = current_user.user_workspaces.collect(&:workspace_id)
#workspaces =#users.workspaces.all
end
I have given new in my index as I am using modal view to create new workspace form the index page.
index.html.erb
<div class = "container">
<% if #users.present? %>
<% #workspaces.each do |workspace| %>
<div class= "workspace-list">
<strong><%= link_to workspace.name, workspaces_show_path(workspace.id) %></strong> </div>
<% end %>
<% end %>
</div>
I would like to know the exact way of creating and displaying the things using has_many :throught association.
There are different kinds of users in my system. One kind is, let's say, a designer:
class Designer < ActiveRecord::Base
attr_accessible :user_id, :portfolio_id, :some_designer_specific_field
belongs_to :user
belongs_to :portfolio
end
That is created immediately when the user signs up. So when a user fills out the sign_up form, a Devise User is created along with this Designer object with its user_id set to the new User that was created. It's easy enough if I have access to the code of the controller. But with Devise, I don't have access to this registration controller.
What's the proper way to create a User and Designer upon registration?
In a recent project I've used the form object pattern to create both a Devise user and a company in one step. This involves bypassing Devise's RegistrationsController and creating your own SignupsController.
# config/routes.rb
# Signups
get 'signup' => 'signups#new', as: :new_signup
post 'signup' => 'signups#create', as: :signups
# app/controllers/signups_controller.rb
class SignupsController < ApplicationController
def new
#signup = Signup.new
end
def create
#signup = Signup.new(params[:signup])
if #signup.save
sign_in #signup.user
redirect_to projects_path, notice: 'You signed up successfully.'
else
render action: :new
end
end
end
The referenced signup model is defined as a form object.
# app/models/signup.rb
# The signup class is a form object class that helps with
# creating a user, account and project all in one step and form
class Signup
# Available in Rails 4
include ActiveModel::Model
attr_reader :user
attr_reader :account
attr_reader :membership
attr_accessor :name
attr_accessor :company_name
attr_accessor :email
attr_accessor :password
validates :name, :company_name, :email, :password, presence: true
def save
# Validate signup object
return false unless valid?
delegate_attributes_for_user
delegate_attributes_for_account
delegate_errors_for_user unless #user.valid?
delegate_errors_for_account unless #account.valid?
# Have any errors been added by validating user and account?
if !errors.any?
persist!
true
else
false
end
end
private
def delegate_attributes_for_user
#user = User.new do |user|
user.name = name
user.email = email
user.password = password
user.password_confirmation = password
end
end
def delegate_attributes_for_account
#account = Account.new do |account|
account.name = company_name
end
end
def delegate_errors_for_user
errors.add(:name, #user.errors[:name].first) if #user.errors[:name].present?
errors.add(:email, #user.errors[:email].first) if #user.errors[:email].present?
errors.add(:password, #user.errors[:password].first) if #user.errors[:password].present?
end
def delegate_errors_for_account
errors.add(:company_name, #account.errors[:name].first) if #account.errors[:name].present?
end
def persist!
#user.save!
#account.save!
create_admin_membership
end
def create_admin_membership
#membership = Membership.create! do |membership|
membership.user = #user
membership.account = #account
membership.admin = true
end
end
end
An excellent read on form objects (and source for my work) is this CodeClimate blog post on Refactoring.
In all, I prefer this approach vastly over using accepts_nested_attributes_for, though there might be even greater ways out there. Let me know if you find one!
===
Edit: Added the referenced models and their associations for better understanding.
class User < ActiveRecord::Base
# Memberships and accounts
has_many :memberships
has_many :accounts, through: :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :account
end
class Account < ActiveRecord::Base
# Memberships and members
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships
has_many :admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => true }
has_many :non_admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => false }
end
This structure in the model is modeled alongside saucy, a gem by thoughtbot. The source is not on Github AFAIK, but can extract it from the gem. I've been learning a lot by remodeling it.
If you don't want to change the registration controller, one way is to use the ActiveRecord callbacks
class User < ActiveRecord::Base
after_create :create_designer
private
def create_designer
Designer.create(user_id: self.id)
end
end