'let' doesn't memoize values in rsepc - ruby-on-rails

I have a user model, who have a many-to-many relationship whit itself: user A add user B as a friend, and automatically, user B becomes friend of user A too.
Performing the following steps in the rails console:
1) Create two users and save them:
2.3.1 :002 > u1 = User.new(name: "u1", email: "u1#mail.com")
=> #<User _id: 5788eae90640fd10cc85f291, created_at: nil, updated_at: nil, friend_ids: nil, name: "u1", email: "u1#mail.com">
2.3.1 :003 > u1.save
=> true
2.3.1 :004 > u2 = User.new(name: "u2", email: "u2#mail.com")
=> #<User _id: 5788eaf80640fd10cc85f292, created_at: nil, updated_at: nil, friend_ids: nil, name: "u2", email: "u2#mail.com">
2.3.1 :005 > u2.save
=> true
2) Add user u2 as friend of u1:
2.3.1 :006 > u1.add_friend u2
=> [#<User _id: 5788eaf80640fd10cc85f292, created_at: 2016-07-15 13:54:04 UTC, updated_at: 2016-07-15 13:55:19 UTC, friend_ids: [BSON::ObjectId('5788eae90640fd10cc85f291')], name: "u2", email: "u2#mail.com">]
3) Check their friendship:
2.3.1 :007 > u1.friend? u2
=> true
2.3.1 :008 > u2.friend? u1
=> true
As we can see, the "mutual friendship" works. But in my tests that doesn't happen. Here are my tests:
require 'rails_helper'
RSpec.describe User, type: :model do
let(:user) { create(:user) }
let(:other_user) { create(:user) }
context "when add a friend" do
it "should put him in friend's list" do
user.add_friend(other_user)
expect(user.friend? other_user).to be_truthy
end
it "should create a friendship" do
expect(other_user.friend? user).to be_truthy
end
end
end
Here are the tests result:
Failed examples:
rspec ./spec/models/user_spec.rb:33 # User when add a friend should create a friendship
The only reason that I can see to the second test is failing is because my let is not memoizing the association to use in other tests. What am I doing wrong?
Here is my User model, for reference:
class User
include Mongoid::Document
include Mongoid::Timestamps
has_many :posts
has_and_belongs_to_many :friends, class_name: "User",
inverse_of: :friends, dependent: :nullify
field :name, type: String
field :email, type: String
validates :name, presence: true
validates :email, presence: true
index({ email: 1 })
def friend?(user)
friends.include?(user)
end
def add_friend(user)
friends << user
end
def remove_friend(user)
friends.delete(user)
end
end

You need to move the creation of the relationship into a before block:
context "when add a friend" do
before do
user.add_friend(other_user)
end
it "should put him in friend's list" do
expect(user.friend? other_user).to be_truthy
end
it "should create a friendship" do
expect(other_user.friend? user).to be_truthy
end
end
In your code, you are only running it within the first it block, to the second one starts from scratch and it's not run.
With the before block, it is run once before each of the it blocks, so the spec should pass then.

Related

Unique email address on User model unit test failing

I have a user model that expects an email address to be unique however the models spec is failing:
spec/user_spec.rb
it "has a unique email" do
user1 = build(:user, email: "example#email.com")
user2 = build(:user, email: "example#email.com")
expect(user2).to_not be_valid
end
app/model/user.rb
class User < ApplicationRecord
validates :email, format: {with: URI::MailTo::EMAIL_REGEXP}, presence: true, uniqueness: true
end
User has a unique email
Failure/Error: expect(user2).to_not be_valid
expected #<User id: nil, email: "example#email.com", created_at: nil, updated_at: nil> not to be valid
The uniqueness validation happens by performing an SQL query into the model's table, searching for an existing record with the same value in that attribute.
So, you just build two users in memory, neither of them is saved to database. Save the user1 to database, and then validate user2.
A possible change to your test may look like this:
it "has a unique email" do
user1 = create(:user, email: "example#email.com")
user2 = build(:user, email: "example#email.com")
expect(user2).to_not be_valid
end

factory_girl factories with associations not assigning IDs correctly

