Add User model

This commit is contained in:
Trevor Vallender 2024-04-14 11:10:10 +01:00
parent 1f52213c35
commit 2158426d88
9 changed files with 162 additions and 11 deletions

View File

@ -10,7 +10,7 @@ gem "importmap-rails"
gem "turbo-rails"
gem "stimulus-rails"
gem "jbuilder"
# gem "bcrypt", "~> 3.1.7"
gem "bcrypt", "~> 3.1.7"
gem "tzinfo-data", platforms: %i[ windows jruby ]
gem "bootsnap", require: false
# gem "image_processing", "~> 1.2"

View File

@ -78,6 +78,7 @@ GEM
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
base64 (0.2.0)
bcrypt (3.1.20)
bigdecimal (3.1.7)
bindex (0.8.1)
bootsnap (1.18.3)
@ -250,6 +251,7 @@ PLATFORMS
x86_64-linux
DEPENDENCIES
bcrypt (~> 3.1.7)
bootsnap
capybara
debug

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

@ -0,0 +1,31 @@
class User < ApplicationRecord
has_secure_password
generates_token_for :password_reset, expires_in: 4.hours do
password_salt.last(10) # Invalidates when password changed
end
validates :username,
presence: true,
uniqueness: true,
length: { minimum: 3, maximum: 20 }
normalizes :username, with: ->(username) { username.strip.downcase }
validates :email,
presence: true,
uniqueness: true,
length: { minimum: 5, maximum: 100 },
format: { with: URI::MailTo::EMAIL_REGEXP,
message: "must be a valid email address" }
normalizes :email, with: ->(email) { email.strip.downcase }
validates :first_name,
presence: true,
length: { maximum: 50 }
validates :last_name,
allow_nil: false,
length: { maximum: 50 }
def full_name
return first_name if last_name.blank?
"#{first_name} #{last_name}"
end
end

View File

@ -9,22 +9,16 @@ def system!(*args)
end
FileUtils.chdir APP_ROOT do
# This script is a way to set up or update your development environment automatically.
# This script is idempotent, so that you can run it at any time and get an expectable outcome.
# Add necessary setup steps to this file.
puts "== Installing dependencies =="
system! "gem install bundler --conservative"
system("bundle check") || system!("bundle install")
# puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml")
# FileUtils.cp "config/database.yml.sample", "config/database.yml"
# end
puts "\n== Preparing database =="
system! "bin/rails db:prepare"
puts "\n== Loading database fixtures =="
system! "bin/rails db:fixtures:load"
puts "\n== Removing old logs and tempfiles =="
system! "bin/rails log:clear tmp:clear"

View File

@ -0,0 +1,23 @@
class CreateUsers < ActiveRecord::Migration[7.1]
def change
create_table :users do |t|
t.string :username, null: false, limit: 20
t.string :password_digest, null: false, limit: 200
t.string :email, null: false, limit: 100
t.string :first_name, null: false, limit: 50
t.string :last_name, null: false, limit: 50, default: ""
t.timestamps
end
add_index :users, :username, unique: true
add_index :users, :email, unique: true
add_check_constraint :users, "length(username) >= 3",
name: "chk_username_min_length"
add_check_constraint :users, "length(email) >= 5",
name: "chk_email_min_length"
add_check_constraint :users, "length(first_name) >= 1",
name: "chk_first_name_min_length"
end
end

32
db/schema.rb generated Normal file
View File

@ -0,0 +1,32 @@
# 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[7.1].define(version: 2024_04_13_152553) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "users", force: :cascade do |t|
t.string "username", limit: 20, null: false
t.string "password_digest", limit: 200, null: false
t.string "email", limit: 100, null: false
t.string "first_name", limit: 50, null: false
t.string "last_name", limit: 50, default: "", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["username"], name: "index_users_on_username", unique: true
t.check_constraint "length(email::text) >= 5", name: "chk_email_min_length"
t.check_constraint "length(first_name::text) >= 1", name: "chk_first_name_min_length"
t.check_constraint "length(username::text) >= 3", name: "chk_username_min_length"
end
end

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

@ -0,0 +1,15 @@
trevor:
username: tsv
password_digest: <%= BCrypt::Password.create('password', cost: 5) %>
email: trevor@example.com
first_name: Trevor
last_name: Vallender
<% 1.upto(10) do |i| %>
user_<%= i %>:
username: <%= "user_#{i}" %>
password_digest: <%= BCrypt::Password.create('password', cost: 5) %>
email: <%= "user_#{i}@example.com" %>
first_name: <%= "User#{i}" %>
last_name: <%= "User#{i}" %>
<% end %>

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

@ -0,0 +1,48 @@
require "test_helper"
class UserTest < ActiveSupport::TestCase
test "full name returns first and last name" do
assert_equal "Trevor Vallender", users(:trevor).full_name
end
test "username must exist" do
assert_must_exist(users(:trevor), "username")
end
test "username must be unique" do
user1 = User.first
user2 = User.second
user1.username = user2.username
assert_not user1.valid?
end
test "email must exist" do
assert_must_exist(users(:trevor), "email")
end
test "email must be unique" do
user1 = User.first
user2 = User.second
user1.email = user2.email
assert_not user1.valid?
end
test "email must be a valid email" do
user = users(:trevor)
user.email = "trevor"
assert_not user.valid?
assert_includes user.errors[:email], "must be a valid email address"
end
test "first name must exist" do
assert_must_exist(users(:trevor), "first_name")
end
test "password reset token is invalid after password changed" do
user = users(:trevor)
token = user.generate_token_for(:password_reset)
assert_equal user, User.find_by_token_for(:password_reset, token)
user.update(password: "new_password")
assert_nil User.find_by_token_for(:password_reset, token)
end
end

View File

@ -10,6 +10,12 @@ module ActiveSupport
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
# Add more helper methods to be used by all tests here...
def assert_must_exist(record, field)
assert record.valid?
record.attributes = { "#{field}": nil }
assert_not record.valid?
assert_includes record.errors[field.to_sym], "can't be blank"
assert_raises { record.save(validate: false) }
end
end
end