[RUBY] (Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 6]

Premise

・ Rails tutorial is the 4th edition ・ This study is the 3rd lap (2nd lap after Chapter 9) ・ The author is a beginner who has done all of Progate.

Basic policy

・ If you read it, you will not understand it. ・ Search and summarize terms that you do not understand (at the bottom of the article, glossary). ・ Dive into what you do not understand. ・ Work on all exercises. ・ Do not copy chords as much as possible.

Next, we will enter Chapter 6. From here to Chapter 12, it seems that we will start developing a login and authentication system. It's a long time, but let's do it.

Click here for today's song. 17 years old and the wall of Berlin "Prism" I haven't cultivated music so much in the last few years, but a good domestic shoegaze band will come out.

[6.1.1 Database migration exercise]

  1. Rails uses a file called schema.rb in the db / directory. It is used to keep track of the structure of the database (called the schema). Now, check the contents of db / schema.rb in your environment and compare it with the contents of the migration file (Listing 6.2). → I don't know what the correct answer is, but the contents of the migration file are reflected.

2. Almost all migrations can be undone (at least in this tutorial you can undo all migrations). Undoing is called "rollback" and can be achieved in Rails with the command db: rollback.

$ rails db:rollback

After running the above command, check the contents of db / schema.rb to see if the rollback was successful (Column 3.1 also summarizes other techniques for migration, so please refer to it. Please give me). The above command internally calls the drop_table command to drop the users table from the database. This works because the change method knows that drop_table and create_table correspond to each other. Knowing this correspondence makes it easy to achieve reverse migration for rollback. In the case of irreversible migration such as deleting a certain column, it is necessary to define up and down methods separately instead of the change method. For more information, see Active Record Migration in the Rails Guide. → Execute rails db: migrate. The contents of the schema have disappeared.

schema.rb


ActiveRecord::Schema.define(version: 0) do

end
  1. Execute the rails db: migrate command again and check that the contents of db / schema.rb have been restored. → I'm back!

[6.1.2 model file exercise]

  1. Open the Rails console and make sure that User.new creates an object of class User, and that the object inherits from ApplicationRecord (hint: use the technique introduced in 4.4.4). Please try).
  2. In the same way, check that ApplicationRecord inherits ActiveRecord :: Base. → Execute the following on the console.
>> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
>> user.class
=> User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
>> user.class.superclass
=> ApplicationRecord(abstract)
>> user.class.superclass.superclass
=> ActiveRecord::Base

[6.1.3 Exercise to create a user object]

  1. Make sure that both user.name and user.email are instances of the String class. → Aiyo.
>> user.name.class
=> String
>> user.email.class
=> String

2. Which class instances are created_at and updated_at? → Both ActiveSupport :: TimeWithZone class (This article may be helpful)

>> user.created_at.class
=> ActiveSupport::TimeWithZone
>> user.updated_at.class
=> ActiveSupport::TimeWithZone

[6.1.4 Exercise to search for user objects]

  1. Try searching for a user object using name. Also, make sure you can use the find_by_name method (old Rails applications will often see the old type of find_by). → Below
>> User.find_by(name: "shoji")
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "shoji"], ["LIMIT", 1]]
=> #<User id: 2, name: "shoji", email: "[email protected]", created_at: "2020-09-08 22:54:09", updated_at: "2020-09-08 22:54:09">
>> User.find_by_name("shoji")
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "shoji"], ["LIMIT", 1]]
=> #<User id: 2, name: "shoji", email: "[email protected]", created_at: "2020-09-08 22:54:09", updated_at: "2020-09-08 22:54:09">