I have 3 models that associate like so:
#user.rb
has_many :forums
has_many :posts
#forum.rb
belongs_to :user
has_many :posts
#post.rb
belongs_to :user
belongs_to :forum
I'm trying to create a single set of factories that all share the needed IDs needed to be associated with each other.
#User factory
FactoryGirl.define do
factory :user do
sequence :email do |n|
"testuser#{n}#postfactory.com"
end
password "password#1"
end
end
#Forum factory
FactoryGirl.define do
factory :forum do
user
name "Forum Name"
description "Forum Description with a minimum character count of 20"
end
end
#Post factory
FactoryGirl.define do
factory :post do
user
forum
title 'Post 1'
description 'This is a test description for Post 1'
end
end
When I run my spec test with:
user = FactoryGirl.create(:user)
forum = FactoryGirl.create(:forum)
post = FactoryGirl.create(:post)
It outputs the following in the console:
#<User id: 1, email: "testuser1#userfactory.com", created_at: "2016-10-27 20:10:36", updated_at: "2016-10-27 20:10:36">
#<Forum id: 1, name: "Forum Name", description: "Forum Description with a minimum character count o...", user_id: 2, created_at: "2016-10-27 20:10:36", updated_at: "2016-10-27 20:10:36">
#<Post id: 1, title: "Post 1", description: "This is a test description for Post 1", user_id: 3, forum_id: 2, created_at: "2016-10-27 20:10:36", updated_at: "2016-10-27 20:10:36">
As you can see, the user_id increments with each factory being created as well as forum_id. I would like these to all have the ID of 1 without having to do some manual work. What have I done incorrectly with my setup
Edit: I sort of see what I'm doing incorrectly. I only need to generate a post in my spec test and it will generate the factories needed (forum and user) to create the post. However, I do notice that I'm generating two users.
(byebug) User.count
2
(byebug) User.first
#<User id: 1, email: "testuser1#postfactory.com", created_at: "2016-10-27 20:30:33", updated_at: "2016-10-27 20:30:33">
(byebug) User.last
#<User id: 2, email: "testuser2#postfactory.com", created_at: "2016-10-27 20:30:33", updated_at: "2016-10-27 20:30:33">
Any idea why that is? I tried removing the sequence :email part and doing it standard. However, I get a validation error that the email has already been taken. For some reason, it's trying to run the user factory twice even though I call it only once in my spec test.
Every time you call FactoryGirl.create, there is a new created user, so after you run this code:
user = FactoryGirl.create(:user)
forum = FactoryGirl.create(:forum)
post = FactoryGirl.create(:post)
actually you created 3 users, as you can see post has user_id: 3.
If you want to create forum and post with user you created, you can assign that user to forum and post when they are created:
user = FactoryGirl.create(:user)
forum = FactoryGirl.create(:forum, user: user)
post = FactoryGirl.create(:post, user: user, forum: forum)
With this code, there is only one created user.

RSpec NoMethodError when calling should_not_be_valid on FactoryGirl.build

In my RSpec user_spec.rb one of my FactoryGirl factories seems valid and I can call should be_valid on it, and this test will pass (and will break if I put an empty string in the factory user_name).
describe User do
it "has a valid factory" do
# should be_valid works fine
FactoryGirl.create(:user).should be_valid
end
it "is invalid without a name" do
# should_not_be_valid throws a NoMethodError
FactoryGirl.build(:user, :user_name => nil).should_not_be_valid
end
end
However when I call the above should_not_be_valid on FactoryGirl.build, the test fails:
1) User is invalid without a name
Failure/Error: FactoryGirl.build(:user, :user_name => nil).should_not_be_valid
NoMethodError:
undefined method `should_not_be_valid' for #<User id: nil, user_name: nil, created_at: nil, updated_at: nil>
# ./spec/models/use
When it should instead pass, since a blank user_name is invalid. Here is what I have in my factories.rb:
FactoryGirl.define do
factory :user do
user_name "Foo Bar"
end
end
And my User model:
class User < ActiveRecord::Base
has_one :musician
validates :user_name, presence: true
end
How could should be_valid be working, but should_not_be_valid throws a NoMethodError?
I would really appreciate any help! I am running Rails 4, gem "rspec-rails", "~> 2.14.1", and gem 'factory_girl_rails', '4.3.0'
Been trying to puzzle this one out for awhile with no success...
Use space between should_not and be_valid:
FactoryGirl.build(:user, :user_name => nil).should_not be_valid

How to create a new Post through a User?

I have the following User and Post relationships:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
has_many :posts
end
class Post < ActiveRecord::Base
attr_accessible :content
belongs_to :user
end
I am trying to create a new Post through a User, but I get an error. I am not sure why:
1.9.3-p392 :001 > #user = User.where(:id => 1)
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1
=> [#<User id: 1, email: "test#test.com", encrypted_password: "$2a$10$ltpBpN0gMzOGULVtMxqgueQHat1DLkY.Ino3E1QoO2nI...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 6, current_sign_in_at: "2013-03-04 05:33:46", last_sign_in_at: "2013-03-03 22:18:17", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", created_at: "2013-03-02 03:41:48", updated_at: "2013-03-04 05:33:46", avatar_file_name: nil, avatar_content_type: nil, avatar_file_size: nil, avatar_updated_at: nil>]
1.9.3-p392 :002 > #user.posts.create(:content => "This is the content")
NoMethodError: undefined method `posts' for #<ActiveRecord::Relation:0x000000024ca868>
There is a difference between where and find in the ActiveRecord Relationships.
The query:
#user = User.where(:id => 1) is giving your the array hash.
So when you do something like #user.posts for the above query, it gives error of NoMethodError on ActiveRecord::Relation as there is no such post associated with this hash. So in order to convert it into the user whose id is 1, you do like this:
#user = User.where(:id => 1).first
or
#user = User.find(:id => 1)
Both will give you the user whose id is 1 (only one record) and then you can use this:
#user.posts
The above will give the associated posts of user with id 1.
Then you can do:
#user.posts.create(:content => "Post of user 1")
So what you are trying to do is actually giving you the hash (set of users) but actually you want only one user to create the relevant post.
Also, See the difference between find and where.
Your code
User.where(:id => 1)
does not give you a model instance, but a relation. Hence the NoMethodError on ActiveRecord::Relation.
Change your first line to
User.find(1)
and you're fine.
Use this
#user = User.where(:id => 1).shift
#user.posts.create(:content => "This is the content")
OR
#user = User.find(1)
#user.posts.create(:content => "This is the content")

