Rspec - test model that has two associations two the same column - ruby-on-rails

In my app I created friend list for User. Inside Friend model I have validations for unique connection between users. Everything works good, but I don't know how to write tests for this model. It's looks like that:
Friend.rb
class Friend < ApplicationRecord
belongs_to :user1, :class_name => 'User'
belongs_to :user2, :class_name => 'User'
validate :uniqueness_of_users_associations, :cant_be_friend_with_yourself
def uniqueness_of_users_associations
unless (user1.friends.where(user2: user2) + user1.is_friend.where(user1: user2)).blank?
errors.add(:friend, 'He is already your friend')
end
end
def cant_be_friend_with_yourself
errors.add(:friend, "You can't be friend with yourself") if user1 == user2
end
end
User.rb:
class User < ActiveRecord::Base
extend Devise::Models
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
include DeviseTokenAuth::Concerns::User
has_many :friends, :class_name => 'Friend', :foreign_key => 'user1', dependent: :destroy
has_many :is_friend, :class_name => 'Friend', :foreign_key => 'user2', dependent: :destroy
end
spec/factories/friends.rb :
FactoryBot.define do
factory :friend do
association :user1, factory: :user
association :user2, factory: :user
confirmed { true }
end
end
friend_spec.rb :
RSpec.describe Friend, type: :model do
describe 'relationships' do
it { is_expected.to belong_to(:user1).class_name('User') }
it { is_expected.to belong_to(:user2).class_name('User') }
end
end
When I try to run test I get error:
Failure/Error: unless (user1.friends.where(user2: user2) + user1.is_friend.where(user1: user2)).blank?
NoMethodError:
undefined method `friends' for nil:NilClass
Why I get nil inside model? I did something wrong inside factory?

What you actually want to find if Users 1 & 2 are friends is this query:
EXISTS (
SELECT 1
FROM friends
WHERE
friends.user1_id = 1 OR friends.user2_id = 1
AND
friends.user1_id = 2 OR friends.user2_id = 2
)
class Friend < ApplicationRecord
validate :uniqueness_of_users_associations, :cant_be_friend_with_yourself
def uniqueness_of_users_associations
if Friend.between(user1.id, user2.id).exists?
errors.add(:base, 'Friendship already exists')
end
end
def self.between(a, b)
user1_id, user2_id = arel_table[:user1_id], arel_table[:user2_id]
where(user1_id.eq(a).or(user2_id.eq(a)))
.where(user1_id.eq(b).or(user2_id.eq(b)))
end
# ...
end
However the naming here is super off. The model should be named Friendship as friend actually means the person that you are friends with.

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

Factory Girl rspec issue: has_many through factory

I am having an issue regarding Factory Girl in Rails. I currently have pundit setup am trying to test my user policies yet the factory does not seem to work when in rspec.
Inside the rails console, I can load up the console and type:
user=FactoryGirl.create(:admin_user)
r=user.roles
This works correctly creates a user and the correct associations between a user and a role. However, when the factory is used in rspec- A user is created but not the associated assignment. (I discovered this by using 'pp' inside the specific tests.)
I do not need to create a role, since the roles are set so I am looking them up.
Any ideas?
Models
class User < ApplicationRecord
has_many :assignments, dependent: :destroy
has_many :roles, through: :assignments, dependent: :destroy
def has_role?(roles)
roles.each do |role|
if self.roles.include? role
return true
end
end
false
end
class Role < ApplicationRecord
# Associations
has_many :assignments
has_many :users, through: :assignments
class Assignment < ApplicationRecord
# Associations
belongs_to :user
belongs_to :role
Factories
FactoryGirl.define do
factory :user do
first_name {Faker::Name.first_name}
last_name {Faker::Name.last_name}
email {Faker::Internet.email}
password {Faker::Internet.password(8)}
factory :admin_user do
after(:create) do |user|
Assignment.create(user: user , role: Role.find_by(label:'System Admin') )
end
end
end
end
Tests
User Policy Test
describe UserPolicy do
subject { UserPolicy }
let (:current_user) { FactoryGirl.build_stubbed :user}
let (:other_user) { FactoryGirl.build_stubbed :user }
let (:admin) { FactoryGirl.build_stubbed :admin_user}
permissions :index? do
it "denies access if not an admin" do
expect(UserPolicy).not_to permit(current_user)
end
it "allows access for an admin" do
expect(UserPolicy).to permit(admin)
end
end
end
Other Test With same Issue
feature 'User index page', :devise do
after(:each) do
Warden.test_reset!
end
scenario 'user sees own email address' do
user = FactoryGirl.create(:admin_user)
expect(user.has_role?(Role.where(label: 'System Admin'))).to eq true
login_as(user, scope: :user)
visit users_path
expect(page).to have_content user.email
end
end
This test fails since the user has no role assigned.
Controller
class AssignmentsController < ApplicationController
def create
#assignment = Assignment.new(assignment_params)
if #assignment.save
redirect_to users_path(), :notice => "Role Added"
else
flash[:alert]="Unable to Add Role"
end
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

Factory_girl create gives validation error

I'm trying to test a destroy action in my rails application. I use Factory_girl to create objects.
When creating a company object it's mandatory to add a user relation. But this is where I get a fail when I try to create a Company with Factory_girl.
user_spec.rb
describe User do
before(:each) { #user = User.new(email: 'user#example.com') }
subject { #user }
it { should respond_to(:email) }
it { should respond_to(:companies) }
it "#email returns a string" do
expect(#user.email).to match 'user#example.com'
end
describe "company associations" do
let!(:a_company) do
FactoryGirl.create(:company, user: #user)
end
it {should have_many :companies, :dependent => :destroy}
end
end
factory.rb
FactoryGirl.define do
factory :user do
confirmed_at Time.now
name "Test User"
email "test#example.com"
password "please123"
trait :admin do
role 'admin'
end
end
factory :company do
name "Total Inc."
user :user
end
end
model/user.rb
class User < ActiveRecord::Base
has_many :companies, dependent: :destroy
enum role: [:user, :vip, :admin]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :user
end
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
end
model/company.rb
class Company < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
end
The error I get:
Failures:
1) User company associations
Failure/Error: FactoryGirl.create(:company, user: #user)
ActiveRecord::RecordInvalid:
Validation failed: User can't be blank
EDIT
I followed the advise from below and now I can create but get following error:
Failure/Error: it {should have_many :companies, :dependent => :destroy} expected #<User:0x007fc7b7ce08c0> to respond to has_many?
The first error, Validation failed: User can't be blank, is a result of not having saved the #user object. You can fix the error by saving the object before you call create:
let!(:a_company) do
#user.save
FactoryGirl.create(:company, user: #user)
end
The second error, Failure/Error: it {should have_many :companies, :dependent => :destroy} expected #<User:0x007fc7b7ce08c0> to respond to has_many? is a testing error - the production code works fine. To fix your test try one of the following options:
Use Shoulda
it {should have_many(:companies).dependent(:destroy)}
Use FactoryGirl
it 'Expects user to have many companies' do
expect{#user.companies}.to_not raise_error
end

Error self.user.update_attribute undefined method nil:Class

UPDATE POST I'm noob in RoR and i start the test. I have an application and I try to use test with rspec and capybara, I want create user and test the login. But when i do my test i have some error with my models users, because in my app I create user and i call an after_create :create_order
I modify my factories.rb but i have an error in my update_attributes
See my model user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
has_many :ratings
has_many :rated_sounds, :through => :ratings, :source => :sounds
has_many :sounds ,:dependent => :destroy
has_many :orders ,:dependent => :destroy
has_many :song_ups ,:dependent => :destroy
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:registerable, :confirmable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me,
:nom, :prenom, :societe, :tel, :cgu, :sign_in_count, :trans_simu,
:trans_limit, :project, :vat_number, :adress, :zip_code, :city, :tutorial
after_create :create_order
def create_order
order = Order.create(user_id: self.id,files_left: 3)
order.subscription = Subscription.where(category: 'test').first || Subscription.create(category: 'test')
self.update_attribute(:trans_limit, 1)
#Ancien Order
# Order.create(user_id: self.id, subscription_id: Subscription.where(category: 'test').first.id, files_left: 3)
# self.update_attribute(:trans_limit, 1)
end
def test?
self.orders.trial.present? && self.orders.count==1
end
def unlimited?
!self.test? && self.orders.current.where(time_left: -1).any?
end
def allow_send?
!self.finish_order? && self.sounds.in_progress.count < self.trans_limit.to_i
end
def finish_order?
self.orders.current.empty?
end
end
For create my user in my test I use FactoryGirl. And i write this :
require 'factory_girl'
FactoryGirl.define do
factory :user do
sequence(:email) {|n| "email#{n}#factory.com" }
password "foobar"
password_confirmation "foobar"
societe "RspecTest"
prenom "John"
nom "Doe"
tel "0101010101"
confirmed_at Time.now
association :order
end
factory :order do
association :subscription
end
factory :subscription do
end
end
And one of my test is :
scenario "User login right" do
visit new_user_session_path
current_path.should == "/users/sign_in"
page.html.should include('<h2>Se connecter</h2>')
user = FactoryGirl.create(:user)
fill_in "Email", :with => user.email
fill_in "Mot de passe", :with => user.password
check "user_remember_me"
click_button "Connexion"
page.should have_content('Mon compte')
current_path.should == root_path
end
My order.rb
class Order < ActiveRecord::Base
attr_accessible :nb_files, :user_id, :period, :time_left, :subscription_id, :promo_id, :promo_end_date, :max_time_file, :files_left, :ended_at
belongs_to :user
belongs_to :subscription
scope :current, where('files_left != ? AND time_left != ? AND (ended_at >= ? OR ended_at IS ?)', 0, 0, Time.now, nil)
before_create :init_value
def self.trial
self.where(subscription_id: Subscription.where(category: 'test').first.id).first
end
def init_value
self.time_left = self.subscription.trans_seconds
self.max_time_file = self.subscription.max_time_file
if self.subscription.category != 'test'
self.user.update_attribute(:trans_limit, 1)
Order.where(user_id: self.user_id, subscription_id: Subscription.where(category: 'test')).destroy_all
else
self.files_left = 3
end
end
end
My error :
Failure/Error: user = FactoryGirl.create(:user)
NoMethodError:
undefined method `trans_seconds' for nil:NilClass
# ./app/models/order.rb:13:in `init_value'
# ./app/models/user.rb:21:in `create_order'
I hope you can help me. Thank's
You don't have Subscription with category 'test' in your database. Solution depend on how you want to handle this kind of error.
If you expect this subscription always to be in your database, use db:seed rake task for prepopulating your db. (try googling it to find out how to do this)
If you don't want to assign any subscription if given doesn't exist try:
def create_order
order = Order.create(user_id: self.id,files_left: 3)
order.subscription = Subscription.where(category: 'test').first
self.update_attribute(:trans_limit, 1)
end
And finally, if you want to create such a subscription if it doesn't exist:
def create_order
order = Order.create(user_id: self.id,files_left: 3)
order.subscription = Subscription.find_or_create_by_category('test')
self.update_attribute(:trans_limit, 1)
end
In create_order try using this:
Order.create!(user: self, subscription: Subscription.where(category: 'test').first, files_left: 3)
Use objects instead of plain ids.
On before_* methods
To make sure that validations pass, return true at the end of these methods. In your case, add return true at the end of the init_value method.

Resources