From cd558466be8a50ec96dc29b8395fff1abacb07e3 Mon Sep 17 00:00:00 2001 From: songtianlun Date: Tue, 31 Dec 2024 14:20:22 +0800 Subject: [PATCH] feat: add user registration functionality - Add User model with validations for name and email - Implement UsersController with new action for signup - Create views for user signup and home page - Update routes to include signup path - Add bcrypt gem for password security - Include tests for user model and controller actions This commit establishes the foundation for user registration in the application, ensuring proper validation and security measures are in place. It also enhances the user experience by providing a dedicated signup page. --- Gemfile | 2 + Gemfile.lock | 2 + app/controllers/users_controller.rb | 4 + app/helpers/users_helper.rb | 2 + app/models/user.rb | 11 +++ app/views/static_pages/home.html.erb | 2 +- app/views/users/new.html.erb | 3 + config/routes.rb | 2 + db/migrate/20241231022507_create_users.rb | 10 +++ ...20241231055957_add_index_to_users_email.rb | 5 ++ ...1231060757_add_password_digest_to_users.rb | 5 ++ db/schema.rb | 22 ++++++ .../static_pages_controller_test.rb | 8 +- test/controllers/users_controller_test.rb | 14 ++++ test/fixtures/users.yml | 5 ++ test/models/user_test.rb | 76 +++++++++++++++++++ test/test_helper.rb | 1 + 17 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 app/controllers/users_controller.rb create mode 100644 app/helpers/users_helper.rb create mode 100644 app/models/user.rb create mode 100644 app/views/users/new.html.erb create mode 100644 db/migrate/20241231022507_create_users.rb create mode 100644 db/migrate/20241231055957_add_index_to_users_email.rb create mode 100644 db/migrate/20241231060757_add_password_digest_to_users.rb create mode 100644 db/schema.rb create mode 100644 test/controllers/users_controller_test.rb create mode 100644 test/fixtures/users.yml create mode 100644 test/models/user_test.rb diff --git a/Gemfile b/Gemfile index a8f5c18..250069b 100644 --- a/Gemfile +++ b/Gemfile @@ -20,6 +20,8 @@ gem "bootstrap-sass", "~> 3.4.1" gem "sassc-rails", ">= 2.1.0" gem "jquery-rails", "~> 4.6.0" +gem 'bcrypt', '~> 3.1' + # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] # gem "bcrypt", "~> 3.1.7" diff --git a/Gemfile.lock b/Gemfile.lock index f83fe37..21fccc9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -79,6 +79,7 @@ GEM autoprefixer-rails (10.4.19.0) execjs (~> 2) base64 (0.2.0) + bcrypt (3.1.20) bcrypt_pbkdf (1.1.1) bcrypt_pbkdf (1.1.1-arm64-darwin) bcrypt_pbkdf (1.1.1-x86_64-darwin) @@ -433,6 +434,7 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES + bcrypt (~> 3.1) bootsnap bootstrap-sass (~> 3.4.1) brakeman diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000..2ec9ecc --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,4 @@ +class UsersController < ApplicationController + def new + end +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 0000000..2310a24 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..6c288ab --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,11 @@ +class User < ApplicationRecord + # before_save { self.email = email.downcase } + before_save { email.downcase! } + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: true + has_secure_password + validates :password, presence: true, length: { minimum: 6 } +end diff --git a/app/views/static_pages/home.html.erb b/app/views/static_pages/home.html.erb index 3939179..696fc08 100644 --- a/app/views/static_pages/home.html.erb +++ b/app/views/static_pages/home.html.erb @@ -7,7 +7,7 @@ Sample application. - <%= link_to "Sing up now!", '#', class:"btn btn-lg btn-primary" %> + <%= link_to "Sing up now!", signup_path, class:"btn btn-lg btn-primary" %> <%= link_to image_tag("rails.svg", alt:"Rails logo", width: "200"), "https://rubyonrails.org/" %> diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb new file mode 100644 index 0000000..9c841e8 --- /dev/null +++ b/app/views/users/new.html.erb @@ -0,0 +1,3 @@ +<% provide(:title, 'Sign up') %> +

Sign up

+

This will be a signup page for new users.

