For the time being, I was able to confirm that create and read work last time. From here, follow this procedure
In this article, I will implement 1 and 2 for the time being, and after 3. I will proceed with the next and subsequent articles.
Write the documentation Exclude errors that also appear in the migration file.
.rubocop.yml can be excluded in this way, but if you exclude what you originally need, it will break the meaning of observing the coding rules in the first place, so let's discuss it firmly when adding it in team development. ..
diff:.rubocop.yml
+ #documentation
+ Style/Documentation:
+ Exclude:
+ - "db/migrate/**/*"
...
Like Test Driven Development (TDD), the first is Red's test. Since validation is not implemented, I will make something that will be Red even if I write a test and run it.
I will write it in a normal Rails tick without using factory_bot once.
spec/models/post_spec.rb
# frozen_string_literal: true
require "rails_helper"
RSpec.describe Post, type: :model do
describe "subject" do
context "When blank" do
it "Become invalid" do
post = Post.new(subject: "", body: "fuga")
expect(post).not_to be_valid
end
end
end
end
So the above code is testing that "it becomes invalid when subject is blank". But so far I haven't validated the subject of the post model so the post is valid and the "invalid" test will fail.
$ rspec spec/models/post_spec.rb
...
Finished in 0.07805 seconds (files took 3.53 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/models/post_spec.rb:8 #Invalid when Post subject blank
ec2-user:~/environment/bbs (master) $ rspec
Let's try to register even if the subject is empty.
$ rails c
[1] pry(main)> Post.create!(subject: "", body: "hoge")
(0.1ms) BEGIN
Post Create (2.5ms) INSERT INTO "posts" ("subject", "body", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["subject", ""], ["body", "hoge"], ["created_at", "2020-09-06 01:07:52.628768"], ["updated_at", "2020-09-06 01:07:52.628768"]]
(0.9ms) COMMIT
=> #<Post:0x0000000005760700
id: 2,
subject: "",
body: "hoge",
created_at: Sun, 06 Sep 2020 01:07:52 UTC +00:00,
updated_at: Sun, 06 Sep 2020 01:07:52 UTC +00:00>
You have saved it.
By the way, without using describe or context
spec/models/post_spec.rb
# frozen_string_literal: true
require "rails_helper"
RSpec.describe Post, type: :model do
it "Invalid when subject is blank" do
post = Post.new(subject: "", body: "fuga")
expect(post).not_to be_valid
end
end
The behavior of the test code is almost the same. This is because you can test by writing expect in the it block. However, it becomes difficult to understand when describing the same column and the same validation conditions by grouping, so basically it is recommended to describe by nesting describe and context.
app/models/post.rb
class Post < ApplicationRecord
+ validates :subject, presence: true
end
Now you can't register with true, that is, blank for presence for the subject column.
Let's try it.
$ rails c
[1] pry(main)> Post.create!(subject: "", body: "hoge")
ActiveRecord::RecordInvalid: Validation failed: Subject can't be blank
from /home/ec2-user/.rvm/gems/ruby-2.7.1/gems/activerecord-6.0.3.2/lib/active_record/validations.rb:80:in `raise_validation_error'
I can't register.
$ rspec ./spec/models/post_spec.rb
...
Finished in 0.05053 seconds (files took 1.63 seconds to load)
1 example, 0 failures
The test also passed.
Since it is a problem if the number of characters can be registered infinitely, we will add a limit. This is also from the test first. I will write a test with a plan to add validation that it is OK if it is 30 characters or less and NG if it is 31 characters or more.
spec/models/post_spec.rb
expect(post).not_to be_valid
end
end
+ context "by maxlength" do
+ context "For 30 characters" do
+ it "Become valid" do
+ post = Post.new(subject: "Ah" * 30, body: "fuga")
+ expect(post).to be_valid
+ end
+ end
+ context "For 31 characters" do
+ it "Become invalid" do
+ post = Post.new(subject: "Ah" * 31, body: "fuga")
+ expect(post).not_to be_valid
+ end
+ end
+ end
end
end
Let's run the test.
$ rspec ./spec/models/post_spec.rb
...
Finished in 0.03204 seconds (files took 1.42 seconds to load)
3 examples, 1 failure
Failed examples:
rspec ./spec/models/post_spec.rb:21 #Post subject maxlength makes it invalid for 31 characters
I haven't added validation yet, so 30 characters will pass but 31 characters will be moss. Add validation to model.
app/models/post.rb
class Post < ApplicationRecord
- validates :subject, presence: true
+ validates :subject, presence: true, length: { maximum: 30 }
end
$ rspec ./spec/models/post_spec.rb
...
Finished in 0.02201 seconds (files took 1.4 seconds to load)
3 examples, 0 failures
You passed the test.
This will result in an error at 31 characters. Try it with rails c
.
For example, in the test code above
post = Post.new(subject: "Ah" * 30, body: "fuga")
However, it is troublesome to specify the body every time. If it's 2 columns, it's still okay, but if it exceeds 10 columns, the code will be unnecessarily long. At that time, use factoryBot.
The factoryBot looks under spec / factories /. This time, there is no need to change the initial value when the model was created, but let's take a look at the contents.
spec/factories/posts.rb
# frozen_string_literal: true
FactoryBot.define do
factory :post do
subject { "MyString" }
body { "MyText" }
end
end
Edit the post_spec.rb file.
spec/models/post_spec.rb
describe "subject" do
context "When blank" do
it "Become invalid" do
- post = Post.new(subject: "", body: "fuga")
+ post = build(:post, subject: "")
expect(post).not_to be_valid
end
end
context "by maxlength" do
context "For 30 characters" do
it "Become valid" do
- post = Post.new(subject: "Ah" * 30, body: "fuga")
+ post = build(:post, subject: "Ah" * 30)
expect(post).to be_valid
end
end
context "For 31 characters" do
it "Become invalid" do
- post = Post.new(subject: "Ah" * 31, body: "fuga")
+ post = build(:post, subject: "Ah" * 31)
expect(post).not_to be_valid
end
end
build is the equivalent of .new
using factoryBot. It is not saved to the database.
In this case, subject is specified, but body is not specified, so " MyText "
is entered in the body of factoryBot.
Also, run a test for each change to make sure it's OK.
For the time being, try changing it as follows.
spec/models/post_spec.rb
RSpec.describe Post, type: :model do
describe "subject" do
context "When blank" do
+ let(:post) do
+ build(:post, subject: "")
+ end
it "Become invalid" do
- post = build(:post, subject: "")
expect(post).not_to be_valid
end
end
context "by maxlength" do
context "For 30 characters" do
+ let(:post) do
+ build(:post, subject: "Ah" * 30)
+ end
it "Become valid" do
- post = build(:post, subject: "Ah" * 30)
expect(post).to be_valid
end
end
context "For 31 characters" do
+ let(:post) do
+ build(:post, subject: "Ah" * 31)
+ end
it "Become invalid" do
- post = build(:post, subject: "Ah" * 31)
expect(post).not_to be_valid
end
end
let is a variable that is limited to the scope within the same describe or context block. In Ruby, the last evaluated expression is the return value, so
let(:post) do
build(:post, subject: "Ah" * 31)
end
In the case of, the post of the build execution result becomes a variable called post by let (: post)
.
Let's implement the required limit / 100 character limit test and validation on the body.
spec/models/post_spec.rb
# frozen_string_literal: true
require "rails_helper"
RSpec.describe Post, type: :model do
describe "subject" do
context "When blank" do
let(:post) do
build(:post, subject: "")
end
it "Become invalid" do
expect(post).not_to be_valid
end
end
context "by maxlength" do
context "For 30 characters" do
let(:post) do
build(:post, subject: "Ah" * 30)
end
it "Become valid" do
expect(post).to be_valid
end
end
context "For 31 characters" do
let(:post) do
build(:post, subject: "Ah" * 31)
end
it "Become invalid" do
expect(post).not_to be_valid
end
end
end
end
describe "body" do
context "When blank" do
let(:post) do
build(:post, body: "")
end
it "Become invalid" do
expect(post).not_to be_valid
end
end
context "by maxlength" do
context "For 100 characters" do
let(:post) do
build(:post, body: "Ah" * 100)
end
it "Become valid" do
expect(post).to be_valid
end
end
context "For 101 characters" do
let(:post) do
build(:post, body: "Ah" * 101)
end
it "Become invalid" do
expect(post).not_to be_valid
end
end
end
end
end
If you run rspec at this point, it will be moss
app/models/post.rb
# frozen_string_literal: true
#
#Post class
#
class Post < ApplicationRecord
validates :subject, presence: true, length: { maximum: 30 }
validates :body, presence: true, length: { maximum: 100 }
end
Exclusion setting because rubocop is moss. It is better not to be too strict because the test may be counterproductive if you comply with DRY and coding standards.
diff:.rubocop.yml
+ #Block length
+ Metrics/BlockLength:
+ Exclude:
+ - "spec/**/*"
At this point, run rspec, rubocop and it will pass