diff --git a/Gemfile b/Gemfile
index fc6d138..1873602 100644
--- a/Gemfile
+++ b/Gemfile
@@ -36,6 +36,8 @@ gem 'bcrypt', '~> 3.1.7'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
+gem 'rails-controller-testing'
+
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platform: :mri
diff --git a/Gemfile.lock b/Gemfile.lock
index 8f749b4..85f1804 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -103,6 +103,10 @@ GEM
bundler (>= 1.3.0, < 2.0)
railties (= 5.0.1)
sprockets-rails (>= 2.0.0)
+ rails-controller-testing (1.0.1)
+ actionpack (~> 5.x)
+ actionview (~> 5.x)
+ activesupport (~> 5.x)
rails-dom-testing (2.0.2)
activesupport (>= 4.2.0, < 6.0)
nokogiri (~> 1.6)
@@ -171,6 +175,7 @@ DEPENDENCIES
pry
puma (~> 3.0)
rails (~> 5.0.1)
+ rails-controller-testing
sass-rails (~> 5.0)
spring
spring-watcher-listen (~> 2.0.0)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1c07694..82974a2 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,3 +1,7 @@
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
+
+ def record_not_found
+ raise ActiveRecord::RecordNotFound.new('Not Found')
+ end
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
new file mode 100644
index 0000000..742ba2a
--- /dev/null
+++ b/app/controllers/users_controller.rb
@@ -0,0 +1,24 @@
+class UsersController < ApplicationController
+ def show
+ @user = User.find_by(slug: params[:slug]) or record_not_found
+ end
+
+ def new
+ @user = User.new
+ end
+
+ def create
+ @user = User.new(user_params)
+ if @user.save
+ redirect_to user_path(slug: @user.slug)
+ else
+ render :new
+ end
+ end
+
+ private
+
+ def user_params
+ params.require(:user).permit(:username, :email, :password, :password_confirmation)
+ end
+end
diff --git a/app/models/concerns/password_format.rb b/app/models/concerns/string_format.rb
similarity index 67%
rename from app/models/concerns/password_format.rb
rename to app/models/concerns/string_format.rb
index e863baf..4db7f79 100644
--- a/app/models/concerns/password_format.rb
+++ b/app/models/concerns/string_format.rb
@@ -1,6 +1,7 @@
-module PasswordFormat
- EIGHT_OR_MORE_CHARACTERS = /\A.{8,}\z/
- CONTAINS_A_DIGIT = /\d/
+module StringFormat
STARTS_WITH_NON_WHITESPACE = /\A\S/
ENDS_WITH_NON_WHITESPACE = /\S\z/
+ ONLY_PRINTABLE_CHARACTERS = /\A[[:print:]]*\z/
+ EIGHT_OR_MORE_CHARACTERS = /\A.{8,}\z/
+ CONTAINS_A_DIGIT = /\d/
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 7aaa78a..76e5919 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,21 +1,37 @@
class User < ApplicationRecord
before_save { email.downcase! }
+ before_validation :generate_slug
validates :username,
presence: true,
length: { maximum: 24 },
uniqueness: { case_sensitive: false }
+ validates :username, format: { with: StringFormat::ONLY_PRINTABLE_CHARACTERS,
+ message: 'can only contain printable characters' }
+ validates :username, format: { with: StringFormat::STARTS_WITH_NON_WHITESPACE,
+ message: 'can not start with whitespace' }
+ validates :username, format: { with: StringFormat::ENDS_WITH_NON_WHITESPACE,
+ message: 'can not end with whitespace' }
validates :email,
presence: true,
length: { maximum: 255 },
format: { with: EmailFormat::EMAIL },
uniqueness: { case_sensitive: false }
+ validates :slug,
+ presence: true,
+ uniqueness: true
has_secure_password
- validates :password, format: { with: PasswordFormat::EIGHT_OR_MORE_CHARACTERS,
+ validates :password, format: { with: StringFormat::EIGHT_OR_MORE_CHARACTERS,
message: 'must have 8 or more characters' }
- validates :password, format: { with: PasswordFormat::CONTAINS_A_DIGIT,
+ validates :password, format: { with: StringFormat::CONTAINS_A_DIGIT,
message: 'must have at least one digit' }
- validates :password, format: { with: PasswordFormat::STARTS_WITH_NON_WHITESPACE,
+ validates :password, format: { with: StringFormat::STARTS_WITH_NON_WHITESPACE,
message: 'can not start with whitespace' }
- validates :password, format: { with: PasswordFormat::ENDS_WITH_NON_WHITESPACE,
+ validates :password, format: { with: StringFormat::ENDS_WITH_NON_WHITESPACE,
message: 'can not end with whitespace' }
+
+ private
+
+ def generate_slug
+ self.slug ||= self.username.parameterize if self.username.present?
+ end
end
diff --git a/app/views/shared/_error_messages.html.erb b/app/views/shared/_error_messages.html.erb
new file mode 100644
index 0000000..f80053e
--- /dev/null
+++ b/app/views/shared/_error_messages.html.erb
@@ -0,0 +1,12 @@
+<% if @user.errors.any? %>
+
+
+ The form contains <%= pluralize(@user.errors.count, "error") %>.
+
+
+ <% @user.errors.full_messages.each do |msg| %>
+ - <%= msg %>
+ <% end %>
+
+
+<% end %>
diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb
new file mode 100644
index 0000000..16ca49d
--- /dev/null
+++ b/app/views/users/new.html.erb
@@ -0,0 +1,21 @@
+<% provide(:title, 'Sign Up') %>
+
+
Sign Up
+ <%= form_for(@user, url: signup_path) do |f| %>
+ <%= render 'shared/error_messages' %>
+
+ <%= f.label :username %>
+ <%= f.text_field :username %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.label :password %>
+ <%= f.password_field :password %>
+
+ <%= f.label :password_confirmation, "Password Again" %>
+ <%= f.password_field :password_confirmation %>
+
+ <%= f.submit "Sign me up!" %>
+ <% end %>
+
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
new file mode 100644
index 0000000..6b685c1
--- /dev/null
+++ b/app/views/users/show.html.erb
@@ -0,0 +1,11 @@
+
+
<%= @user.username %>
+
first_name: <%= @user.first_name %>
+
last_name: <%= @user.last_name %>
+
username: <%= @user.first_name %>
+
id: <%= @user.id %>
+
+
description: <%= @user.description %>
+
website: <%= @user.website %>
+
email: <%= @user.email %>
+
diff --git a/config/routes.rb b/config/routes.rb
index bc557ce..51247a1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,3 +1,7 @@
Rails.application.routes.draw do
root 'welcome#index'
+
+ resources :users, param: :slug, only: [:show]
+ get '/signup', to: 'users#new'
+ post '/signup', to: 'users#create'
end
diff --git a/db/migrate/20170111005636_add_slug_to_users.rb b/db/migrate/20170111005636_add_slug_to_users.rb
new file mode 100644
index 0000000..4001e43
--- /dev/null
+++ b/db/migrate/20170111005636_add_slug_to_users.rb
@@ -0,0 +1,5 @@
+class AddSlugToUsers < ActiveRecord::Migration[5.0]
+ def change
+ add_column :users, :slug, :string, unique: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index eee5e57..5572d3c 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170110022729) do
+ActiveRecord::Schema.define(version: 20170111005636) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -25,6 +25,7 @@
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
+ t.string "slug"
end
end
diff --git a/db/seeds.rb b/db/seeds.rb
index 1beea2a..1a49aa1 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -1,7 +1 @@
-# This file should contain all the record creation needed to seed the database with its default values.
-# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
-#
-# Examples:
-#
-# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
-# Character.create(name: 'Luke', movie: movies.first)
+User.create(username: 'Alex', password: 'alex@allegroplanet.com', password: 'pass1word', password_confirmation: 'pass1word')
diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb
new file mode 100644
index 0000000..b4ec4e1
--- /dev/null
+++ b/test/controllers/users_controller_test.rb
@@ -0,0 +1,51 @@
+require 'test_helper'
+
+class UsersControllerTest < ActionDispatch::IntegrationTest
+ def user
+ @user ||= User.first
+ end
+
+ test 'GET #show is successful' do
+ get user_path(user.slug)
+ assert_response :success
+ end
+
+ test 'GET #show renders the "show" template' do
+ get user_path(user.slug)
+ assert_template :show
+ end
+
+ test 'GET #new is successful' do
+ get signup_path
+ assert_response :success
+ end
+
+ test 'GET #new renders the "new" template' do
+ get signup_path
+ assert_template :new
+ end
+
+ test 'POST #create redirects to the user page successfuly' do
+ valid_new_user_params = {
+ username: 'Joe Valid',
+ email: 'valid@email.com',
+ password: 'valid1pass',
+ password_confirmation: 'valid1pass'
+ }
+
+ post signup_path, params: { user: valid_new_user_params }
+ assert_redirected_to '/users/joe-valid'
+ end
+
+ test 'POST #create renders the "new" template when invalid user params are passed' do
+ invalid_new_user_params = {
+ username: 'Joe InValid',
+ email: 'invalidemail',
+ password: 'pass',
+ password_confirmation: 'pass2'
+ }
+
+ post signup_path, params: { user: invalid_new_user_params }
+ assert_template :new
+ end
+end
diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml
index e804abe..b070b45 100644
--- a/test/fixtures/users.yml
+++ b/test/fixtures/users.yml
@@ -1,6 +1,7 @@
markoates:
username: markoates
email: marks@example.com
+ slug: markoates
first_name: Mark
last_name: Oates
website: www.example.com
diff --git a/test/models/user_test.rb b/test/models/user_test.rb
index d866b38..4bf8ccf 100644
--- a/test/models/user_test.rb
+++ b/test/models/user_test.rb
@@ -1,84 +1,126 @@
require 'test_helper'
class UserTest < ActiveSupport::TestCase
+
+ def user
+ @user ||= User.new(username: 'mr. test',
+ email: 'test@email.com',
+ password: 'pass1word',
+ password_confirmation: 'pass1word'
+ )
+ end
+
test 'creates a new user in the database' do
User.destroy_all
- new_user = User.create!(username: 'mr. test',
- email: 'test@email.com',
- password: 'pass1word',
- password_confirmation: 'pass1word'
- )
+ user.save
assert_equal User.count, 1
end
test 'username must be present' do
- new_user = User.create
- assert_includes new_user.errors[:username], "can't be blank"
+ user.username = ''
+ user.save
+ assert_includes user.errors[:username], "can't be blank"
end
test 'username can not be longer than 24 characters' do
- too_long_username = 'a' * 25
- new_user = User.create(username: too_long_username)
- assert_includes new_user.errors[:email], 'is invalid'
+ username_too_long = 'a' * 25
+ user.username = username_too_long
+ user.save
+ assert_includes user.errors[:username], 'is too long (maximum is 24 characters)'
end
test 'username must be unique' do
already_existing_username = users(:markoates).username
- new_user = User.create(username: already_existing_username)
- assert_includes new_user.errors[:username], 'has already been taken'
+ user.username = already_existing_username
+ user.save
+ assert_includes user.errors[:username], 'has already been taken'
+ end
+
+ test 'username must contain only printable characters' do
+ user.username = "\x0A"
+ user.save
+ assert_includes user.errors[:username], 'can only contain printable characters'
+ end
+
+ test 'username can not end in whitespace' do
+ user.username = 'endsinwhitespace '
+ user.save
+ assert_includes user.errors[:username], 'can not end with whitespace'
+ end
+
+ test 'username can not start with whitespace' do
+ user.username = ' startswithwhitespace'
+ user.save
+ assert_includes user.errors[:username], 'can not start with whitespace'
end
test 'email must be present' do
- new_user = User.create
- assert_includes new_user.errors[:email], "can't be blank"
+ user.email = ''
+ user.save
+ assert_includes user.errors[:email], "can't be blank"
end
test 'email must be less that 255 characters' do
too_long_email = 'a' * 256
- new_user = User.create(email: too_long_email)
- assert_includes new_user.errors[:email], 'is invalid'
+ user.email = too_long_email
+ user.save
+ assert_includes user.errors[:email], 'is invalid'
end
test 'email must be valid' do
- new_user = User.create(email: 'an_invalid%^&*email')
- assert_includes new_user.errors[:email], 'is invalid'
+ user.email = 'an_invalid%^&*email'
+ user.save
+ assert_includes user.errors[:email], 'is invalid'
end
test 'email must be unique' do
already_existing_email = users(:markoates).email
- new_user = User.create(email: already_existing_email)
- assert_includes new_user.errors[:email], 'has already been taken'
+ user.email = already_existing_email
+ user.save
+ assert_includes user.errors[:email], 'has already been taken'
end
test 'email is saved in lowercase' do
jumblecase_email = 'JuMbLeCaSe@EmAiL.CoM'
- new_user = User.create(username: 'Mrs. Jumble',
- email: jumblecase_email,
- password: 'pass1word',
- password_confirmation: 'pass1word'
- )
- new_user.save
- new_user.reload
- assert_includes new_user.email, 'jumblecase@email.com'
+ user.email = jumblecase_email
+ user.save
+ user.reload
+ assert_includes user.email, 'jumblecase@email.com'
end
test 'with a password less than 8 characters, is invalid' do
- new_user = User.create(password: 'pw2shrt')
- assert_includes new_user.errors[:password], 'must have 8 or more characters'
+ user.password = 'pw2shrt'
+ user.save
+ assert_includes user.errors[:password], 'must have 8 or more characters'
end
test 'with a password that does not contain at least one digit, is invalid' do
- new_user = User.create(password: 'nodigitshere')
- assert_includes new_user.errors[:password], 'must have at least one digit'
+ user.password = 'nodigits'
+ user.save
+ assert_includes user.errors[:password], 'must have at least one digit'
end
test 'with a password that starts with whitespace, is invalid' do
- new_user = User.create(password: ' startwithspace')
- assert_includes new_user.errors[:password], 'can not start with whitespace'
+ user.password = ' startwithspace'
+ user.save
+ assert_includes user.errors[:password], 'can not start with whitespace'
end
test 'with a password that ends with whitespace, is invalid' do
- new_user = User.create(password: 'endswithspace ')
- assert_includes new_user.errors[:password], 'can not end with whitespace'
+ user.password = 'endswithspace '
+ user.save
+ assert_includes user.errors[:password], 'can not end with whitespace'
+ end
+
+ test 'generates a slug on validation' do
+ user.save
+ assert_equal user.slug, 'mr-test'
+ end
+
+ test 'with a slug that already exists, is invalid' do
+ username_that_already_exists = users(:markoates).username
+ user.username = username_that_already_exists
+ user.save
+ assert_includes user.errors[:slug], 'has already been taken'
end
end