2. For practical purposes, User.all can be treated like an array, but it is not actually an array. Examine the objects generated by User.all and make sure they are in the User :: ActiveRecord_Relation class instead of the Array class. → Below (I'm also checking up to the upper class)

>> users = User.all
  User Load (0.2ms)  SELECT  "users".* FROM "users" LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "miura", email: "[email protected]", created_at: "2020-09-08 22:53:59", updated_at: "2020-09-08 22:53:59">, #<User id: 2, name: "shoji", email: "[email protected]", created_at: "2020-09-08 22:54:09", updated_at: "2020-09-08 22:54:09">]>
>> users.class
=> User::ActiveRecord_Relation
>> users.class.superclass
=> ActiveRecord::Relation
>> users.class.superclass.superclass
=> Object
>> users.class.superclass.superclass.superclass
=> BasicObject
>> users.class.superclass.superclass.superclass.superclass
=> nil

3. Make sure that when you call the length method on User.all, you will be asked for that length (4.2.3). One of the properties of Ruby is that you can somehow understand how to handle objects without knowing the class in detail. This is called duck typing and is often expressed in the following maxim: "If you look like a duck and sound like a duck, it's already a duck." By the way, in the keynote speech of RubyKaigi 2016, Ruby author Matz explained duck typing. It's a short and easy-to-understand explanation for a few minutes, so please watch it!) → The number of data is displayed.

>> User.all.length
  User Load (0.2ms)  SELECT "users".* FROM "users"
=> 2

[6.1.5 Exercise to update user objects]

  1. Update using the name attribute using the assignment to the user object, and save it with save. → Below
>> user1.name = "yongon"
=> "yongon"
>> user1.save
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.6ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "yongon"], ["updated_at", "2020-09-08 23:12:52.428275"], ["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true

2. Now try updating and saving the email attributes using update_attributes. → The following (the id of making various mistakes is incorrect)

>> user1.update_attributes(name: "yongon", email: "[email protected]")
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.1ms)  UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["email", "[email protected]"], ["updated_at", "2020-09-09 03:12:33.687572"], ["id", 4]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true

3. In the same way, please check that the magic column created_at can also be updated directly. Tip: It is convenient to use "1.year.ago" when updating. This is one of the Rails-style time specifications, and it calculates the time one year ago from the current time. → Below

>> user1.update_attribute(:created_at, 1.year.ago)
   (0.1ms)  SAVEPOINT active_record_1
  SQL (1.1ms)  UPDATE "users" SET "created_at" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["created_at", "2019-09-09 03:18:24.829284"], ["updated_at", "2020-09-09 03:18:24.830017"], ["id", 4]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true

[6.2.1 Notes and exercises to verify effectiveness]

If you use the setup method, the process written in the method will be executed just before the test. If you define an instance variable in this, you can use it in all tests.

  1. From the console, let's check that the newly created user object is valid. → Below
>> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
>> user.valid?
=> true
  1. Let's check if the user object created in 6.1.3 is also valid. → The console is closed once, so it disappears. I will omit it because it is effective anyway.

[6.2.2 Notes and exercises to verify existence]

assert_not @ user.valid? Is RED → It's easy to understand if you think that "@user is valid, isn't it?", But there is a tsukkomi saying "Valid!". As it appears in validates, {} can be omitted when passing a hash as the last argument of the method.

  1. Create a new user u and check that it is invalid at the time of creation. Why isn't it valid? Let's check the error message. → Validation is working because neither name nor email has been entered.
>> u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
>> u.valid?
=> false
>> u.errors.full_messages
=> ["Name can't be blank", "Email can't be blank"]

2. Execute u.errors.messages and check that the errors can be obtained in hash format. How can I get only the error information about email? → I wrote on this page. . messages It's the same with or without it.

>> u.errors.messages
=> {:name=>["can't be blank"], :email=>["can't be blank"]}
>> u.errors[:email]
=> ["can't be blank"]
>> u.errors.messages[:email]
=> ["can't be blank"]

[6.2.3 Exercise to verify length]

  1. Create a user object with a name and email attribute that is too long and check that it is not valid.
  2. What kind of error message will be generated when the validation regarding the length fails? Please check. → Collectively below
>> user = User.new(name: "a"*55, email: "e"*244 + "@example.com")
=> #<User id: nil, name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...", email: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee...", created_at: nil, updated_at: nil>
>> user.valid?=> false
>> user.errors.full_messages=> ["Name is too long (maximum is 50 characters)", "Email is too long (maximum is 255 characters)"]

