Rails Demo 2: Building a simple Blog app
So let’s create a caveman blog app. Let’s start by creating a new app.
-
We use the rails command to do this.
rails new blog
-
Now let’s use the scaffold generator to create our first entity. Note we use the capital letter singular version of the entity
rails generate scaffold Post title:string article:text likes:integer status:integer
-
Review briefly what this accomplishes (e.g. creates the MVC triad for Posts) as well as the db migration. Then run rake to cause the migration to execute.
rake db:migrate
-
Now we can run the rails server and play around with the MVC triad that was created.
rails s --binding=0.0.0.0
-
Let’s setup our
Post
model so it can deal with the enumerated status types properly. We need to add the following line of code to thePost
class inmodel/post.rb
:enum status: [:published, :unpublished]
-
But let’s say that we wanted the
:unpublished
value (which is actually1
) to be the default in the database, rather than0
(or:published
). We can do this by tweaking the migration that we generated and adding a default to the status field:class CreatePosts < ActiveRecord::Migration def change create_table :posts do |t| t.string :title t.text :article t.integer :likes t.integer :status, default: 1 t.timestamps null: false end end end
-
Yikes, we already ran our migration. No problem, let’s back off the migration and re-run it:
rake db:rollback STEP=1
-
Cool, now that we undid the previous migration, we can do it again with our updated migration.
rake db:migrate
-
However, we still have a problem, because our input forms are treating status as an integer value. Take a look at
views/_form.html.erb
for example. We can setup our form to handle the enumerated types by making several updates. First let’s add an action to theposts_controller.rb
. At the top we put this line:before_action :set_statuses
and on the bottom we put
def set_statuses @statuses = Post.statuses end
-
Now we need to update the form code, replacing this line of code:
<%= form.number_field :status, id: :post_status %>
with
<%= form.select :status, @statuses.keys, selected: @post.status %>
-
Now go ahead and visit the posts form and check out the enumerated type!
Using TDD to validate models
-
Let’s look at how we can add some validation to the form. Let’s do this via a TDD approach. Let’s start by writing down some of our constraints in the README file!
Testing specifications for posts: title: string article: text likes: integer status: enum - published or unpublished - title must be present - title must be between 5 and 80 characters - article must be present - article must be between 20 and 600 characters - likes must be positive - status must be valid
-
This is how we want the Post model to behave. Now let’s go ahead and write some tests that can test these particular specifications. First under
test/models/
add topost_test.rb
.require 'test_helper' class PostTest < ActiveSupport::TestCase def setup @post = Post.create(title: "a title", article: "This is the actual text of our article. It can be rather long.", likes: 0, status: 1) end test "post must be valid" do assert @post.valid? end end
-
Now that we have a test, we can actually run our tests with rake.
rake test
-
Notice that we pass all tests at this point, since we have not validation rules established for our model. Let’s go ahead and add test routines for each of our model specifications:
test "title must be present" do end test "title must not be too short" do end test "title must not be too long" do end test "article must be present" do end test "article must not be too short" do end test "article must not be too long" do end test "likes must be positive" do end test "status must be valid" do end
-
Now let’s implement each one of these with the appropriate forms of the
assert
method:test "post must be valid" do assert @post.valid? end test "title must be present" do @post.title = "" assert_not @post.valid? end test "title must not be too short" do @post.title = "aa" assert_not @post.valid? end test "title must not be too long" do @post.title = "a" * 81 assert_not @post.valid? end test "article must be present" do @post.article = "" assert_not @post.valid? end test "article must not be too short" do @post.article = "aa" assert_not @post.valid? end test "article must not be too long" do @post.article = "a" * 601 assert_not @post.valid? end test "likes must be positive" do @post.likes = -1 assert_not @post.valid? end test "status must be valid" do invalid_statuses = [-10, -1, 2, 10] invalid_statuses.each do |is| begin @post.status = is assert false, "#{is} should be invalid" rescue assert true end end end test "status must be published or unpublished" do valid_statuses = [:published, :unpublished] valid_statuses.each do |is| begin @post.status = is assert true rescue assert false, "#{is} should be invalid" end end end
-
Now if we run our tests again, many (but not all of them fail) since we have not yet implemented our model validation.
rake test
-
Ok, that makes sense, so lets proceed to implement the validations in our
post.rb
model.title
validates :title, presence: true, length: {minimum: 5, maximum: 80}
article
validates :article, presence: true, length: {minimum: 20, maximum: 600}
likes
validates :likes, numericality: {greater_than_or_equal_to: 0}
(Feeling a little overwhelmed with methods and parameters appearing out of nowhere? I certainly did when I first learned Rails.)
-
Wild, now all of our model tests pass, but some of our generated controller tests do not! That is because the auto-generated fixtures do not meet our model validation constraints! In particular the article fields aren’t long enough. Let’s fix this by editing
test/fixtures/posts.yml
. Then run the tests again. Eureka! We now have a a great deal of test automation in place and accomplished all this without breaking a sweat. - Let’s go ahead and try to run the web app and see how the validation works in our form. Pretty cool, eh?
- Discuss how the error messages are generated.
- Use Rails console to create invalid object. Notice that calling
valid?
fills themessages
instance variable.
-
We can play around with validations in the rails console as well.
rails console p = Post.create(title: "This is a valid title", article: "This is a very long article about absolutely nothing.", likes: 0) p.valid? p.title = "a" * 1000 p.valid? p.errors.messages