[Week 4 & 5] Infrastructure, tools and authentication

2022/01/10 - 2022/01/23

Introduction

In the last blog, I shared about week 3 was a rather unproductive one. I planned to build the whole authentication flow from scratch on week 4 but my body wasn't feeling well, so not much was done either on week 4. Week 5 wasn't a very focused week, I only managed to get a small part of the authentication flow done, but spent quite an amount of time working on the infrastructures and some additional tooling for a better development experience. Here are some of the highlights for week 4 and week 5:

  1. Authentication flow from scratch (30% done)
  2. Infrastructure (Sidekiq, Redis for the worker's queues, Redis for cache)
  3. Additional tools like Simplecov, code climate, spoom, RedisInsight and Rails credentials

Authentication flow from scratch

I have gone through the Michael Hartl's Ruby on Rails Tutorial where he taught about building authentication flow from scratch and also came across this repo by Steve Polito just at the right time when I am building this. I am using both of these as my guides and also with some personal tweaks. Again, this isn't a step by step guide, both of the resources are very well written for beginners to follow along, so be sure to check them out if you are interested. I am going to break down some of my learnings into two sections, Rails helpers that you can use for authentication-related stuff and Rails testing.

PS: These are not all the helpers available, I am only listing down whatever I have used so far, more will come in the next blog when I complete the whole authentication flow

For now, I have done the registration and email confirmation flows and here are some of the nice helpers provided by Rails:

  1. has_secure_password. To use this you have to have the bcrypt gem installed and a column name XXX_digest, in my case, I have a password_digest column in the User's model. With this setup, the front end can send back password and/or password_confirmation, and it will be encrypted and saved to the password_digest. There are many other methods provided by has_secure_password like authenticate but I will talk more about those when I start using them for handling the session.

  2. signed_id. This helps to generate a one-time use token with expiration and purpose for use cases like email confirmation or password reset. This way we don't have to store the token in the database. Here's an example of how I am using it together with find_signed:

# Generate a token and include them in the confirmation email
confirmation_token = user.signed_id(
    expires_in: 10.minutes,
    purpose: :confirm_email
)

# When a user clicks the link with the token
@user = User.find_signed(
    params[:confirmation_token],
    purpose: :confirm_email
)
# Do whatever you want on confirming the user,
# in my case, it was just updating the confirmed_at column.

(b) Rails testing and some useful helper methods

I am using Rspec at work for testing and wanted to try minitest out for this project. There are no additional steps to set up minitest because Rails comes with minitest as the default testing framework. However, you might want to install rails-controller-testing which brings back some really useful helper methods like assigns and assert_template.

Let's talk about the different types of testing in Rails, be sure to check out the testing guides, it is really well written and a good read to understand the overview of testing a Rails's application. Rails have different terminologies for their tests like Model Test, Integration Test, System Test etc. Most of them are self-explanatory, the only one that confuses me was the Integration and Functional test (previously called controller test). For me, both of them are testing the same thing, so my question was do I need to write both? I didn't manage to find some recent discussion on this matter but came across this blog post by Jason Swett which was written 3 years ago. In conclusion, I am not writing functional test now, and every action in the controller will be tested in the integration test instead. So here's how I am testing my app now:

  1. Model Test, for all the validations and public methods in the model.
  2. Mailer Test, for email related stuff like making sure the email is enqueued, right email's subject, body, from address, to address etc have been used.
  3. Helper Test, for all the custom helper methods added.
  4. Integration Test, for all the actions added in the controllers that can be tested without Javascript.
  5. System Test, for all the flows that have to be tested with Javascript.
  6. Job/Worker Test, for all the workers/jobs. I am planning to use just Sidekiq workers instead of Active jobs. The reason being is that I want to see the difference because I have been using Active Jobs with the adapter set to :sidekiq at work and it seems like using just sidekiq has better performance as well according to the documentation. There is also good documentation on how sidekiq testing works.

Testing with minitest involves a lot of assertions, as a first-time minitest user I wasn't very sure what assertions to use but the documentation is pretty well written so I picked a few up as I go along. You can find the available assertions through:

  1. Official Minitest documentation
  2. Subset of assertions that minitest supports on Rails documentation
  3. Rails specific assertions

After writing tests for both the registration and confirmation flows, here are some helper methods that I find useful.

  1. travel and travel_back, I am using this to test the confirmation token generated by signed_id with custom expires_in

    test "confirmation token expires in the set expiration times" do
     @user.save
     confirmation_token = @user.generate_confirmation_token
    
     travel User::CONFIRMATION_TOKEN_EXPIRATION + 1.minute
     assert_nil User.find_signed(confirmation_token, purpose: :confirm_email)
    
     travel_back
     assert_equal User.find_signed(confirmation_token, purpose: :confirm_email), @user
    end
    
  2. I think this is a controversial one, assigns and assert_template. Both of these methods are removed from Rails back in 2015 in this pull request but a third party gem rails-controller-testing is adding them back.

    • assigns allows you to access the instance variables that have been passed to your views
    • assert_template allows you to assert that certain templates have been rendered.

Infrastructure

Sidekiq and Redis wasn't something new to me as I have been using them at work. The new thing is setting them up with Render. Again I really appreciate products with well-written documentation, you can read more on how to set up a background worker for Sidekiq and Redis on Render. Here's what my current set-up looks like and the price Render charged. With all the services, I have to pay RM130 ($31) every month which I think is pretty reasonable.

image.png

Toolings that improves the development experience

  1. simplecov, it is super nice that it can tell which line is not tested in the test suite. Here's an example of what it looks like for a Rails project. Finger crossed that I can keep it at 100% covered as the project grows :)

    image.png

  2. Code Climate, it provides some sort of automated code review for maintainability and test coverage. You can send the report generated from simplecov to code climate through CI, but I didn't set that up for now. Here's the maintainability of the project as of now.

    image.png

  3. spoom, I am using it to get an overview of typing coverage report from Sorbet.

    image.png

  4. Rails credentials, at work I have been using environment variables to store sensitive data like API keys, but now that I have learned about Rails credentials, I am using it instead to store sensitive data. One benefit of using this so far is, I don't have to worry about adding those environment variables to the hosting provider like Render or Heroku every time I added some new keys. The environment variables I need is the RAILS_MASTER_KEYS for every environment I have, and I can easily add new keys to the credentials without worrying that I forget to add them to the hosting provider. Here is how I am using it now

    # before
    ENV["SOME_KEY"]
    # now
    Rails.application.credentials.some_key
    
  5. RedisInsight client, I think this is just a nice to have. I am using it to look at whatever is inside my cache now, for example:

    image.png

Conclusion

That's all for this week, even though I didn't manage to finish the authentication flow, I have learned a lot of new things on Week 4 and Week 5. Thanks for reading, and wishing you a very good day! Till the next one :)