[6.2.4 Exercise to verify the format]

Do I need to remember regular expressions? It's complicated, so it's better to check and implement it each time.

  1. Try posting the list of valid email addresses in Listing 6.18 and the list of invalid email addresses in Listing 6.19 to Your test string: in Rubular. Then try posting the regular expression in Listing 6.21 to Your regular expression: to make sure that only valid email addresses match and not all invalid email addresses. → Just check.

2. As mentioned earlier, the regular expression that checks the email address in Listing 6.21 allows invalid email addresses with consecutive dots, such as [email protected]. First, add this email address to the list of invalid email addresses in Listing 6.19 and make sure this causes the test to fail. Then use the slightly more complex regular expression shown in Listing 6.23 to verify that this test passes. → Just do this too.   3. Add [email protected] to the list of Rubular email addresses and try using the regular expression in Listing 6.23 in Rubular. Let's make sure that only valid email addresses match and not all invalid email addresses. → Confirmed.

[6.2.5 Notes and exercises to verify uniqueness]

The content here is a little confusing, but the point is that the email address is set to be case insensitive. Does the test make sure that uppercase users are not valid (the same address for uppercase)? And a callback method appeared to make everything lowercase before the email address was stored in the database. After a quick look, the use of callbacks seems to be careful. It seems that you should avoid conditional branching in the callback.

  1. Let's add a test to lowercase email addresses to Listing 6.26, as shown in Listing 6.33. By the way, in the test code to be added, the reload method that updates according to the value in the database and the assert_equal method that checks whether the values match are used. To see if the tests in Listing 6.33 are working, comment out the before_save line to make it red, and uncomment it to make it green. → Execute as instructed. Since it is converted to lowercase before saving with before_save, it will be RED when commented out and GREEN when canceled.

  2. Let's rewrite the before_save callback to email.downcase! While checking the execution result of the test suite. Tip: You can change the email attribute directly by adding a! To the end of the method (Listing 6.34). → The "!" That appears here represents a destructive process. This means that your email address will remain converted to lowercase. Even if it is rewritten, the test is GREEN.

[6.3.2 Exercise for users to have a secure password]

  1. At this point, even if you give a valid name and email address to the user object, please make sure that it fails with valid ?.
  2. Why does it fail? Check the error message. → I'll put it together! You can't have a blank password.
>> user = User.new(name: "kote", email: "[email protected]")
=> #<User id: nil, name: "kote", email: "[email protected]", created_at: nil, updated_at: nil, password_digest: nil>
>> user.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "[email protected]"], ["LIMIT", 1]]
=> false
>> user.errors.messages
=> {:password=>["can't be blank"]}

[6.3.3 Minimum password number of characters exercise]

  1. Even with a valid name and email address, make sure that the user object will not be valid if the password is too short.
  2. What kind of error message will you get if you fail above? Let's check. → I will summarize this time as well! Of course, if your password is too short, you will get angry. You can see that has_secure_password works here and the password is hashed.
>> user = User.new(name: "kote", email: "[email protected]", password: "kotte")
=> #<User id: nil, name: "kote", email: "[email protected]", created_at: nil, updated_at: nil, password_digest: "$2a$10$7Svz/KnRoF7zab0PnhKFL.n/OsSltRvvREHECcmuq.D...">
>> user.valid?  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "[email protected]"], ["LIMIT", 1]]
=> false
>> user.errors.messages=> {:password=>["is too short (minimum is 6 characters)"]}

[6.3.4 User creation and authentication exercise]

  1. Restart the console once (delete the user object) and try searching for the user object created in this section. → Below
>> user = User.find(1)
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "[email protected]", created_at: "2020-09-10 02:37:56", updated_at: "2020-09-10 02:37:56", password_digest: "$2a$10$A5n.HFBigQfwnWVJZw2N0e4M9sxPaR8ndLZwqtZWYS7...">

2. If you can find the object, replace the name with a new character string and update it with the save method. It doesn't work ... why didn't it work? → Below. Since the save method tries to update all attributes, it seems that an error is thrown when updating the password.