diff --git a/config/routes.rb b/config/routes.rb index 8cd3f39..6f6cf03 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do + get "users/new" get "static_pages/home" get "static_pages/help" get "static_pages/about" @@ -7,6 +8,7 @@ Rails.application.routes.draw do get '/help', to: 'static_pages#help' get '/about', to: 'static_pages#about' get '/contact', to: 'static_pages#contact' + get '/signup', to: 'users#new' root "static_pages#home" # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html diff --git a/db/migrate/20241231022507_create_users.rb b/db/migrate/20241231022507_create_users.rb new file mode 100644 index 0000000..3b80eb0 --- /dev/null +++ b/db/migrate/20241231022507_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/db/migrate/20241231055957_add_index_to_users_email.rb b/db/migrate/20241231055957_add_index_to_users_email.rb new file mode 100644 index 0000000..a8e4ed8 --- /dev/null +++ b/db/migrate/20241231055957_add_index_to_users_email.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersEmail < ActiveRecord::Migration[8.0] + def change + add_index :users, :email, unique: true + end +end diff --git a/db/migrate/20241231060757_add_password_digest_to_users.rb b/db/migrate/20241231060757_add_password_digest_to_users.rb new file mode 100644 index 0000000..81c7c9e --- /dev/null +++ b/db/migrate/20241231060757_add_password_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :password_digest, :string + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..94fd020 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,22 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 2024_12_31_060757) do + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "password_digest" + t.index ["email"], name: "index_users_on_email", unique: true + end +end diff --git a/test/controllers/static_pages_controller_test.rb b/test/controllers/static_pages_controller_test.rb index 4562acd..54aa596 100644 --- a/test/controllers/static_pages_controller_test.rb +++ b/test/controllers/static_pages_controller_test.rb @@ -9,25 +9,25 @@ class StaticPagesControllerTest < ActionDispatch::IntegrationTest test "should get root" do get root_path assert_response :success - assert_select "title", "Home | #{@base_title}" + assert_select "title", full_title("Home") end test "should get help" do get help_path assert_response :success - assert_select "title", "Help | #{@base_title}" + assert_select "title", full_title("Help") end test "Should get about" do get about_path assert_response :success - assert_select "title", "About | #{@base_title}" + assert_select "title", full_title("About") end test "Should get contact" do get contact_path assert_response :success - assert_select "title", "Contact | #{@base_title}" + assert_select "title", full_title("Contact") end end diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb new file mode 100644 index 0000000..8d9af29 --- /dev/null +++ b/test/controllers/users_controller_test.rb @@ -0,0 +1,14 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + test "should get new" do + get signup_path + assert_response :success + end + + test "Should get sign up title" do + get signup_path + assert_response :success + assert_select "title", full_title("Sign up") + end +end diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml new file mode 100644 index 0000000..46789ac --- /dev/null +++ b/test/fixtures/users.yml @@ -0,0 +1,5 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + email: MyString diff --git a/test/models/user_test.rb b/test/models/user_test.rb new file mode 100644 index 0000000..e1aee96 --- /dev/null +++ b/test/models/user_test.rb @@ -0,0 +1,76 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end + def setup + @user = User.new(name: "Example User", email: "user@example.com", + password: "foobar", password_confirmation: "foobar") + end + + test "should be valid" do + assert @user.valid? + end + + test "name should be present" do + @user.name = " " * 6 + assert_not @user.valid? + end + + test "email should be present" do + @user.email = " " * 6 + assert_not @user.valid? + end + + test "name should not be too long" do + @user.name = "a" * 51 + assert_not @user.valid? + end + + test "email should not be too long" do + @user.email = "a" * 244 + "@example.com" + assert_not @user.valid? + end + + test "email validation should accept valid addresses" do + valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org + first.last@foo.cn alice+bob@baz.cn] + valid_addresses.each do |valid_address| + @user.email = valid_address + assert @user.valid?, "#{valid_address.inspect} should be valid" + end + end + + test "email validation should reject invalid addresses" do + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. + foo@bar_baz.com foo@bar+baz.com foo@bar...cc] + invalid_addresses.each do |invalid_address| + @user.email = invalid_address + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" + end + end + + test "email addresses should be unique" do + duplicate_user = @user.dup + @user.save + assert_not duplicate_user.valid? + end + + test "email addresses should be saved as lower-case" do + mixed_case_email = "Foo@ExAMPle.CoM" + @user.email = mixed_case_email + @user.save + assert_equal mixed_case_email.downcase, @user.reload.email + end + + test "password should be present (non blank)" do + @user.password = @user.password_confirmation = " " * 6 + assert_not @user.valid? + end + + test "password should have a minimum length" do + @user.password = @user.password_confirmation = " " * 5 + assert_not @user.valid? + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 3daa94e..e10ed8c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -11,6 +11,7 @@ module ActiveSupport # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. fixtures :all + include ApplicationHelper # Add more helper methods to be used by all tests here... end