rails 3 tutorial : rspec + factory_girl_rails problem

i've been following the Rails tutorial (http://railstutorial.org/chapters/beginning , Rails 3 version), and i've stopped at 11th chapter when using Factory Girl and Rspec, I have a test that isn't passing and I feel I'm doing something wrong but I don't see what.
First of all there is a git repository on Github with the code that doesn't pass that test.
http://github.com/Monomachus/ch3_static_pages
So I got users model
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
has_many :microposts
.
.
.
I got microposts model
class Micropost < ActiveRecord::Base
attr_accessible :content
belongs_to :user
default_scope :order => 'microposts.created_at DESC'
end
Then I got Factory girl settings
Factory.define :user do |user|
user.name "Michael Hartl"
user.email "mhartl#example.com"
user.password "foobar"
user.password_confirmation "foobar"
end
Factory.define :micropost do |micropost|
micropost.content "Foo bar"
micropost.association :user
end
And finally Rspec code
require 'spec_helper'
describe Micropost do
.
.
describe "microposts associations" do
before(:each) do
#user = User.create(#attr)
#mp1 = Factory(:micropost, :user => #user, :created_at => 1.day.ago)
#mp2 = Factory(:micropost, :user => #user, :created_at => 1.hour.ago)
end
it "should have a microposts attribute" do
#user.should respond_to(:microposts)
end
it "should be in the reverse order of appearing" do
#user.microposts.should == [#mp2, #mp1]
end
end
end
And I got the error which definitely tells me that I do something wrong.
Failures:
1) Micropost microposts associations should be in the reverse order of appearing
Failure/Error: #user.microposts.should == [#mp2, #mp1]
expected: [#<Micropost id: 2, content: "Foo bar", user_id: nil, created_at: "2010-12-24 12:47:02", update
d_at: "2010-12-24 13:47:02">, #<Micropost id: 1, content: "Foo bar", user_id: nil, created_at: "2010-12-23 13:
47:02", updated_at: "2010-12-24 13:47:02">],
got: [] (using ==)
Diff:
## -1,3 +1,2 ##
-[#<Micropost id: 2, content: "Foo bar", user_id: nil, created_at: "2010-12-24 12:47:02", updated_at: "20
10-12-24 13:47:02">,
- #<Micropost id: 1, content: "Foo bar", user_id: nil, created_at: "2010-12-23 13:47:02", updated_at: "20
10-12-24 13:47:02">]
+[]
# ./spec/models/micropost_spec.rb:42:in `block (3 levels) in <top (required)>'
As you can see even the user_id property is not set correctly +
apparently #user.microposts doesn't have any elements.
Please help me with this issue thanks.
Well the answer was simple :)
I included microposts associations in the Micropost spec.
And clearly
describe "microposts associations" do
before(:each) do
#user = User.create(#attr)
#mp1 = Factory(:micropost, :user => #user, :created_at => 1.day.ago)
#mp2 = Factory(:micropost, :user => #user, :created_at => 1.hour.ago)
end
it "should have a microposts attribute" do
#user.should respond_to(:microposts)
end
it "should be in the reverse order of appearing" do
#user.microposts.should == [#mp2, #mp1]
end
end
#attr did not contain the user properties but the micropost properties and of course #user = nil and then everything makes sense. So if you do have the same problem, include this code into User spec.
Now all my tests pass :)
By the time I had finished the pagination chapter, the tutorial was creating 100 sample users using Faker (listing 10.25 on page 390), and in RubyMine I was able to see my test was failing because the program was throwing an exception on duplicate user email address (which has a unique constraint). The #attr on line 8 of user_spec.rb has :email => "user#example.com", however this throws an exception since it's a duplicate email (I guess because Faker has already created it).
For me the fix was to copy #attr from line 8 and paste it into the describe "micropost associations" block (user_spec.rb), and change the email address to :email => "user#example999.com". I'm sure this is a total hack but I'm a n00b.
Update:
Another fix for me was to comment out the line #user = User.create(#attr), and simply create #mp1 and #mp2.
I was also getting test failure in this section, even though I already had "micropost associations" in user_spec.rb. Turns out I needed to restart spork and autotest in order to get them to use the new "micropost" factory in factories.rb.

Resources