>> user.name = "meshino"
=> "meshino"
>> user.save
   (0.1ms)  begin transaction
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ?  [["email", "[email protected]"], ["id", 1], ["LIMIT", 1]]
   (0.0ms)  rollback transaction
=> false
>> user.errors.messages
=> {:password=>["can't be blank", "is too short (minimum is 6 characters)"]}

3. Now try updating the user's name using the technique introduced in 6.1.5. → So, update using update_attribute. (However, since the user who threw the error earlier is being reused, it is not valid even after updating the name. It seems that the password needs to be reset.)

>> user.update_attribute(:name, "nakamura")
   (0.1ms)  begin transaction
  SQL (4.2ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "nakamura"], ["updated_at", "2020-09-10 03:07:11.190666"], ["id", 1]]
   (6.4ms)  commit transaction
=> true
>> user.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ?  [["email", "[email protected]"], ["id", 1], ["LIMIT", 1]]
=> false
>> user.errors.messages=> {:password=>["can't be blank", "is too short (minimum is 6 characters)"]}

Chapter 6 Summary

-Create a migration and migrate each time you update the database. -Active Record is convenient. Rails is going down, but Active Record was written somewhere with its advantages. -Set invalid input contents in validation and play. ・ I think it's enough to recognize that regular expressions are probably such things. -Improved search efficiency by adding an index to the database. Uniqueness is also guaranteed. -Since has_secure_password is used by gem, I wonder if it is possible to implement secure processing with other convenient gems. When I searched for it, it came out after all. Let's learn from now on.

It takes time because I am investigating various concerns by detouring. However, I don't think that the content of the tutorial alone will work, so I will be interested in various other things and absorb them. Well next! Chapter 7 is the implementation of user registration! sign up!

Go to Chapter 7! Click here for Chapter 5 Click here for premise and author status for learning

A glossary that somehow captures the image

・ Assert_not Since it is denied by not, it means the opposite. If the target is true, it fails, if it is false, it succeeds.

· Active Record callback You can set a method to call before / after some processing. For more information, go to the Rails Guide. ](Https://railsguides.jp/active_record_callbacks.html)

·stub A substitute to prepare at the time of testing. The substitute called from the process under test is the stub, and the substitute that calls the process under test is the driver.

Recommended Posts

(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 11]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 1]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 14]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 12]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 5]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 3]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 4]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 8]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 6]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 13]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 9]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 10]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 7]
(Giri) A local government employee in his twenties works on a Rails tutorial [Chapter 2]
(Giri) A local government employee in his twenties works on a Rails tutorial [Introduction]
[Ruby on Rails Tutorial] Error in the test in Chapter 3
[Rails Tutorial Chapter 5] Create a layout
[Rails Struggle/Rails Tutorial] What you learned in Rails Tutorial Chapter 6
[Rails Struggle/Rails Tutorial] What you learned in Rails Tutorial Chapter 3
(Ruby on Rails6) Creating data in a table
[Rails tutorial] A memorandum of "Chapter 11 Account Activation"
rails tutorial Chapter 6
rails tutorial Chapter 1
rails tutorial Chapter 7
rails tutorial Chapter 5
rails tutorial Chapter 10
rails tutorial Chapter 9
rails tutorial Chapter 8
[Rails Tutorial Chapter 2] What to do when you make a mistake in the column name
Rails Tutorial Chapter 5 Notes
Rails Tutorial Memorandum (Chapter 3, 3.1)
Rails Tutorial Chapter 4 Notes
Rails Tutorial Chapter 4 Learning
Rails Tutorial Chapter 1 Learning
Rails Tutorial Chapter 2 Learning
Rails Tutorial Chapter 8 Notes
Rails Tutorial Memorandum (Chapter 3, 3.3.2)
Difficulties in building a Ruby on Rails environment (Windows 10) (SQLite3)
How to display a graph in Ruby on Rails (LazyHighChart)
Apply CSS to a specific View in Ruby on Rails