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.
This commit is contained in:
songtianlun 2024-12-31 14:20:22 +08:00
parent 02b7393ed4
commit cd558466be
17 changed files with 169 additions and 5 deletions

View File

@ -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"

View File

@ -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

View File

@ -0,0 +1,4 @@
class UsersController < ApplicationController
def new
end
end

View File

@ -0,0 +1,2 @@
module UsersHelper
end

11
app/models/user.rb Normal file
View File

@ -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

View File

@ -7,7 +7,7 @@
Sample application.
</h2>
<%= link_to "Sing up now!", '#', class:"btn btn-lg btn-primary" %>
<%= link_to "Sing up now!", signup_path, class:"btn btn-lg btn-primary" %>
</div>
<%= link_to image_tag("rails.svg", alt:"Rails logo", width: "200"),
"https://rubyonrails.org/" %>

View File

@ -0,0 +1,3 @@
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<p>This will be a signup page for new users.</p>

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,5 @@
class AddIndexToUsersEmail < ActiveRecord::Migration[8.0]
def change
add_index :users, :email, unique: true
end
end

View File

@ -0,0 +1,5 @@
class AddPasswordDigestToUsers < ActiveRecord::Migration[8.0]
def change
add_column :users, :password_digest, :string
end
end

22
db/schema.rb generated Normal file
View File

@ -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

View File

@ -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

View File

@ -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

5
test/fixtures/users.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: MyString
email: MyString

76
test/models/user_test.rb Normal file
View File

@ -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

View File

@ -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