diff --git a/rails6/en/chapter01-introduction.adoc b/rails6/en/chapter01-introduction.adoc index 765a807..405eb66 100644 --- a/rails6/en/chapter01-introduction.adoc +++ b/rails6/en/chapter01-introduction.adoc @@ -1,7 +1,7 @@ [#chapter01-introduction] = Introduction -Welcome to API on Rails 6, a tutorial on steroid to learn the best way to build your next API with Rails. The purpose of this book is to provide a comprehensive methodology to develop a RESTful API following best practices. +Welcome to API on Rails 6, a tutorial on steroids to learn the best way to build your next API with Rails. The purpose of this book is to provide a comprehensive methodology to develop a RESTful API following best practices. As soon as you finish this book, you will be able to create your own API and integrate it with any client such as a web browser or mobile application. The generated code is built with Ruby on Rails 6.0 which is the current version. @@ -14,20 +14,20 @@ The purpose of this book is not only to teach you how to build an API with Rails - Use JSON:API specification - Optimize and cache the API -I strongly recommend you follow all the steps in this book. Try not to skip chapters because I will give you some tips and tricks to improve your skills throughout the book. You can consider yourself the main character of a video game who gain a level in each chapter. +I strongly recommend you follow all the steps in this book. Try not to skip chapters because I will give you some tips and tricks to improve your skills throughout the book. You can consider yourself the main character of a video game who gains a level in each chapter. -In this first chapter I will explain how to configure your environment (in case you don't already have it). Then we will create an application called `market_place_api`. I will ensure that I teach you the best practices I have learned during my experience. This means that we'll start using *Git* just after initializing the project. +In the first chapter, I will explain how to configure your environment (in case you don't already have it). Then we will create an application called `market_place_api`. I will ensure that I teach you the best practices I have learned during my experience. This means that we'll start using *Git* just after initializing the project. We'll build the application following a simple working method that I use daily in the next chapters. We will develop the entire application using Test Driven Development (TDD). I will also explain the interest of using an API for your next project and choosing a suitable response format such as JSON or XML. Further on, we will get our hands on the code and complete the basics of the application by building all the necessary roads. We will also secure access to the API by building authentication by exchanging HTTP headers. Finally, in the last chapter, we will add some optimization techniques to improve the structure and response times of the server. -The final application will scratch the surface of being a market place where users will be able to place orders, upload products and more. There are plenty of options out there to set up an online store, such as http://shopify.com[Shopify], http://spreecommerce.com/[Spree] or http://magento.com[Magento]. +The final application will scratch the surface of being a market place where users will be able to place orders, upload products, and more. There are plenty of options out there to set up an online store, such as http://shopify.com[Shopify], http://spreecommerce.com/[Spree], or http://magento.com[Magento]. == Conventions on this book -The conventions on this book are based on the ones from http://www.railstutorial.org/book/beginning#sec-conventions[Ruby on Rails Tutorial]. In this section I’ll mention some that may not be so clear. +The conventions on this book are based on the ones from http://www.railstutorial.org/book/beginning#sec-conventions[Ruby on Rails Tutorial]. In this section, I’ll mention some that may not be so clear. -I’ll be using many examples using command-line commands. I won’t deal with windows `cmd` (sorry guys), so I’ll based all the examples using Unix-style command line prompt, as follows: +I’ll be using many examples using command-line instructions. I won’t deal with windows `cmd` (sorry guys), so all the examples use Unix-style command line prompt, as follows: [source,bash] ---- @@ -41,7 +41,7 @@ I’ll be using some guidelines related to the language, what I mean by this is: * *Prefer* indicates that from the 2 options, the first it’s a better fit * *Use* means you are good to use the resource -If for any reason you encounter some errors when running a command, rather than trying to explain every possible outcome, I’ll will recommend you to `google it', which I don’t consider a bad practice or whatsoever. But if you feel like want to grab a beer or have troubles with the tutorial you can always mailto:contact@rousseau-alexandre.fr[email me]. +If for any reason you encounter some errors when running a command, rather than trying to explain every possible outcome, I recommend you to `google it', which I don’t consider a bad practice or whatsoever. But if you feel like you want to grab a beer or have some trouble with the tutorial you can always mailto:contact@rousseau-alexandre.fr[email me]. == Development environments @@ -49,10 +49,10 @@ One of the most painful parts for almost every developer is setting everything u === Text editors and Terminal -There are many cases in which development environments may differ from computer to computer. That is not the case with text editors or IDE’s. I think for Rails development an IDE is way to much, but some other might find that the best way to go, so if that it’s your case I recommend you go with http://www.aptana.com/products/radrails[RadRails] or http://www.jetbrains.com/ruby/index.html[RubyMine], both are well supported and come with many integrations out of the box. +There are many cases in which development environments may differ from computer to computer. That is not the case with text editors or IDE’s. I think for Rails development an IDE is way too much, but some other might find that the best way to go, so if that it’s your case I recommend you go with http://www.aptana.com/products/radrails[RadRails] or http://www.jetbrains.com/ruby/index.html[RubyMine], both are well supported and come with many integrations out of the box. * *Text editor*: I personally use http://www.vim.org/[vim] as my default editor with https://github.com/carlhuda/janus[janus] which will add and handle many of the plugins you are probably going to use. In case you are not a _vim_ fan like me, there are a lot of other solutions such as http://www.sublimetext.com/[Sublime Text] which is a cross-platform easy to learn and customize (this is probably your best option), it is highly inspired by http://macromates.com/[TextMate] (only available for Mac OS). A third option is using a more recent text editor from the guys at http://gitub.com[GitHub] called https://atom.io/[Atom], it’s a promising text editor made with JavaScript, it is easy to extend and customize to meet your needs, give it a try. Any of the editors I present will do the job, so I’ll let you decide which one fits your eye. -* *Terminal*: If you decided to go with http://icalialabs.github.io/kaishi/[kaishi] for setting the environment you will notice that it sets the default shell to `zsh`, which I highly recommend. For the terminal, I’m not a fan of the _Terminal_ app that comes out of the box if you are on Mac OS, so check out http://www.iterm2.com/#/section/home[iTerm2], which is a terminal replacement for Mac OS. If you are on Linux you probable have a nice terminal already, but the default should work just fine. +* *Terminal*: If you decided to go with http://icalialabs.github.io/kaishi/[kaishi] for setting the environment you will notice that it sets the default shell to `zsh`, which I highly recommend. For the terminal, I’m not a fan of the _Terminal_ app that comes out of the box if you are on Mac OS, so check out http://www.iterm2.com/#/section/home[iTerm2], which is a terminal replacement for Mac OS. If you are on Linux you probably have a nice terminal already, but the default should work just fine. === Browsers @@ -60,14 +60,14 @@ When it comes to browsers I would say http://www.mozilla.org/en-US/firefox/new/[ === Package manager -* *Mac OS*: There are many options to manage how you install packages on your Mac, such as https://www.macports.org/[Mac Ports] or http://brew.sh/[Homebrew], both are good options but I would choose the last one, I’ve encountered less troubles when I install software and I manage it. To install `brew` just run the command below: +* *Mac OS*: There are many options to manage how you install packages on your Mac, such as https://www.macports.org/[Mac Ports] or http://brew.sh/[Homebrew], both are good options but I would choose the last one, I’ve encountered fewer troubles when I install software and I manage it. To install `brew` just run the command below: [source,bash] ---- $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" ---- -* *Linux*: You are all set!, it really does not matter if you are using `apt`, `pacman`, `yum` as long you feel comfortable with it and know how to install packages so you can keep moving forward. +* *Linux*: You are all set! It really does not matter if you are using `apt`, `pacman`, `yum` as long you feel comfortable with it, and you know how to install packages so you can keep moving forward. === Git @@ -99,7 +99,7 @@ $ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804B $ \curl -sSL https://get.rvm.io | bash ---- -Next it is time to install ruby: +Next, it is time to install ruby: [source,bash] ---- @@ -110,7 +110,7 @@ Now it is time to install the rest of the dependencies we will be using. ==== Gems, Rails & Missing libraries -First we update the gems on the whole system: +First, we update the gems on the whole system: [source,bash] ---- @@ -142,7 +142,7 @@ Rails 6.0.0 ==== Database -I highly recommend you install http://www.postgresql.org/[Postgresql] to manage your databases. But here we’ll be using http://www.sqlite.org/[SQlite] for simplicity . If you are using Mac OS you should be ready to go, in case you are on Linux, don’t worry we have you covered: +I highly recommend you install http://www.postgresql.org/[Postgresql] to manage your databases. But here we’ll be using http://www.sqlite.org/[SQlite] for simplicity. If you are using Mac OS you should be ready to go, in case you are on Linux, don’t worry we have you covered: [source,bash] ---- @@ -179,7 +179,7 @@ Remember that Git helps you track and maintain your code history. Keep in mind t Ruby on Rails initialized the Git directory for you when you used the `rails new` command. This means that you do not need to execute the `git init` command. -However it is necessary to configure the information of the author of _commits_. If you have not already done so, go to the directory and run the following commands: +However, it is necessary to configure the information of the author of _commits_. If you have not already done so, go to the directory and run the following commands: [source,bash] ---- @@ -237,7 +237,7 @@ Then we push the code: $ git push -u origin master ---- -As we move forward with the tutorial, I’ll be using the practices I follow on my daily basis, this includes working with `branches`, `rebasing`, `squash` and some more. For now you don’t have to worry if some of these don’t sound familiar to you, I walk you through them in time. +As we move forward with the tutorial, I’ll be using the practices I follow on my daily basis, this includes working with `branches`, `rebasing`, `squash` and some more. For now, you don’t have to worry if some of these don’t sound familiar to you, I walk you through them in time. == Conclusion diff --git a/rails6/en/chapter02-api.adoc b/rails6/en/chapter02-api.adoc index c61f7f3..762f010 100644 --- a/rails6/en/chapter02-api.adoc +++ b/rails6/en/chapter02-api.adoc @@ -1,7 +1,7 @@ [#chapter02-api] = The API -In this section I’ll outline the application. By now you should have read the previous chapter. If you did not read it I recommend you to do it. +In this section, I’ll outline the application. By now you should have read the previous chapter. If you did not read it I recommend you to do it. You can clone the project until this point with: @@ -15,34 +15,34 @@ To summarize, we simply generated our Rails application and made our first commi == Planning the application -As we want to go simple with the application it consists on five models. Don’t worry if you don’t fully understand what is going on. We will review and build each of these resources as we move on with the tutorial. +As we want to go simple with the application it consists of five models. Don’t worry if you don’t fully understand what is going on. We will review and build each of these resources as we move on with the tutorial. image:data_model.png[Schema of links betweens models] -In brief, the `user` will be able to place many `orders`, upload multiple `products` which can have many `images` or `comments` from another users on the app. +In brief, the `user` will be able to place many `orders`, upload multiple `products` which can have many `images` or `comments` from other users on the app. We are not going to build views for displaying or interacting with the API, so not to make this a huge tutorial, I’ll let that to you. There are plenty of options out there like javascript frameworks (https://angularjs.org/[Angular], https://vuejs.org/[Vue.js], https://reactjs.org/[React.js]). By this point you must be asking yourself: -> all right but I need to explore or visualize the api we are going to be building? +> all right but I need to explore or visualize the API we are going to be building? -That’s fair. Probably if you google something related to api exploring, an application called https://www.getpostman.com/[Postman] will pop. It is a great software but we won’t be using that anyway because we'll use *cURL* who allow anybody to reproduce request on any computer. +That’s fair. Probably if you google something related to API exploring, an application called https://www.getpostman.com/[Postman] will pop. It is great software but we won’t be using that anyway because we'll use *cURL* which allows anybody to reproduce requests on any computer. == Setting the API -An API is defined by http://en.wikipedia.org/wiki/Application_programming_interface[wikipedia] as _an application programming interface (API) specifies how some software components should interact with each other._ In other words the way systems interact with each other through a common interface, in our case a web service built with JSON. There are other kinds of communication protocols like SOAP, but we are not covering that in here. +An API is defined by http://en.wikipedia.org/wiki/Application_programming_interface[wikipedia] as _an application programming interface (API) specifies how some software components should interact with each other._ In other words, the way systems interact with each other through a common interface, in our case a web service built with JSON. There are other kinds of communication protocols like SOAP, but we are not covering that in here. -JSON, as the Internet media type standard, is widely accepted, readable, extensible and easy to implement. Many of the current frameworks consume JSON API’s by default (https://angularjs.org/[Angular] or https://vuejs.org/[Vue.js] for example). There are also great libraries for Objective-C too like https://github.com/AFNetworking/AFNetworking[AFNetworking] or http://restkit.org/[RESTKit]. There are probably good solutions for Android but because of my lack of experience on that development platform I might not be the right person to recommend you something. +JSON, as the Internet media type standard, is widely accepted, readable, extensible, and easy to implement. Many of the current frameworks consume JSON APIs by default (https://angularjs.org/[Angular] or https://vuejs.org/[Vue.js] for example). There are also great libraries for Objective-C too like https://github.com/AFNetworking/AFNetworking[AFNetworking] or http://restkit.org/[RESTKit]. There are probably good solutions for Android but because of my lack of experience on that development platform, I might not be the right person to recommend something. -All right. So we are building our API with JSON. There are many ways to achieve this. The first thing that comes to mind would be just to start adding routes defining the end points. This may be bad because they may not have a http://www.w3.org/2005/Incubator/wcl/matching.html[URI pattern] clear enough to know which resource is being exposed. The protocol or structure I’m talking about is http://en.wikipedia.org/wiki/Representational_state_transfer[REST] which stands for Representational State Transfer and by wikipedia definition +All right. So we are building our API with JSON. There are many ways to achieve this. The first thing that comes to mind would be just to start adding routes defining the endpoints. This may be bad because they may not have a http://www.w3.org/2005/Incubator/wcl/matching.html[URI pattern] clear enough to know which resource is being exposed. The protocol or structure I’m talking about is http://en.wikipedia.org/wiki/Representational_state_transfer[REST] which stands for Representational State Transfer and by Wikipedia definition [source,soap] ---- aService.getUser("1") ---- -And in REST you may call a URL with an specific HTTP request, in this case with a GET request: +And in REST you may call a URL with a specific HTTP request, in this case with a GET request: RESTful APIs must follow at least three simple guidelines: @@ -54,11 +54,11 @@ RESTful APIs must follow at least three simple guidelines: ** *PUT*: Updates a collection or member of the resources ** *DELETE*: Destroys a collection or member of the resources -This might not be clear enough or may look like a lot of information to digest but as we move on with the tutorial, hopefully it’ll get a lot easier to understand. +This might not be clear enough or may look like a lot of information to digest but as we move on with the tutorial, hopefully, it’ll get a lot easier to understand. === Routes, Constraints and Namespaces -Before start typing any code, we prepare the code with git. We’ll be using a branch per chapter, upload it to GitHub and then merge it with master. So let’s get started open the terminal, `cd` to the `market_place_api` directory and type in the following: +Before start typing any code, we prepare the code with git. We’ll be using a branch per chapter, upload it to GitHub and then merge it on `master` branch. So let’s get started open the terminal, `cd` to the `market_place_api` directory and type in the following: [source,bash] ---- @@ -76,7 +76,7 @@ Rails.application.routes.draw do end ---- -First of all erase all commented code that comes within the file, we are not gonna need it. Then commit it, just as a warm up: +First of all, erase all commented code that comes within the file, we are not gonna need it. Then commit it, just as a warm-up: [source,bash] ---- @@ -84,7 +84,7 @@ $ git add config/routes.rb $ git commit -m "Removes comments from the routes file" ---- -We are going to isolate the api controllers under a namespace. With Rails this is fairly simple: you just have to create a folder under the `app/controllers` named `api`. The name is important because that's the namespace we’ll use for managing the controllers for the api endpoints. +We are going to isolate the API controllers under a namespace. With Rails this is fairly simple: you just have to create a folder under the `app/controllers` named `api`. The name is important because that's the namespace we’ll use for managing the controllers for the API endpoints. [source,bash] ---- @@ -97,7 +97,7 @@ Then we add that namespace into our _routes.rb_ file: .config/routes.rb ---- Rails.application.routes.draw do - # Api definition + # API definition namespace :api do # We are going to list our resources here end @@ -124,14 +124,14 @@ This is important because we are going to be working with JSON, one of the built .config/routes.rb ---- Rails.application.routes.draw do - # Api definition + # API definition namespace :api, defaults: { format: :json } do # We are going to list our resources here end end ---- -Up to this point we have not made anything crazy. What we want to generate is a _base_uri_ wich include the API version. But let's make a comit before go to next section: +Up to this point, we have not made anything crazy. What we want to generate is a _base_uri_ which includes the API version. But let's commit changes before going to the next section: [source,bash] ---- @@ -141,20 +141,20 @@ $ git commit -m "Set the routes constraints for the api" == Api versioning -At this point we should have a nice routes mapping using a namespace. Your `routes.rb` file should look like this: +At this point, we should have a nice routes mapping using a namespace. Your `routes.rb` file should look like this: [source,ruby] .config/routes.rb ---- Rails.application.routes.draw do - # Api definition + # API definition namespace :api, defaults: { format: :json } do # We are going to list our resources here end end ---- -Now it is time to set up some other constraints for versioning purposes. You should care about versioning your application from the beginning since this will give a better structure to your api, and when changes need to be done, you can give developers who are consuming your api the opportunity to adapt for the new features while the old ones are being deprecated. There is an excellent http://railscasts.com/episodes/350-rest-api-versioning[railscast] explaining this. +Now it is time to set up some other constraints for versioning purposes. You should care about versioning your application from the beginning since this will give a better structure to your API, and when changes need to be done, you can give developers who are consuming your API the opportunity to adapt for the new features while the old ones are being deprecated. There is an excellent http://railscasts.com/episodes/350-rest-api-versioning[railscast] explaining this. In order to set the version for the API, we first need to add another directory under the `api` we created: @@ -178,16 +178,16 @@ Rails.application.routes.draw do end ---- -By this point the API is now scoped via the URL. For example with the current configuration an end point for retrieving a product would be like: . +By this point, the API is now scoped via the URL. For example, with the current configuration, an endpoint for retrieving a product would be like . .Common API patterns **** -You can find many approaches to set up the _base_uri_ when building an api following different patterns, assuming we are versioning our api: +You can find many approaches to set up the _base_uri_ when building an API following different patterns, assuming we are versioning our api: -* `api.example.com/`: In my opinion this is the way to go, gives you a better interface and isolation, and in the long term can help you to http://www.makeuseof.com/tag/optimize-your-dns-for-faster-internet/[quickly scalate] -* `example.com/api/`: This pattern is very common, and it is actually a good way to go when you don’t want to namespace your api under a subdomain -* `example.com/api/v1`: it seems like a good idea, by setting the version of the api through the URL seems like a more descriptive pattern, but this way you enforce the version to be included on URL on each request, so if you ever decide to change this pattern, this becomes a problem of maintenance in the long-term +* `api.example.com/`: In my opinion, this is the way to go, gives you a better interface and isolation, and in the long term can help you to http://www.makeuseof.com/tag/optimize-your-dns-for-faster-internet/[quickly scalate] +* `example.com/api/`: This pattern is very common, and it is actually a good way to go when you don’t want to namespace your API under a subdomain +* `example.com/api/v1`: it seems like a good idea, by setting the version of the API through the URL seems like a more descriptive pattern, but this way you enforce the version to be included on URL on each request, so if you ever decide to change this pattern, this becomes a problem of maintenance in the long-term There are some practices in API building that recommend not to version the API via the URL. That's true. The developer should not be aware of the version he's using. For the sake of simplicity, I have chosen to set aside this convention, which we will be able to apply in a second phase. **** @@ -214,4 +214,4 @@ It’s been a long way, I know, but you made it, don’t give up this is just ou * https://github.com/Sutto/rocket_pants[RocketPants] * https://github.com/bploetz/versionist[Versionist] -I’m not covering those in this book, since we are trying to learn how to actually implement this kind of functionality, but it is good to know though. By the way the code up to this point is https://github.com/madeindjs/market_place_api_6/releases/tag/checkpoint_chapter03[here]. +I’m not covering those in this book, since we are trying to learn how to actually implement this kind of functionality, but it is good to know though. By the way, the code up to this point is https://github.com/madeindjs/market_place_api_6/releases/tag/checkpoint_chapter03[here]. diff --git a/rails6/en/chapter03-presenting-users.adoc b/rails6/en/chapter03-presenting-users.adoc index 7bdd435..782fea6 100644 --- a/rails6/en/chapter03-presenting-users.adoc +++ b/rails6/en/chapter03-presenting-users.adoc @@ -1,9 +1,9 @@ [#chapter03-presenting-users] = Presenting users -In the last chapter we manage to set up the bare bones for our application endpoints configuration. +In the last chapter, we manage to set up the bare bones for our application endpoints configuration. -In a next chapter we will handle users authentication through authentication tokens as well as setting permissions to limit access for let’s say signed in users. In coming chapters we will relate `products` to users and give them the ability to place orders. +In the next chapter, we will handle user authentication through authentication tokens as well as setting permissions to limit access for let’s say, signed-in users. In the coming chapters, we will relate `products` to users and give them the ability to place orders. You can clone the project until this point with: @@ -13,11 +13,11 @@ $ git checkout tags/checkpoint_chapter03 ---- -As you can already imagine there are a lot of authentication solutions for Rails, https://github.com/binarylogic/authlogic[AuthLogic], https://github.com/thoughtbot/clearance[Clearance] and https://github.com/plataformatec/devise[Devise]. +As you can already imagine there are a lot of authentication solutions for Rails, https://github.com/binarylogic/authlogic[AuthLogic], https://github.com/thoughtbot/clearance[Clearance], and https://github.com/plataformatec/devise[Devise]. -These solutions are turnkey libraries, i.e. they allow you to manage a whole bunch of things like authentication, password forgetfulness, validation, etc... Nevertheless we will use https://github.com/codahale/bcrypt-ruby[bcrypt] gem to hash the user's password. +These solutions are turnkey libraries, i.e. they allow you to manage a whole bunch of things like authentication, password forgetfulness, validation, etc... Nevertheless, we will use https://github.com/codahale/bcrypt-ruby[bcrypt] gem to hash the user's password. -This chapter will be complete. It may be a long one but I will try to cover as many topics as possible. Feel free to have a coffee and let's go. At the end of this chapter you will have built all the user logic as well as the validation and error management. +This chapter will be complete. It may be a long one but I will try to cover as many topics as possible. Feel free to have a coffee and let's go. At the end of this chapter, you will have built all the user logic as well as validation and error management. It is a good time to create a new branch: @@ -50,7 +50,7 @@ invoke active_record create test/fixtures/users.yml ---- -NOTE: The _model_ is the element containing the data as well as the logic related to the data: validation, reading and recording. +NOTE: The _model_ is the element containing the data as well as the logic related to the data: validation, reading, and recording. This command generates a lot of files! Don't worry we'll review them one by one. @@ -73,7 +73,7 @@ end NOTE: The inserted date at the beginning of the migration file name should be different for you since it corresponds to the migration creation date. -We will make a small change to the migration in order to add some database validations. With Rails it is common practice to make these verifications directly in the Ruby model. It is good practice to do so also in the database schema. +We will make a small change to the migration in order to add some database validations. With Rails, it is common practice to make these verifications directly in the Ruby model. It is good practice to do so also in the database schema. We will therefore add two additional constraints: @@ -411,7 +411,7 @@ $ git add . && git commit -m "Adds show action to the users controller" So we finally have a resource to test. We have several solutions to test it. The first one that comes to mind is the use of cURL, which is integrated in almost all Linux distributions. So let's try it: -First initialize the rails server on a new terminal. +First, initialize the rails server on a new terminal. [source,bash] ---- $ rails s @@ -459,8 +459,8 @@ end That's a lot of code. Don't worry I'll explain everything: -* In the first test we check the creation of a user by sending a valid POST request. Then, we checked that an additional user exists in the database and that the HTTP code of the response is `created` (status code 201) -* In the second test we check that the user is not created using an email already used. Then, we check that the HTTP code of the response is `unprocessable_entity` (status code 422) +* In the first test, we check the creation of a user by sending a valid POST request. Then, we checked that an additional user exists in the database and that the HTTP code of the response is `created` (status code 201) +* In the second test, we check that the user is not created using an email already used. Then, we check that the HTTP code of the response is `unprocessable_entity` (status code 422) At that point, the tests must fail (as we expected): @@ -574,7 +574,7 @@ Then we implement the update action on the user controller and run our tests: ---- class Api::V1::UsersController < ApplicationController before_action :set_user, only: %i[show update] - + # GET /users/1 def show render json: @user diff --git a/rails6/en/chapter04-athentification.adoc b/rails6/en/chapter04-athentification.adoc index 3d85d94..a7d5e1b 100644 --- a/rails6/en/chapter04-athentification.adoc +++ b/rails6/en/chapter04-athentification.adoc @@ -1,9 +1,9 @@ -[#chapter05-athentification] +[#chapter04-authentication] = Authenticating user It's been a long time since you started. I hope you enjoy this trip as much as I do. -In the previous chapter we set up user resource entries. If you have skipped this chapter or if you have not understood everything, I strongly recommend that you look at it. It covers the first bases of the tests and is an introduction to JSON answers. +In the previous chapter, we set up user resource entries. If you have skipped this chapter or if you have not understood everything, I strongly recommend that you look at it. It covers the first bases of the tests and is an introduction to JSON answers. You can clone the project up to this point: @@ -12,7 +12,7 @@ You can clone the project up to this point: $ git checkout tags/checkpoint_chapter04 ---- -In this chapter things will get very interesting because we are going to set up our authentication mechanism. In my opinion it's one of the most interesting chapters. We will introduce a lot of new terms and you will end with a simple but powerful authentication system. Don’t feel panic we will get to that. +In this chapter, things will get very interesting because we are going to set up our authentication mechanism. In my opinion, it's one of the most interesting chapters. We will introduce a lot of new terms and you will end with a simple but powerful authentication system. Don’t feel panic we will get to that. First things first (and as usual when starting a new chapter) we will create a new branch: @@ -29,9 +29,9 @@ The flow for authenticating the user through an API is very simple: . The client request for `sessions` resource with the corresponding credentials (usually email and password) . The server returns the `user` resource along with its corresponding authentication token -. For every page that requires authentication the client has to send that `authentication token` +. For every page that requires authentication, the client has to send that `authentication token` -Of course this is not the only 3-step to follow, and even on step 2 you might think, well do I really need to respond with the entire user or just the `authentication token` ? I would say, it really depends on you, but I like to return the entire user, this way I can map it right away on my client and save another possible request from being placed. +Of course, this is not the only 3-step to follow, and even on step 2 you might think, well do I really need to respond with the entire user or just the `authentication token`? I would say, it really depends on you, but I like to return the entire user, this way I can map it right away on my client and save another possible request from being placed. This section and the next we will be focusing on building a Sessions controller along with its corresponding actions. We’ll then complete the request flow by adding the necessary authorization access. @@ -100,9 +100,9 @@ The library is very simple. There are two methods: `JWT.encode` and `JWT.decode` => [{"message"=>"Hello World"}, {"alg"=>"HS256"}] ---- -In the first line we encoded a _payload_ with the secret key `my_secret_key`. So we get a token we can simply decode. The second line decodes the token and we see that we find our _payload_ well. +In the first line, we encoded a _payload_ with the secret key `my_secret_key`. So we get a token we can simply decode. The second line decodes the token and we see that we find our _payload_ well. -We will now include all this logic in a `JsonWebToken` class in a new file located in `lib/`. This will allow us to avoide duplicating the code. This class will just encode and decode the JWT tokens. So here is the implementation. +We will now include all this logic in a `JsonWebToken` class in a new file located in `lib/`. This will allow us to avoid duplicating the code. This class will just encode and decode the JWT tokens. So here is the implementation. .lib/json_web_token.rb [source,ruby] @@ -179,8 +179,8 @@ end We will build functional tests before going any further. The desired behavior is the following: -- I receive a token if I send a valid email / password pair -- otherwise server repond a `forbidden` response +- I receive a token if I send a valid email/password pair +- otherwise, the server responds a `forbidden` response The tests therefore materialize as follows: @@ -294,7 +294,7 @@ $ git add . && git commit -m "Setup tokens controller" So we implemented the following logic: API returns the authentication token to the client if credentials are correct. -We will now implement the following logic: we'll find corresponding user of authentication token given into the HTTP header. We'll need to do so each time this client requests an entry point which requires permission. +We will now implement the following logic: we'll find the corresponding user of the authentication token given into the HTTP header. We'll need to do so each time this client requests an entry point which requires permission. We will use the HTTP header `Authorization` which is often used for this purpose. We may also use a GET parameter named `apiKey` but I prefer to use an HTTP header because it gives context to the request without polluting the URL with additional parameters. @@ -442,7 +442,7 @@ $ git add . && git commit -m "Adds authenticable module for managing authenticat == Authentication with the token -Authorization plays an important role in the construction of applications because it helps us define what user is allowed to do. +Authorization plays an important role in the construction of applications because it helps us define what the user is allowed to do. We have a route to update the user but there is a problem: anyone can update any user. In this section, we will implement a method that will require the user to be logged in to prevent unauthorized access. @@ -514,7 +514,7 @@ Expected response to be a <2XX: success>, but was a <403: Forbidden> ..F Failure: -"User.count" didn t change by -1. +"User.count" didn't change by -1. Expected: 0 Actual: 1 ---- @@ -550,6 +550,6 @@ $ git merge chapter04 == Conclusion -Yeah! you made it! you are half way done! Keep up the good work. This chapter was a long and hard one but it is a great step forward on setting a solid mechanism for handling user authentication. We even scratch the surface for simple authorization rules. +Yeah! you made it! you are halfway done! Keep up the good work. This chapter was a long and hard one but it is a great step forward on setting a solid mechanism for handling user authentication. We even scratch the surface for simple authorization rules. -In the next chapter we will be focusing on customizing the JSON output for the user with https://github.com/Netflix/fast_jsonapi[fast_jsonapi] gem and adding a `product` model to the equation by giving the user the ability to create a product and publish it for sale. +In the next chapter, we will be focusing on customizing the JSON output for the user with https://github.com/Netflix/fast_jsonapi[fast_jsonapi] gem and adding a `product` model to the equation by giving the user the ability to create a product and publish it for sale. diff --git a/rails6/en/chapter05-user-products.adoc b/rails6/en/chapter05-user-products.adoc index ee1b680..63d4d88 100644 --- a/rails6/en/chapter05-user-products.adoc +++ b/rails6/en/chapter05-user-products.adoc @@ -29,7 +29,7 @@ $ git checkout -b chapter05 == The product model -We will first create a `Product` model first. Then we'll add some validations and finally associate it with the `User` model. Like the `User` model, the `Product` will be fully tested and will be automatically deleted if the user is deleted. +We will first create a `Product` model. Then we'll add some validations and finally associate it with the `User` model. Like the `User` model, the `Product` will be fully tested and will be automatically deleted if the user is deleted. === The foundations of the product @@ -82,7 +82,7 @@ We now just have to initiate migration: $ rake db:migrate ---- -A test must fails at this point: +A test must fail at this point: [source,bash] ---- @@ -118,7 +118,7 @@ one: You can see this _fixture_ does not use the attribute `user_id` but `user`. This means that the `one` product will have an `user_id` attribute corresponding to the `one` user ID. -It is therefore necessary to specify a cascading deletion in order to delete the `one` product when the `one` user is deleted. Let's start with the unit test: +It is, therefore, necessary to specify a cascading deletion in order to delete the `one` product when the `one` user is deleted. Let's start with the unit test: .test/models/user_test.rb @@ -158,9 +158,9 @@ $ git add . && git commit -m "Generate product model" === Product validations -Validations are an important part when building any kind of application. This will prevent any junk data from being saved onto the database. In the product we have to make sure for example the price is a `number` and that is not negative. +Validations are an important part when building any kind of application. This will prevent any junk data from being saved onto the database. In the product, we have to make sure for example the price is a `number` and that is not negative. -Also an important thing about validation is to validate that every product has a user. In this case we need to validate the presence of the `user_id`. You can see what I’m talking about in next code snippet. +Also, an important thing about validation is to validate that every product has a user. In this case, we need to validate the presence of the `user_id`. You can see what I’m talking about in the next code snippet. [source,ruby] .test/models/product_test.rb @@ -205,7 +205,7 @@ $ git commit -am "Adds some validations to products" == Products endpoints -It is now time to start building the products endpoints. For now we will just build five REST actions. In the next Chapter we will customize the JSON output by implementing the https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. +It is now time to start building the products endpoints. For now, we will just build five REST actions. In the next chapter, we will customize the JSON output by implementing the https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. First we need to create the `products_controller`, and we can easily achieve this with the command below: @@ -221,11 +221,11 @@ $ rails generate controller api::v1::products The above command will generate a lot of files that will allow us to start working quickly. What I mean by that is that it will generate the controller and test files already _scoped_ to version 1 of the API. -As a warmup we will start nice and easy by building the `show` action for the product. +As a warmup, we will start nice and easy by building the `show` action for the product. === Show action for products -As usual we begin by adding some product `show` controller specs. The strategy here is very simple: we just need to create a single product and make sure the response from server is what we expect. +As usual, we begin by adding some product `show` controller specs. The strategy here is very simple: we just need to create a single product and make sure the response from the server is what we expect. [source,ruby] .test/controllers/api/v1/products_controller_test.rb @@ -282,11 +282,11 @@ $ rake test ................. ---- -As you may notice already the specs and implementation are very simple. Actually they behave the same as the users. +As you may notice already the specs and implementation are very simple. They behave the same as users. === Products list -Now it is time to output a list of products (which could be displayed as the market place product catalog). This endpoint should be accessible without credentials. That means we don’t require the user to be logged-in to access the data. As usual we will start writing some tests: +Now it is time to output a list of products (which could be displayed as the market place product catalog). This endpoint should be accessible without credentials. That means we don’t require the user to be logged in to access the data. As usual, we will start writing some tests: [source,ruby] .test/controllers/api/v1/products_controller_test.rb @@ -341,7 +341,7 @@ Rails.application.routes.draw do end ---- -We are done for now with the public product endpoints. In the next sections we will focus on building the actions requiring a user to be logged in to access them. Said that we are committing this changes and continue. +We are done for now with the public product endpoints. In the next sections, we will focus on building the actions requiring a user to be logged in to access them. Said that we are committing these changes and continue. [source,bash] ---- @@ -382,11 +382,11 @@ class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest end ---- -Wow! We added a lot of code. If you remember previous section, tests are pretty similar than those for about user creation. Except for some minor changes. +Wow! We added a lot of code. If you remember the previous section, tests are pretty similar to those for about user creation. Except for some minor changes. In this way, we can see the user and create a product associated with them. But wait! There's something better. -If we adopt this approach, we can increase the scope of our authorization mechanism. We actually built the logic to get logged user from the header `Authorization` and assigned him a method `current_user`. It is therefore quite easy to set up by simply adding the authorization header to the request and retrieving the user from it. So let's do it: +If we adopt this approach, we can increase the scope of our authorization mechanism. We built the logic to get a logged user from the header `Authorization` and assigned him a method `current_user`. It is therefore quite easy to set up by simply adding the authorization header to the request and retrieving the user from it. So let's do it: [source,ruby] @@ -413,7 +413,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -As you can see, we protect the `create` action with the `check_login` method. We also build the product by associating the current user. I added this very simplistic method to the _concern_ `authenticable.rb`: +As you can see, we protect the `create` action with the `check_login` method. We also build the product by associating the current user. I added this very simple method to the _concern_ `authenticable.rb`: [source,ruby] .app/controllers/concerns/authenticable.rb @@ -454,7 +454,7 @@ $ rake test === Updating products -Hopefully by now you understand the logic to build the upcoming actions. In this section we will focus on the `update` action which will work similarly to the `create` one. We just need to fetch the product from the database and the update it. +Hopefully, by now you understand the logic to build the upcoming actions. In this section, we will focus on the `update` action which will work similarly to the `create` one. We just need to fetch the product from the database and update it. We first adding the action to the routes so we don’t forget later: @@ -471,7 +471,7 @@ Rails.application.routes.draw do end ---- -Before we start dropping some tests I just want to clarify that similarly to the `create` action we will scope the product to the `current_user`. In this case we want to make sure the product we are updating is owned by the current user. So we will fetch that product from the `user.products` association provided by Rails. +Before we start dropping some tests I just want to clarify that similarly to the `create` action we will scope the product to the `current_user`. In this case, we want to make sure the product we are updating is owned by the current user. So we will fetch that product from the `user.products` association provided by Rails. Let's add some specs: @@ -502,7 +502,7 @@ end ---- -NOTE: I have added a _fixture_ corresponding to a second user in order to verify that the second user cannot modify the first user's product. +NOTE: I have added a _fixture_ corresponding to a second user to verify that the second user cannot modify the first user's product. Tests may look complex but take a second peek. They are almost the same we built for users. @@ -561,7 +561,7 @@ $ rake test === Destroying products -Our last stop for the products endpoints will be the `destroy` action. You might now imagine how this would look like. The strategy in here will be pretty similar to the `create` and `update` actions: we'll get the logged user with JWT token and then fetch the product from the `user.products` association and finally destroy it, returning a `204` code. +Our last stop for the product endpoints will be the `destroy` action. You might now imagine how this would look like. The strategy in here will be pretty similar to the `create` and `update` actions: we'll get the logged user with JWT token and then fetch the product from the `user.products` association and finally destroy it, returning a `204` code. Let’s start again by adding the route name to the routes file: @@ -626,7 +626,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -As you can see the four lines implementation does the job. We can run tests to make sure everything is good and then we will commit the changes as we added a bunch of new code. Also make sure you hook this action to the `before_action` callback as with the `update` action. +As you can see the four lines implementation does the job. We can run tests to make sure everything is good and then we will commit the changes as we added a bunch of new code. Also, make sure you hook this action to the `before_action` callback as with the `update` action. [source,bash] ---- @@ -760,4 +760,3 @@ $ git merge chapter05 I hope you have enjoyed this chapter. It's a long one but the code we put together is an excellent base for the core app. In the next chapter, we will focus on customizing the output of user and product models using the gem https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. It will allow us to easily filter the attributes to display and manage associations such as embedded objects for example. - diff --git a/rails6/en/chapter06-improve-json.adoc b/rails6/en/chapter06-improve-json.adoc index 9b122e0..e394380 100644 --- a/rails6/en/chapter06-improve-json.adoc +++ b/rails6/en/chapter06-improve-json.adoc @@ -1,9 +1,9 @@ [#chapter06-improve-json] = Building JSON -In the previous chapter we added products to the application and built all the necessary routes. We have also associated a product with a user and restricted some of actions of `products_controller`. +In the previous chapter, we added products to the application and built all the necessary routes. We have also associated a product with a user and restricted some of the actions of `products_controller`. -Now you should be satisfied with all this work. But we still have a lot of work to do. Currently we have a JSON output that is not perfect. JSON output looks like this: +Now you should be satisfied with all this work. But we still have a lot of work to do. Currently, we have a JSON output that is not perfect. JSON output looks like this: [source,json] ---- @@ -22,9 +22,9 @@ Now you should be satisfied with all this work. But we still have a lot of work } ---- -However we want an output that does not contain the fields `user_id`, `created_at` and `updated_at`. +However, we want an output that does not contain the fields `user_id`, `created_at`, and `updated_at`. -An important (and difficult) part when creating your API is to decide the output format. Fortunately some organizations have already faced to this kind of problem and have established some conventions that you will discover in this chapter. +An important (and difficult) part when creating your API is to decide the output format. Fortunately, some organizations have already faced this kind of problem and have established some conventions that you will discover in this chapter. You can clone the project up to this point with: @@ -42,7 +42,7 @@ $ git checkout -b chapter06 == Presentation of https://jsonapi.org/[JSON:API] -An important and difficult part of creating your API is deciding the output format. Fortunately some conventions already exist. Certainly the most used of them is https://jsonapi.org/[JSON:API]. +An important and difficult part of creating your API is deciding the output format. Fortunately, some conventions already exist. Certainly, the most used of them is https://jsonapi.org/[JSON:API]. The https://jsonapi.org/format/#document-structure[JSON:API documentation] gives us some rules to follow regarding the formatting of the JSON document. @@ -58,7 +58,7 @@ The content of the `data` key is also quite strict: * properties of the objects must be placed in an `attributes` key * links of the objects must be placed in a `relationships` key -In this chapter we will customize the JSON output using Netflix's gem: https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. Luckily for use they already implements all https://jsonapi.org/[JSON:API] specifications. +In this chapter we will customize the JSON output using Netflix's gem: https://github.com/Netflix/fast_jsonapi[fast_jsonapi]. Luckily for us, they already implement all https://jsonapi.org/[JSON:API] specifications. So let's install the gem `fast_jsonapi`: @@ -73,7 +73,7 @@ You should be ready to continue with this tutorial. FastJSON API uses *serializers*. Serializers represent Ruby classes that will be responsible to converting a model into an https://ruby-doc.org/core-2.6.3/Hash.html[`Hash`] or a JSON. -So we need to add a `user_serializer.rb` file. We can do it manually but the gem provides a command line interface to do it: +So we need to add a `user_serializer.rb` file. We can do it manually but the gem provides a command-line interface to do it: [source,bash] ---- @@ -92,7 +92,7 @@ class UserSerializer end ---- -This _serializer_ will allow us to convert our `User` object to JSON which implements all JSON:API specifications. Because we specified `email` as `attributes` we retreive it in `data` array. +This _serializer_ will allow us to convert our `User` object to JSON which implements all JSON:API specifications. Because we specified `email` as `attributes` we retrieve it in `data` array. Let's try all this in the Rails with `rails console` console: @@ -102,7 +102,7 @@ Let's try all this in the Rails with `rails console` console: => {:data=>{:id=>"25", :type=>:user, :attributes=>{:email=>"tova@beatty.org"}}} ---- -There you go. As you can see this is really easy. Now we can use our new _serializer_ in our _controller_: +There you go. As you can see this is easy. Now we can use our new _serializer_ in our _controller_: .app/controllers/api/v1/users_controller.rb @@ -135,7 +135,7 @@ class Api::V1::UsersController < ApplicationController end ---- -Quite easy isn't it? However we should have a test who fails. Try it for yourself: +Quite easy isn't it? However, we should have a test that fails. Try it for yourself: [source,bash] ---- @@ -180,7 +180,7 @@ $ git add . && git commit -am "Adds user serializer for customizing the json out == Serialize products -Now that we understand how the serialization gem works it's time to customize the product output. The first step is the same as what we did in previous section. We need a product serializer. So let's do it: +Now that we understand how the serialization gem works it's time to customize the product output. The first step is the same as what we did in the previous section. We need product serializer. So let's do it: [source,bash] ---- @@ -285,11 +285,11 @@ class Product < ApplicationRecord end ---- -It is a good idea to integrate users into the JSON outputs of products. This will make the output more cumbersome but it will prevent the API client from executing other requests to retrieve user information related to the products. This method can really save you a huge bottleneck. +It is a good idea to integrate users into the JSON outputs of products. This will make the output more cumbersome but it will prevent the API client from executing other requests to retrieve user information related to the products. This method can save you a huge bottleneck. == Theory of the injection of relationships -Imagine a scenario where you go to the API to get the products, but in this case you have to display some of the user information. +Imagine a scenario where you go to the API to get the products, but in this case, you have to display some of the user information. One possible solution would be adding the attribute `user_id` to the `product_serializer` so that we can get the corresponding user later. This may sound like a good idea, but if you are concerned about performance, or if your database transactions are not fast enough, you should reconsider this approach. You must understand that for each product you retrieve, you will have to retrieve its corresponding user. @@ -297,7 +297,7 @@ Faced with this problem, there are several alternatives. === Integrate into a meta attribute -The first solution (a good one in my opinion) is to integrate identifiers of linkded users to products in a meta attribute. So we obtain a JSON like bellow: +The first solution (a good one in my opinion) is to integrate identifiers of linked users to products in a meta attribute. So we obtain a JSON like below: [source,json] ---- @@ -313,7 +313,7 @@ So that the client can retrieve these users from these `user_ids`. === Incorporate the object into the attribute -Another solution is to incorporate the `user` object into the `product` object. This may make the first request a little slower but in this way the client does not need to make another additional request. An example of the expected results is presented below: +Another solution is to incorporate the `user` object into the `product` object. This may make the first request a little slower but in this way, the client does not need to make another additional request. An example of the expected results is presented below: [source,json] ---- @@ -342,7 +342,7 @@ Another solution is to incorporate the `user` object into the `product` object. } ---- -The problem with this approach is we have to duplicate the `User' objects for each product that belong to the same user: +The problem with this approach is we have to duplicate the `User' objects for each product that belongs to the same user: [source,json] ---- @@ -396,7 +396,7 @@ The problem with this approach is we have to duplicate the `User' objects for ea The third solution (chosen by the JSON:API) is a mixture of the first two. -We will include all the relationships in an `include` key that will contain all the relationships of the previously mentioned objects. Also, each object will include a relationship key that defines the relationship and that must be found in the include key. +We will include all the relationships in an `include` key that will contain all the relationships of the previously mentioned objects. Also, each object will include a relationship key that defines the relationship and that must be found in the included key. A JSON is worth a thousand words: @@ -485,7 +485,7 @@ We are now checking three things on the JSON that has been returned: . it contains the user ID of the user linked to the product . the user data is included in the `include` key -NOTE: You may have noticed that I have chosen to use the method https://ruby-doc.org/core-2.6.3/Hash.html#method-i-dig[`Hash#dig`]. It is a Ruby method allowing you to retrieve elements in an nested _Hash_ by avoiding errors if an element is not present. +NOTE: You may have noticed that I have chosen to use the method https://ruby-doc.org/core-2.6.3/Hash.html#method-i-dig[`Hash#dig`]. It is a Ruby method allowing you to retrieve elements in a nested _Hash_ by avoiding errors if an element is not present. To pass this test we will start by including the relationship in the _serializer_: @@ -524,7 +524,7 @@ This addition will add a `relationship` key containing the user's identifier: } ---- -This allows us correcting our first two assertions. We now want to include attributes of the user who owns the product. To do this we simply need to pass an option `:include` to the _serializer_ instantiated in the _controller_. Then let's do it: +This allows us to correct our first two assertions. We now want to include attributes of the user who owns the product. To do this we simply need to pass an option `:include` to the _serializer_ instantiated in the _controller_. Then let's do it: [source,ruby] .app/controllers/api/v1/products_controller.rb @@ -539,7 +539,7 @@ class Api::V1::ProductsController < ApplicationController end ---- -There you go. Now this is what the JSON should look like: +There you go. Now, this is what the JSON should look like: [source,json] ---- @@ -628,7 +628,7 @@ class Api::V1::UsersController < ApplicationController end ---- -There you go. We obtain a JSON like following: +There you go. We obtain a JSON like the following: [source,json] ---- @@ -682,7 +682,7 @@ $ git commit -am "Add products relationship to user#show" == Search for products -In this last section we will continue to strengthen the `Products#index` action by setting up a very simple search mechanism allowing any customer to filter the results. This section is optional as it will have no impact on the application modules. But if you want to practice more with the TDD I recommend that you to complete this last step. +In this last section, we will continue to strengthen the `Products#index` action by setting up a very simple search mechanism allowing any customer to filter the results. This section is optional as it will have no impact on the application modules. But if you want to practice more with the TDD I recommend that you complete this last step. I use https://github.com/activerecord-hackery/ransack[Ransack] or https://github.com/casecommons/pg_search[pg_search] to build advanced search forms extremely quickly. But since the goal is learning and searching we are going to do is very simple. I think we can build a search engine from scratch. We simply have to consider the criteria by which we will filter the attributes. Hang on to your seats it's going to be a tough trip. @@ -753,7 +753,7 @@ class Product < ApplicationRecord end ---- -NOTE: _scoping_ allows you to specify commonly used queries that can be referenced as method calls on models. With these __scopes__ you can also link with Active Record methods like `where`, `joins` and `includes` because a _scope_ always returns an object https://api.rubyonrails.org/classes/ActiveRecord/Relation.html[`ActiveRecord::Relation`]. I invite you taking a look at https://guides.rubyonrails.org/active_record_querying.html#scopes_record_querying.html#scopes[Rails documentation] +NOTE: _scoping_ allows you to specify commonly-used queries that can be referenced as method calls on models. With these __scopes__ you can also link with Active Record methods like `where`, `joins`, and `includes` because a _scope_ always returns an object https://api.rubyonrails.org/classes/ActiveRecord/Relation.html[`ActiveRecord::Relation`]. I invite you to take a look at https://guides.rubyonrails.org/active_record_querying.html#scopes_record_querying.html#scopes[Rails documentation] Implementation is sufficient for our tests to pass: @@ -837,7 +837,7 @@ $ rake test ............................ ---- -As you can see, we haven't had many problems. Let's just add another _scope_ to sort the records by date of last update. In the case where the owner of the products decides to update some data he will surely want to sort his products by creation date. +As you can see, we haven't had many problems. Let's just add another _scope_ to sort the records by date of the last update. In the case where the owner of the products decides to update some data, he will surely want to sort his products by creation date. === Sort by creation date @@ -890,7 +890,7 @@ $ git commit -am "Adds search scopes on the product model" Now that we have the basis for the search engine we will use in the application, it is time to implement a simple but powerful search method. It will manage all the logic to retrieve the product records. -The method will consist in linking all the `scope` that we have previously built and returning the result. Let's start by adding some tests: +The method will consist of linking all the `scope` that we have previously built and returning the result. Let's start by adding some tests: [source,ruby] .test/models/product_test.rb @@ -980,4 +980,4 @@ $ git merge chapter06 == Conclusion -Until now it was easy thanks to the gem https://github.com/Netflix/fast_jsonapi_jsonapi[fast_jsonapi]. In the coming chapters we will start building the `Order` model that will involve users in the products. +Until now it was easy thanks to the gem https://github.com/Netflix/fast_jsonapi_jsonapi[fast_jsonapi]. In the coming chapters, we will start building the `Order` model that will involve users in the products. diff --git a/rails6/en/chapter07-placing-orders.adoc b/rails6/en/chapter07-placing-orders.adoc index 65ea292..1a2e2c9 100644 --- a/rails6/en/chapter07-placing-orders.adoc +++ b/rails6/en/chapter07-placing-orders.adoc @@ -1,7 +1,7 @@ [#chapter07-placing-orders] = Placing Orders -In previous chapter we handle associations between products and users and how serialize them in order to scale fast and easy. Now it is time to start placing orders which is going to be a more complex situation. We will handle associations between theses three models. We have to be smart enough to handle the JSON output we are delivering. +In the previous chapter, we handle associations between products and users and how to serialize them in order to scale fast and easy. Now it is time to start placing orders which is going to be a more complex situation. We will handle associations between these three models. We have to be smart enough to handle the JSON output we are delivering. In this chapter we will make several things which I list below: @@ -25,7 +25,7 @@ $ git checkout -b chapter07 == Modeling order -If you remember associations model, `Order` model is associated with users and products at the same time. Actually, It is really simple to achieve this in Rails. The tricky part is when it comes to serializing this object. I'll talk more about this in a next section. +If you remember the associations model, `Order` model is associated with users and products at the same time. Actually, It is really simple to achieve this in Rails. The tricky part is when it comes to serializing this object. I'll talk more about this in the next section. Let’s start by creating the order model with a special form: @@ -99,7 +99,7 @@ $ git add . && git commit -m "Generate orders" === Orders and Products -We need to setup the association between the `order` and the `product` and this is build with a *has-many-to-many* association. As many products will be placed on many orders and the orders will have multiple products. So in this case we need a model in the middle which will join these two other objects and map the appropriate association. +We need to setup the association between the `order` and the `product` and this is build with a *has-many-to-many* association. As many products will be placed on many orders and the orders will have multiple products. So in this case we need a model in the middle that will join these two other objects and map the appropriate association. Let’s generate this model: @@ -108,7 +108,7 @@ Let’s generate this model: $ rails generate model placement order:belongs_to product:belongs_to ---- -Let’s run migration on database: +Let’s run the migration on the database: [source,bash] ---- @@ -176,7 +176,7 @@ Let us first define what actions we will take: . A show action to retrieve a particular order from the current user . A creation action to actually place the order -Let's start with the action `index`. First we have to create the order controller: +Let's start with the action `index`. First, we have to create the order controller: [source,bash] ---- @@ -361,7 +361,7 @@ $ git commit -am "Adds the show action for order" === Placing an order -It is now time to give the user the opportunity to place some orders. This will add complexity to the application, but don't worry, we'll do it one step at a time. +It is now time to allow the user to place some orders. This will add complexity to the application but don't worry, we'll do it one step at a time. Before launching this feature, let's take the time to think about the implications of creating an order in the application. I'm not talking about setting up a transaction service like https://stripe.com/[Stripe] or https://www.braintreepayments.com/[Braintree] but things like: @@ -369,7 +369,7 @@ Before launching this feature, let's take the time to think about the implicatio * decrease in product inventory * add some validation for order placement to ensure that there are enough products at the time the order is placed -It seems like there's still a lot to do but believe me: you're closer than you think and it's not as hard as it looks. For now let's keep it simple and assume that we still have enough products to place any number of orders. We're just concerned about the server's response at the moment. +It seems like there's still a lot to do but believe me: you're closer than you think and it's not as hard as it looks. For now, let's keep it simple and assume that we still have enough products to place any number of orders. We're just concerned about the server's response at the moment. If you remember order model we need three things: @@ -415,7 +415,7 @@ end As you can see we are creating a `order_params` variable with the order data. Can you see the problem here? If not I’ll explain it later. Let’s just add the necessary code to make this test pass. -First we need to add the action to the resources on the routes file: +First, we need to add the action to the resources on the routes file: [source,ruby] .config/routes.rb @@ -465,11 +465,11 @@ $ rake test ---- -Ok, so we have everything nice and green. Now we should move on to the next chapter right? Let me stop you right there. We have some serious errors on the app, and they are not related to the code itself but on the business part. +Ok, so we have everything nice and green. Now we should move on to the next chapter, right? Let me stop you right there. We have some serious errors on the app, and they are not related to the code itself but on the business part. -Not because the tests are green, it means the app is filling the business part of the app. I wanted to bring this up because in many cases that's super easy just receiving params and building objects from those params thinking that we are always receiving the correct data. In this particular case we cannot rely on that, and the easiest way to see this, is that we are letting the client to set the order total, yeah crazy! +Not because the tests are green, it means the app is filling the business part of the app. I wanted to bring this up because in many cases that's super easy just receiving params and building objects from those params thinking that we are always receiving the correct data. In this particular case, we cannot rely on that, and the easiest way to see this is that we are letting the client set the order total, yeah crazy! -We have to add some validations or a callback to calculate the order total an set it through the model. This way we don’t longer receive that total attribute and have complete control on this attribute. So let’s do that. +We have to add some validations or a callback to calculate the order total an set it through the model. This way we don’t longer receive that total attribute and have complete control over this attribute. So let’s do that. We first need to add some specs for the order model: @@ -520,7 +520,7 @@ class Order < ApplicationRecord end ---- -At this point we are making sure the total is always present and bigger or equal to zero. This means we can remove those validations and remove the specs. I’ll wait. Our tests should be passing by now: +At this point, we are making sure the total is always present and bigger or equal to zero. This means we can remove those validations and remove the specs. I’ll wait. Our tests should be passing by now: [source,bash] ---- @@ -684,11 +684,11 @@ $ git merge chapter07 == Conclusion -That's it! You did it! You can applaud yourself. I know it's been a long time but believe me it's almost over. +That's it! You did it! You can applaud yourself. I know it's been a long time but believe me, it's almost over. -In next chapters we will continue working on the order template to add validations when placing an order. Some scenarios are: +In the next chapters, we will continue working on the order template to add validations when placing an order. Some scenarios are: * What happens when products are not available? * Decrease the quantity of the product in progress when placing an order -The next chapter will be short, but it is very important for health application. So don't skip it. +The next chapter will be short, but it is very important for the health of the application. So don't skip it. diff --git a/rails6/en/chapter08-improve-orders.adoc b/rails6/en/chapter08-improve-orders.adoc index 1cff410..b685efa 100644 --- a/rails6/en/chapter08-improve-orders.adoc +++ b/rails6/en/chapter08-improve-orders.adoc @@ -1,7 +1,7 @@ [#chapter08-improve_orders] = Improving orders -In previous chapter we extended our API to place orders and send a confirmation email to the user (just to improve the user experience). This chapter will take care of some validations on the order model, just to make sure it is placeable, just like: +In the previous chapter, we extended our API to place orders and send a confirmation email to the user (just to improve the user experience). This chapter will take care of some validations on the order model, just to make sure it is placeable, just like: - Decrement the current product quantity when an order is placed - What happens when products are not available? @@ -24,14 +24,14 @@ $ git checkout -b chapter08 == Decrementing product quantity -On this first stop we will work on update the product quantity to make sure every order will deliver the actual product. Currently the `product` model doesn’t have a `quantity` attribute. So let’s do that: +On this first stop, we will work on update the product quantity to make sure every order will deliver the actual product. Currently, the `product` model doesn’t have a `quantity` attribute. So let’s do that: [source,bash] ---- $ rails generate migration add_quantity_to_products quantity:integer ---- -Wait, don’t run migrations now. We'll making a small modification to it. As a good practice I like adding default values for the database just making sure I don’t mess things up with `null` values. This is a perfect case! +Wait, don’t run migrations now. We'll be making a small modification to it. As a good practice, I like adding default values for the database just making sure I don’t mess things up with `null` values. This is a perfect case! Your migration file should look like this: @@ -45,7 +45,7 @@ class AddQuantityToProducts < ActiveRecord::Migration[6.0] end ---- -Now we can migrate database: +Now we can migrate the database: [source,bash] ---- @@ -216,7 +216,7 @@ $ git add . $ git commit -m "Allows the order to be placed along with product quantity" ---- -Did you notice we are not saving the quantity for each product anywhere? There is no way to keep track of that. This can be fix really easy. Just adding a quantity attribute to the `Placement` model. So this way for each product we save its corresponding quantity. Let’s start by creating the migration: +Did you notice we are not saving the quantity for each product anywhere? There is no way to keep track of that. This can be easily fixed by just adding a quantity attribute to the `Placement` model. So this way for each product we save its corresponding quantity. Let’s start by creating the migration: [source,bash] ---- @@ -296,7 +296,7 @@ $ git add . && git commit -m "Adds quantity to placements" === Extending the Placement model -It is time to update the product quantity once the order is saved, or more accurate once the placement is created. In order to achieve this we are going to add a method and then hook it up to an `after_create` callback. +It is time to update the product quantity once the order is saved, or more accurate once the placement is created. To achieve this we are going to add a method and then hook it up to an `after_create` callback. [source,ruby] .test/models/placement_test.rb @@ -346,7 +346,7 @@ Since the beginning of the chapter, we have added the attribute `quantity` to th NOTE: You can consult https://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations[documentation]. -First we need adding a `validators` directory under the `app` directory (Rails will pick it up for so we do not need to load it). +First, we need to add a `validators` directory under the `app` directory (Rails will pick it up for so we do not need to load it). [source,bash] ---- @@ -371,7 +371,7 @@ class OrderTest < ActiveSupport::TestCase end ---- -As you can see on the spec, we first make sure that `placement_2` is trying to request more products than are available, so in this case the `order` is not supposed to be valid. +As you can see on the spec, we first make sure that `placement_2` is trying to request more products than are available, so in this case, the `order` is not supposed to be valid. The test by now should be failing, let’s turn it into green by adding the code for the validator: @@ -412,9 +412,9 @@ $ git add . && git commit -m "Adds validator for order with not enough products == Updating the total -Did you realize that the `total` is being calculated incorrectly, because currently it is just adding the price for the products on the order regardless of the quantity requested. Let me add the code to clarify the problem: +Did you realize that the `total` is being calculated incorrectly? This is because currently, it is just adding the price for the products on the order regardless of the quantity requested. Let me add the code to clarify the problem: -Currently in the `order` model we have this method to calculate the amount to pay: +Currently, in the `order` model we have this method to calculate the amount to pay: [source,ruby] .app/models/order.rb @@ -428,7 +428,7 @@ class Order < ApplicationRecord end ---- -Now instead of calculating the `total` by just adding the product prices we need to multiply it by the quantity. So let’s update the spec first: +Now instead of calculating the `total` by just adding the product prices, we need to multiply it by the quantity. So let’s update the spec first: [source,ruby] .test/models/order_test.rb @@ -494,4 +494,4 @@ $ git merge chapter08 Oh, you're here! Allow me to congratulate you! That's a long way from the first chapter. But you're one step closer. In fact, the next chapter will be the last. So try to make the most of it. -The last chapter will focus on the way to optimize the API using paging, caching and background tasks. So buckle up, it's going to be a hectic ride. +The last chapter will focus on the way to optimize the API using paging, caching, and background tasks. So buckle up, it's going to be a hectic ride. diff --git a/rails6/en/chapter09-optimization.adoc b/rails6/en/chapter09-optimization.adoc index 9043db4..055d66f 100644 --- a/rails6/en/chapter09-optimization.adoc +++ b/rails6/en/chapter09-optimization.adoc @@ -27,15 +27,15 @@ $ git checkout -b chapter09 == Pagination -A very common strategy to optimize an array of records from the database, is to load just a few by paginating them and if you are familiar with this technique you know that in Rails that's really easy to achieve it whether if you are using https://github.com/mislav/will_paginate[will_paginate] or https://github.com/amatsuda/kaminari[kaminari]. +A very common strategy to optimize an array of records from the database is to load just a few by paginating them and if you are familiar with this technique you know that in Rails that's really easy to achieve it whether if you are using https://github.com/mislav/will_paginate[will_paginate] or https://github.com/amatsuda/kaminari[kaminari]. -Then only tricky part in here is how are we suppose to handle the JSON output now to give enough information to the client on how the array is paginated. If you recall first chapter I shared some resources on the practices I was going to be following in here. One of them was http://jsonapi.org/ which is a must-bookmark page. +Then only tricky part in here is how are we suppose to handle the JSON output now to give enough information to the client on how the array is paginated. If you recall the first chapter I shared some resources on the practices I was going to be following in here. One of them was http://jsonapi.org/ which is a must-bookmark page. -If we read the format section we will reach a sub section called http://jsonapi.org/format/#document-structure-top-level[Top Level] and in very few words they mention something about pagination: +If we read the format section we will reach a sub-section called http://jsonapi.org/format/#document-structure-top-level[Top Level] and in very few words they mention something about pagination: > "meta": meta-information about a resource, such as pagination. -It is not very descriptive but at least we have a hint on what to look next about the pagination implementation, but don’t worry that is exactly what we are going to do in here. +It is not very descriptive but at least we have a hint on what to look next about the pagination implementation, but don’t worry that is exactly what we are going to do here. Let’s start with the `products` list. @@ -43,7 +43,7 @@ Let’s start with the `products` list. We are going to start nice and easy by paginating the products list as we don’t have any kind of access restriction which leads to easier testing. -First we need to add the https://github.com/amatsuda/kaminari[kaminari] gem to our `Gemfile`: +First, we need to add the https://github.com/amatsuda/kaminari[kaminari] gem to our `Gemfile`: [source,bash] ---- @@ -190,7 +190,7 @@ $ git commit -m "Adds pagination for the products index action to optimize respo === Orders list -Now it’s time to do exactly the same for the `orders` list endpoint which should be really easy to implement. But first let’s add some specs to the `orders_controller_test.rb` file: +Now it’s time to do exactly the same for the `orders` list endpoint which should be really easy to implement. But first, let’s add some specs to the `orders_controller_test.rb` file: [source,ruby] .test/controllers/api/v1/orders_controller_test.rb @@ -287,7 +287,7 @@ assert_not_nil json_response.dig(:links, :next) assert_not_nil json_response.dig(:links, :prev) ---- -In order to factor it, we will move these assertions into the `test_helper.rb` file in a method we will use: +In order to refactor it, we will move these assertions into the `test_helper.rb` file in a method we will use: [source,ruby] .test/test_helper.rb @@ -429,7 +429,7 @@ $ git commit -am "Factorize pagination" There is currently an implementation to do caching with the gem `fast_jsonapi` which is really easy to handle. Although in older versions of the gem, this implementation can change, it does the job. -If we make a request to the product list, we will notice that the response time takes about 174 milliseconds using cURL: +If we request the product list, we will notice that the response time takes about 174 milliseconds using cURL: [source,bash] ---- @@ -478,7 +478,7 @@ $ curl -w 'Total: %{time_total}\n' -o /dev/null -s http://localhost:3000/api/v1/ Total: 0,032341 ---- -So we went from 174 ms to 21 ms. The improvement is therefore enormous! Let’s commit our change a last time: +So we went from 174 ms to 21 ms. The improvement is therefore enormous! Let’s commit our changes: [source,ruby] ---- @@ -487,9 +487,9 @@ $ git commit -am "Adds caching for the serializers" == N+1 Queries -N+1* requests are a wound that can have a huge impact on the performance of an application. This phenomenon often occurs when using a **ORM** because it generates **automatically** SQL queries for us. This handy tool is double-edged because it can generate a **large number** of SQL queries. +N+1* requests are a wound that can have a huge impact on the performance of an application. This phenomenon often occurs when using an **ORM** because it generates **automatically** SQL queries for us. This handy tool is double-edged because it can generate a **large number** of SQL queries. -Something to know about SQL queries is that it's better limiting the number. In other words, a large request is often more efficient than a hundred small ones. +Something to know about SQL queries is that it's better to limit the number. In other words, a large request is often more efficient than a hundred small ones. Here is an example where we want to recover all users who have already created a product. Open the Rails console with `rails console` and execute the following Ruby code: @@ -560,7 +560,7 @@ With the help of the token obtained, we can now make a request to access the pro $ curl --header "Authorization=ey..." http://localhost:3000/api/v1/products ---- -You will most likely see several requests in the Rails console running the web server. +You will most likely see several requests in the Rails console running the webserver. [source,sql] ---- @@ -605,7 +605,7 @@ Rails.application.configure do end ---- -Restart the web server and restart the last request with cURL: +Restart the webserver and restart the last request with cURL: [source,bash] ----