- Read Tutorial
- Watch Guide Video
In this guide we're going to implement a connection between two models in Rails and we're going to use TDD to build the feature.
For this guide, we need to implement some test helpers, and we are going to add them to our Rails helper file. Let's open the rails_helper.rb
, and add these helpers:
# spec/rails_helper.rb include Warden::Test::Helpers Warden.test_mode!
We are using these helpers to mimic the process of a user signing in, as this is outside the scope of Capybara.
Now, open the /spec/features/post_spec.rb
file. As you'll see, we already have the ability to visit a new post in our before
block, so let's add a user to it and leverage the login_as
method to log a user in.
# spec/features/post_spec.rb describe 'creation' do before do user = User.create(email: "test@test.com", password: "asdfasdf", password_confirmation: "asdfasdf", first_name: "Jon", last_name: "Snow") login_as(user, :scope => :user) visit new_post_path end # Other methods not shown... end
Essentially, before each new post, this code is going to log in a user and mimic a user session.
Next, we'll create a test to see if a user is associated with the post. The code for that is the following:
# spec/features/post_spec.rb it 'will have a user associated it' do fill_in 'post[date]', with: Date.today fill_in 'post[rationale]', with: "User Association" click_on "Save" expect(User.last.posts.last.rationale).to eq("User Association") end
Let's see what happens when you run rspec
.
To get this test passing, open the models/post.rb
file, and add a belongs_to
call, like this:
# app/models/post.rb class Post < ActiveRecord::Base belongs_to :user validates_presence_of :date, :rationale end
Now, go to user.rb
, and update the class definition:
# app/models/user.rb class User < ActiveRecord::Base has_many :posts # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable validates_presence_of :first_name, :last_name end
All we're doing here is adding the code has_many :posts
right below where we're declaring the class.
If you run rspec
, it'll say that a column called posts.user_id
does not exist. Let's take a look at our database schema. In the posts
table, we need a new column called user_id
. To make this change, first run:
bundle exec rake db:setup
To clear all the bad data from the database. Then, run:
rails g migration add_users_to_posts user:references
This code will not only create a user_id
in the posts
table, but also an index and foreign key relation.
To create the column in database, run the command:
bundle exec rake db:migrate
Now, if you open the db/schema.rb
file, you should see the user_id
column.
If you run rspec
, it just says that there is an undefined method called rationale
.
To fix this, update the posts_controller.rb
file. Since we are using devise
, we have access to a method called current_user
. When a user is logged in, we have their details stored inside a cookie. So, all that we have to do is pass the id
of the current user to the user_id
column of post
.
# app/controllers/posts_controller.rb def create @post = Post.new(post_params) @post.user_id = current_user.id # this is the new line added if @post.save redirect_to @post, notice: 'Your post was created successfully' else render :new end end
Run rspec
now, and everything is working fine.
Let's also check if it works in the browser. Open the url localhost:3000/posts/new
, enter the values and click on save. This throws an error.
This is because we haven't signed in yet. So, let's register again. Open the url localhost:3000/users/sign_up
and sign up.
If you click the sign up button, it'll throw another error because first name and last name were made mandatory in our code, but there are no fields in the form. You can actually fix this in the terminal. Open the rails console, and create a user manually like this:
User.create!(first_name: "Jordan", last_name: "Hudgens", email: "asdf@asdf.com", password: "asdfasdf", password_confirmation: "asdfasdf")
Next, restart the server and open the localhost:3000/users/sign_in
page. Enter the email
and password
we created in the console and log in. Now, go to localhost:3000/posts/new
and create a new post. This should bring up the below screen:
As you'll see, a user_id
is printed for you. So, everything is working properly.
To prevent this error in the future, let's open our application_controller.rb
file, and have a before_action
for authentication, like this:
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception before_action :authenticate_user! end
Now, if a user tries to access any page without signing in, it will redirect them to the sign in page which is exactly what you want in an enterprise application.