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")
Related
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.
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.
I'm having a lot of trouble understanding sessions and authentication with Rails. So basically, I tried to set a session based on user_id, following a tutorial from a book. Here is the create method in my sessions_controller.rb file:
def create
if user = User.authenticate(params[:email], params[:password])
session[:id] = user.id
redirect_to root_path, :notice => 'Logged in successfully'
else
flash.now[:alert] = "Invalid login/password combination"
render :action => 'new'
end
end
But, when I try to define a current_user method in my application_controller.rb file, it asks me to reference the session based on user_id:
def current_user
return unless session[:user_id]
#current_user = User.find_by_id(session[:user_id])
end
Here is what confuses me - user_id is an attribute of each Recipe (equivalent of articles or posts in my app) that creates a has_one relationship with a user. nowhere else in my application is user_id defined. so user_id shouldn't be an attribute of a User, right?
Just to clarify, I've listed the parameters for User objects and Recipe objects from the rails console:
2.0.0-p598 :019 > Recipe
=> Recipe(id: integer, title: string, body: text, published_at:
datetime, created_at: datetime, updated_at: datetime, user_id:
integer, photo_of_recipe_file_name: string,
photo_of_recipe_content_type: string, photo_of_recipe_file_size:
integer, photo_of_recipe_updated_at: datetime)
2.0.0-p598 :020 > User
=> User(id: integer, email: string, hashed_password: string,
created_at: datetime, updated_at: datetime, username: string)
In your create method it should be
session[:user_id] = user.id
You are setting up a key :user_id in the session hash and storing the authenticated user's id user.id in it for later use
Key name can be anything
Here is my Rspec when testing an API end point related to Users:
context "updating a user" do
let(:user) { User.create! }
it "should let me update a user without an email" do
put "/api/v1/users/#{user.id}", {:user => {:first_name => 'Willy'}}.to_json, {'CONTENT_TYPE' => 'application/json', 'HTTP_AUTHORIZATION' => "Token token=\"#{auth_token.access_token}\""}
p user.inspect
end
And the controller action that I am testing looks like this:
def update
begin
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
p #user.inspect
render json: #user, :except => [:created_at, :updated_at]
else
render json: { :errors => #user.errors }, :status => :unprocessable_entity
end
rescue ActiveRecord::RecordNotFound
head :not_found
end
end
Surprisingly, the #user.inspect in the controller shows this:
"#<User id: 2, first_name: \"Willy\", last_name: nil, email: nil, state: nil, created_at: \"2013-06-22 11:21:22\", updated_at: \"2013-06-22 11:21:22\">"
And the user.inspect in the rspec, right after the call to the controller has been done, looks like this:
"#<User id: 2, first_name: nil, last_name: nil, email: nil, state: nil, created_at: \"2013-06-22 11:21:22\", updated_at: \"2013-06-22 11:21:22\">"
Why does the Rspec not catch the updates? I mean, I have tested this manually and the database gets updated correctly.
What am I missing here?
In rspec example you define user method with let, which returns ActiveRecord object. Your controller is creating different object, that points to the same database entry. Change in db is not reflected in user object in rspec example, as there is no callback mechanism that would notify it to change.
Using #reload method on AR object in test should solve your problem, as it forces reloading data from db.
So it seems that I must be doing this wrong.
Task.create :name => 'apples'
(0.2ms) begin transaction
(0.2ms) rollback transaction
=> #<Task id: nil, name: "apples", task: nil, created_at: nil, updated_at: nil>
Then I thought, maybe my controller is wrong:
def create
Task.create(params[:task])
redirect_to tasks_path, :flash => {:success => 'We have created the task.'}
end
because it seems that my tests, using capybara, are failing - because they can't create a task.....
thoughts?
You can't save a Rails model to the database if it has a validation which fails, or a before_save callback which returns false