Pergunta

I have a simple test as such:

it "should create a post through a user for a blog." do
  @user.blogs.create(title: @blog.title)
  @user.blogs.find_by_title(@blog.title).posts.create(title: 'some title')
  post = Post.find_by_title('some title')
  post.title.should == 'some title'
end

This fails. Why? Because we don't have any blogs for a user. Ok lets throw a binding.pry in their:

it "should create a post through a user for a blog." do
  @user.blogs.create(title: @blog.title)
  binding.pry
  @user.blogs.find_by_title(@blog.title).posts.create(title: 'some title')
  post = Post.find_by_title('some title')
  post.title.should == 'some title'
end

Now that were in the console, lets see if @user.blogs gives us anything.

@user.blogs
=> [#<Blog id: nil, title: "user_blog_26">]

Ok .... But thats not the whole command. Lets see if @blogs has anything.

@blog
=> #<Blog id: 26, title: "user_blog_26">

Ok we are getting some where, we see that @user has the same blog associated to them. (although the id is missing in @user.blogs ... (Note: The relationship between users and blogs is: User has_and_belongs_to_many :blogs, join_table: 'blogs_users')

So lets do:

@user.blogs.find_by(title: @blog.title)
=> nil

Um .....

Whats going on?

  • One: My Blog association for a user doesn't have an id.
  • Two: @user.blogs, returns all blogs for that user, but @user.blogs.find_by(title: @blog.title) returns nil? oO

Whats going on?

Extra Info for the fun of it

Models

class User < ActiveRecord::Base
  include Promiscuous::Subscriber
  subscribe :first_name, :email, :user_name, :last_name

  has_and_belongs_to_many :blogs, join_table: 'blogs_users'
  has_many :posts, through: :blogs

  validates :first_name, presence: true
  validates :email, presence: true, uniqueness: true
  validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i

end


class Blog < ActiveRecord::Base
  has_many :posts

  validates :title, presence: true, uniqueness: true
end
Foi útil?

Solução

@blog
=> #<Blog id: 26, title: "user_blog_26">

@blog has an id assigned to it. So, that means its an existing record in blogs table with id = 26.

@user.blogs.create(title: @blog.title)

This line means that create a new record in blogs table with title same as that of @blog.title (i.e., user_blog_26)

Now, in Blog model you have

validates :title, presence: true, uniqueness: true ## title must be unique

A uniqueness check on the title field. So, obviously @user.blogs.create(title: @blog.title) failed as a record with title user_blog_26 already exists in the blogs table (Recall @blog here). This is the reason why @user.blogs shows a blog with id = nil as the associated blog creation failed.

@user.blogs.find_by(title: @blog.title)
=> nil

As the associated blog with title user_blog_26 was not created for @user. This query returns nil.

Possible Solution

You already have a blog record in the database referred by @blog. To assign that blog to your @user, simply update the example as below:

it "should create a post through a user for a blog." do
  @user.blogs << @blog ## This will set @blog.user_id = @user.id
  @user.blogs.find_by_title(@blog.title).posts.create(title: 'some title')
  post = Post.find_by_title('some title')
  post.title.should == 'some title'
end

Outras dicas

When testing your code, you should have the test isolated form the world - they should not pass or fail because your real database has a row missing, or an unexpected row exist.

For that reason, you should use some kind of method where at the beginning of each test you setup the database to contain only what you need for the test, and at the end of the test you should tear down the database to prepare it for the next test.

There are a few gems out there which can help you with this strategy, such as database cleaner. Use one of those, and your tests will behave in a much more predictable manner.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top