How to give an instance variable access to an 'id' in rails? - ruby-on-rails

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

Related

Destroy method: Passing params for API gathered data

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

How to add existing songs to a music library Rails 4

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

Creating User model with Connection (friendships)

I have Retailer and Supplier model which inherits from the User model. I been struggling to create Friendship model between them. I want retailer to send friend request to supplier based on #suuplier.account_number. Retailer can only send request if he has account number. So far i have request model which does exactly that. Here is what i have in controller,
class RequestsController < ApplicationController
before_action :authenticate_user!
def new
#retailer = current_user
#request = #retailer.requests.new
end
def create
#retailer = current_user
#supplier = Supplier.find_by(params[:account_number])
#request = #retailer.requests.new(request_params)
#request.retailer = current_user
#request.supplier = #supplier
if #request.save
redirect_to retailer_pending_suppliers_path, notice: "Your request has been successfully sent"
else
render 'new'
end
end
private
def request_params
params.require(:request).permit(:retailer_id, :supplier_id,:account_number ,:status)
end
end
It works but still i am still 100% not confident with what i am doing.This is like a brock wall moment, not sure what to do now. How do i implement friendship with them? what models do i need? and controller?
thank you in advance.
user model
class User < ActiveRecord::Base
before_validation :generate_account_number
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def role?
self.class.name.downcase.to_sym
end
def admin?
self.type == "Admin"
end
def supplier?
self.type == "Supplier"
end
def retailer?
self.type == "Retailer"
end
def active_for_authentication?
super && approved?
end
def inactive_message
if !approved?
:not_approved
else
super # Use whatever other message
end
end
private
#this method generates and assigns random account number to the users
def generate_account_number
rand_num = SecureRandom.hex(3).upcase
if self.admin?
self.account_number = "EGYPT" + "-" + rand_num
elsif self.retailer?
self.account_number = "NJ" + "-" + rand_num + "-" + "RET"
elsif self.supplier?
self.account_number = "NJ" +"-" + rand_num + "-" + "SUP"
else
self.account_number = 0
end
end
end
Retailer Model
class Retailer < User
has_many :stations
has_many :retailer_suppliers
has_many :suppliers , through: :retailer_suppliers, as: :connections
has_many :requests
end
Supplier Model
class Supplier < User
has_many :retailer_suppliers
has_many :retailers, through: :retailer_suppliers
has_many :requests
end
Request model
class Request < ActiveRecord::Base
belongs_to :retailer
belongs_to :supplier
enum status: [:pending, :approved, :denied]
end
I have User type attribute in User model.
You can have a friendship model similar to your request model that belongs to both retailer and supplier
class Friendship < ActiveRecord::Base
belongs_to :retailer
belongs_to :supplier
end
Alternatively, just use request model with :status=> :approved to track friendship.
Show supplier the request, when the supplier clicks accept button, you can find the request and update_attributes for it from pending to approved
I think this railscast might help you: self referential association
But it's similar to Haider's response. You'll either need another joins table, or modify the requests joins table to indicate a friendship, which I'm guessing will need to store additional metadata.

undefined method `current_user' for #<Teacher:0x00000004fcac48>

I am using Devise gem in my Rails project. The reason I use Devise is because I want just logged in user can rate their teachers. Now, I get this error in my ratings_controller.rb although I already added user_id into my ratings and teachers table.
undefined method `current_user' for Teacher:0x00000004fcac48
#rating = #teacher.current_user.ratings.build
Here is my ratings_controller.rb:
class RatingsController < ApplicationController
def new
get_teacher
#rating = #teacher.current_user.ratings.build
end
def create
get_teacher
#rating = #teacher.current_user.ratings.create(rating_params)
if #rating.save
redirect_to school_teacher_path(#teacher.school, #teacher)
else
render 'new'
end
end
def destroy
get_teacher
#rating = #teacher.ratings.find(params[:id])
#rating.destroy
redirect_to school_teacher_path(#teacher.school, #teacher)
end
def get_teacher
#teacher = Teacher.find(params[:teacher_id])
end
private
def rating_params
params.require(:rating).permit(:easiness, :helpfulness, :clarity, :comment,
:teacher_id, :school_id)
end
end
rating.rb:
class Rating < ActiveRecord::Base
belongs_to :teacher
belongs_to :user
end
teacher.rb:
class Teacher < ActiveRecord::Base
belongs_to :school
has_many :ratings, dependent: :destroy
has_many :users
def name
"#{firstName} #{middleName} #{lastName}"
end
def to_s
name
end
end
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
has_many :ratings
has_many :teachers
end
current_user is a controller helper, it's not a instance method of model.
you can add before_action :authenticate_user! in controller to make sure only logged in user can rate
You can change several code here. Before fixing your code, I suggest you to user before_action to make DRY code.
class RatingsController < ApplicationController
before_action :authenticate_user!
before_action :get_teacher
# your code #
private
def get_teacher
#teacher = Teacher.find(params[:teacher_id])
end
def rating_params
params.require(:rating).permit(:easiness, :helpfulness, :clarity, :comment, :teacher_id, :school_id)
end
end
Then, add before_action :authenticate_user! above before_action :get_teacher so you can get current_user in each method.
Finally, you have to fix your new and create method into this:
def new
#rating = current_user.ratings.build
end
def create
#rating = current_user.ratings.create(rating_params)
if #rating.save
redirect_to school_teacher_path(#teacher.school, #teacher)
else
render 'new'
end
end
You do not need to #teacher.current_user.ratings.create(rating_params) to get teacher_id because you have teacher_id in your rating_params. I hope this help you.
You need to build rating object for user like this:-
#rating = current_user.ratings.build

How to create another object when creating a Devise User from their registration form in Rails?

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

Resources