diff --git a/Gemfile b/Gemfile index b1a320395a..36ed6a484b 100644 --- a/Gemfile +++ b/Gemfile @@ -11,3 +11,12 @@ end group :development, :test do gem 'rubocop', '1.20' end + +gem "pg", "~> 1.5" + +gem "sinatra", "~> 3.0" +gem "sinatra-contrib", "~> 3.0" +gem "webrick", "~> 1.8" +gem "rack-test", "~> 2.1" + +gem "bcrypt", "~> 3.1" diff --git a/Gemfile.lock b/Gemfile.lock index 66064703c7..f413380a48 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,11 +3,21 @@ GEM specs: ansi (1.5.0) ast (2.4.2) + bcrypt (3.1.18) diff-lcs (1.4.4) docile (1.4.0) + multi_json (1.15.0) + mustermann (3.0.0) + ruby2_keywords (~> 0.0.1) parallel (1.20.1) parser (3.0.2.0) ast (~> 2.4.1) + pg (1.5.3) + rack (2.2.7) + rack-protection (3.0.6) + rack + rack-test (2.1.0) + rack (>= 1.3) rainbow (3.0.0) regexp_parser (2.1.1) rexml (3.2.5) @@ -36,6 +46,7 @@ GEM rubocop-ast (1.11.0) parser (>= 3.0.1.1) ruby-progressbar (1.11.0) + ruby2_keywords (0.0.5) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) @@ -46,21 +57,40 @@ GEM terminal-table simplecov-html (0.12.3) simplecov_json_formatter (0.1.3) + sinatra (3.0.6) + mustermann (~> 3.0) + rack (~> 2.2, >= 2.2.4) + rack-protection (= 3.0.6) + tilt (~> 2.0) + sinatra-contrib (3.0.6) + multi_json + mustermann (~> 3.0) + rack-protection (= 3.0.6) + sinatra (= 3.0.6) + tilt (~> 2.0) terminal-table (3.0.1) unicode-display_width (>= 1.1.1, < 3) + tilt (2.1.0) unicode-display_width (2.0.0) + webrick (1.8.1) PLATFORMS ruby DEPENDENCIES + bcrypt (~> 3.1) + pg (~> 1.5) + rack-test (~> 2.1) rspec rubocop (= 1.20) simplecov simplecov-console + sinatra (~> 3.0) + sinatra-contrib (~> 3.0) + webrick (~> 1.8) RUBY VERSION ruby 3.0.2p107 BUNDLED WITH - 2.2.26 + 2.4.12 diff --git a/app.rb b/app.rb new file mode 100644 index 0000000000..fa84c95861 --- /dev/null +++ b/app.rb @@ -0,0 +1,180 @@ +require 'sinatra/base' +require 'sinatra/reloader' +require_relative 'lib/peep_repository' +require_relative 'lib/maker_repository' +require_relative 'lib/database_connection' + +DatabaseConnection.connect + +class Application < Sinatra::Base + + enable :sessions + + configure :development do + register Sinatra::Reloader + also_reload 'lib/maker_repository' + also_reload 'lib/peep_repository' + end + + get '/' do + return erb(:homepage) + end + + get '/peeps' do + peep_repo = PeepRepository.new + @maker_repo = MakerRepository.new + @all_peeps = peep_repo.all.sort_by(&:date_posted).reverse + return erb(:peeps) + end + + get '/peeps/:id' do + if session[:id].nil? + return erb(:user_peeps_no_session) + else + peep_repo = PeepRepository.new + maker_repo = MakerRepository.new + @maker = maker_repo.find(session[:id]) + @makers_peeps = peep_repo.by_maker(session[:id]).sort_by(&:date_posted).reverse + return erb(:user_peeps) + end + end + + get '/signup' do + return erb(:signuppage) + end + + post '/signup' do + repo = MakerRepository.new + maker = Maker.new + maker.name = params[:name].strip + maker.username = params[:username].strip + maker.email_address = params[:email_address].strip + maker.password = params[:password].strip + + if signup_param_validation(maker.name, maker.username, maker.email_address, maker.password) + status 400 + else + repo.create(maker) + redirect '/loginpage' + end + end + + get '/loginpage' do + return erb(:loginpage) + end + + post '/loginpage' do + username = params[:username] + password = params[:password] + repo = MakerRepository.new + @maker = repo.find_by_username(username) + + stored_password = BCrypt::Password.new(@maker.password) + + if stored_password == password + session[:id] = @maker.id + return erb(:userpage) + else + return erb(:login_error) + end + + # if @maker.password == password + # session[:id] = @maker.id + # return erb(:userpage) + # else + # return erb(:login_error) + # end + end + + get '/userpage' do + if session[:id].nil? + return erb(:loginpage) + else + repo = MakerRepository.new + @maker = repo.find(session[:id]) + return erb(:userpage) + end + end + + get '/peep/new' do + if session[:id].nil? + return erb(:post_peep_no_session) + else + return erb(:new_peep) + end + end + + post '/peep/new' do + time = Time.new + repo = PeepRepository.new + peep = Peep.new + peep.title = params[:title].strip + peep.content = params[:content].strip + peep.date_posted = time + peep.maker_id = session[:id] + + if title_valid?(peep.title) + status 400 + else + repo.create(peep) + return erb(:peep_created) + end + end + + get '/delete_peep' do + if session[:id].nil? + return erb(:delete_no_session) + else + return erb(:delete_peep) + end + end + + post '/delete_peep' do + title = params[:title] + repo = PeepRepository.new + @selected = repo.find_by_title(title) + + if @selected.maker_id != session[:id] + status 400 # add an error stating you cannot delete a peep which you have not posted. + else + id = @selected.id + repo.delete(id) + return erb(:peep_deleted) + end + end + + get '/update_details' do + if session[:id].nil? + return erb(:update_no_session) + else + return erb(:update_maker) + end + end + + post '/update_details' do + @new_name = params[:name] + repo = MakerRepository.new + maker = repo.find(session[:id]) + maker.name = @new_name + repo.update(maker) + return erb(:updated_maker) + end + + get '/logout' do + session[:id] = nil + return redirect('/') + end + + def title_valid?(title) + repo = PeepRepository.new + return repo.all.any? { |row| row.title == title } || title.empty? || title.nil? + end + + def signup_param_validation(name, username, email_address, password) + repo = MakerRepository.new + all = [name, username, email_address, password] + return (repo.all.any? { |row| row.username == username } || + repo.all.any? { |row| row.email_address == email_address } || + all.any?(nil) || all.any?(&:empty?)) + end +end diff --git a/config.ru b/config.ru new file mode 100644 index 0000000000..af14ef717e --- /dev/null +++ b/config.ru @@ -0,0 +1,2 @@ +require './app' +run Application diff --git a/lib/database_connection.rb b/lib/database_connection.rb new file mode 100644 index 0000000000..3d2a00d6a0 --- /dev/null +++ b/lib/database_connection.rb @@ -0,0 +1,21 @@ +require 'pg' + +class DatabaseConnection + def self.connect + if ENV['ENV'] == 'test' + database_name = 'chitter_base_test' + else + database_name = 'chitter_base' + end + @connection = PG.connect({ host: '127.0.0.1', dbname: database_name }) + end + + def self.exec_params(query, params) + if @connection.nil? + raise 'DatabaseConnection.exec_params: Cannot run a SQL query as the connection to'\ + 'the database was never opened. Did you make sure to call first the method '\ + '`DatabaseConnection.connect` in your app.rb file (or in your tests spec_helper.rb)?' + end + @connection.exec_params(query, params) + end +end diff --git a/lib/maker.rb b/lib/maker.rb new file mode 100644 index 0000000000..049edb5d9c --- /dev/null +++ b/lib/maker.rb @@ -0,0 +1,3 @@ +class Maker + attr_accessor :id, :name, :username, :email_address, :password +end diff --git a/lib/maker_repository.rb b/lib/maker_repository.rb new file mode 100644 index 0000000000..3c61694bff --- /dev/null +++ b/lib/maker_repository.rb @@ -0,0 +1,57 @@ +require_relative 'maker' +require 'bcrypt' + +class MakerRepository + + def all + result = DatabaseConnection.exec_params('SELECT * FROM makers;', []) + makers = [] + result.each do |row| + maker = Maker.new + maker.id = row['id'].to_i + maker.name = row['name'] + maker.username = row['username'] + maker.email_address = row['email_address'] + maker.password = row['password'] + makers << maker + end + makers + end + + def find_by_username(username) + sql = 'SELECT * FROM makers WHERE username = $1;' + result = DatabaseConnection.exec_params(sql, [username]) + maker = Maker.new + maker.id = result[0]['id'].to_i + maker.name = result[0]['name'] + maker.username = result[0]['username'] + maker.email_address = result[0]['email_address'] + maker.password = result[0]['password'] + return maker + end + + def find(id) + sql = 'SELECT * FROM makers WHERE id = $1;' + result = DatabaseConnection.exec_params(sql, [id]) + maker = Maker.new + maker.id = result[0]['id'].to_i + maker.name = result[0]['name'] + maker.username = result[0]['username'] + maker.email_address = result[0]['email_address'] + maker.password = result[0]['password'] + return maker + end + + def create(maker_obj) + encrypted_password = BCrypt::Password.create(maker_obj.password) + sql = 'INSERT INTO makers(name, username, email_address, password) VALUES($1, $2, $3, $4);' + params = [maker_obj.name, maker_obj.username, maker_obj.email_address, encrypted_password] + result = DatabaseConnection.exec_params(sql, params) + end + + def update(maker) + sql = 'UPDATE makers SET name = $1 WHERE id = $2;' + params = [maker.name, maker.id] + update = DatabaseConnection.exec_params(sql, params) + end +end diff --git a/lib/peep.rb b/lib/peep.rb new file mode 100644 index 0000000000..3fdaf4f4a1 --- /dev/null +++ b/lib/peep.rb @@ -0,0 +1,3 @@ +class Peep + attr_accessor :id, :title, :content, :date_posted, :maker_id +end diff --git a/lib/peep_repository.rb b/lib/peep_repository.rb new file mode 100644 index 0000000000..a2ec27d5c0 --- /dev/null +++ b/lib/peep_repository.rb @@ -0,0 +1,57 @@ +require_relative 'peep' + +class PeepRepository + + def all + result = DatabaseConnection.exec_params('SELECT * FROM peeps;', []) + peeps = [] + result.each do |row| + peep = Peep.new + peep.id = row['id'].to_i + peep.title = row['title'] + peep.content = row['content'] + peep.date_posted = row['date_posted'] + peep.maker_id = row['maker_id'].to_i + peeps << peep + end + peeps + end + + def by_maker(maker_id) + result = DatabaseConnection.exec_params('SELECT * FROM peeps WHERE maker_id = $1;', [maker_id]) + maker_peeps = [] + result.each do |row| + peep = Peep.new + peep.id = row['id'].to_i + peep.title = row['title'] + peep.content = row['content'] + peep.date_posted = row['date_posted'] + peep.maker_id = row['maker_id'].to_i + maker_peeps << peep + end + maker_peeps + end + + def find_by_title(title) + result = DatabaseConnection.exec_params('SELECT * FROM peeps WHERE title = $1;', [title]) + peep = Peep.new + peep.id = result[0]['id'].to_i + peep.title = result[0]['title'] + peep.content = result[0]['content'] + peep.date_posted = result[0]['date_posted'] + peep.maker_id = result[0]['maker_id'].to_i + return peep + end + + def create(peep_obj) + sql = 'INSERT INTO peeps(title, content, date_posted, maker_id) VALUES($1, $2, $3, $4);' + params = [peep_obj.title, peep_obj.content, peep_obj.date_posted, peep_obj.maker_id] + result = DatabaseConnection.exec_params(sql, params) + end + + def delete(id) + sql = 'DELETE FROM peeps WHERE id = $1;' + params = [id] + result = DatabaseConnection.exec_params(sql, params) + end +end diff --git a/public/bob.jpg b/public/bob.jpg new file mode 100644 index 0000000000..acd373addf Binary files /dev/null and b/public/bob.jpg differ diff --git a/public/logo.jpg b/public/logo.jpg new file mode 100644 index 0000000000..9e24cdd553 Binary files /dev/null and b/public/logo.jpg differ diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000000..040523f8fa --- /dev/null +++ b/public/style.css @@ -0,0 +1,37 @@ +head { + background-color: lightblue; + text-align: center; +} + +body { + background-color: lightblue; + text-align: center; + padding: 10px; + border: 5px; + font-size: 20px; + font-family: fantasy; +} + +h1 { + color: #2982ff; + border: 3px solid #2982ff; + padding: 10px; + font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +button { + font-size: 20px; + background-color: #2982ff; + color: lightblue; + border-radius: 12px; + border-color: #2982ff; + padding: 10px 20px; + border: none; + text-decoration: underline; + transition-duration: 0.3s; + cursor: pointer; +} + +button:hover { + background-color: lightblue; +} diff --git a/recipe.md b/recipe.md new file mode 100644 index 0000000000..91173ca0db --- /dev/null +++ b/recipe.md @@ -0,0 +1,294 @@ +# Chitter Model and Repository Classes Design Recipe + +## 1. Design and Create the table + +User Stories/specification: + +> As a Maker +> So that I can let people know what I am doing +> I want to post a message (peep) to chitter + +> As a maker +> So that I can see what others are saying +> I want to see all peeps in reverse chronological order + +> As a Maker +> So that I can better appreciate the context of a peep +> I want to see the time at which it was made + +> As a Maker +> So that I can post messages on Chitter as me +> I want to sign up for Chitter + +> As a Maker +> So that only I can post messages on Chitter as me +> I want to log in to Chitter + +> As a Maker +> So that I can avoid others posting messages on Chitter as me +> I want to log out of Chitter + + +#### relationship: + +1. Can one Maker have many Peeps ? YES + +-> A Maker HAS MANY Peeps +-> A Peep BELONGS TO a Maker +-> Therefore, the foreign key is on the Peeps table. + +Table design: +Makers: id | name | username | email_address | password +Peeps: id | title | content | date_posted | maker_id + +id: SERIAL +name: text +username: text +email_address: text +password: text + +id: SERIAL +title: text +content: text +posted: timestamp +maker_id: int + + +#### Creating the tables: + +```sql +CREATE TABLE makers ( + id SERIAL PRIMARY KEY, + name text, + username text, + email_address text, + password text +); + +CREATE TABLE peeps ( + id SERIAL PRIMARY KEY, + title text, + content text, + date_posted timestamp, + maker_id int, + constraint fk_maker foreign key (maker_id) + references makers(id) + on delete cascade +); +``` + +Create table: +psql -h 127.0.0.1 chitter_base < DB_table_setup.sql + + +## 2. Create the SQL seeds +```sql +-- file: spec/chitter_seeds.sql) + +TRUNCATE TABLE makers RESTART IDENTITY CASCADE; +TRUNCATE TABLE peeps RESTART IDENTITY CASCADE; + +INSERT INTO makers(name, username, email_address, password) VALUES('Matty Boi', 'MattyMooMilk', 'mattys_fake_email@tiscali.net', 'Password1!'); +INSERT INTO makers(name, username, email_address, password) VALUES('Hayley Lady', 'HayleyOk', 'another_fake_email420@gmail.com', 'DifferentPassword123.'); + + +INSERT INTO peeps(title, content, date_posted, maker_id) VALUES('My first ever peep', 'Internet is crazy', '12-12-2023 04:05:06', 1); +INSERT INTO peeps(title, content, date_posted, maker_id) VALUES('Hayleys peep', 'Another test peep', '12-12-2023 04:05:06', 2); + +``` + +psql -h 127.0.0.1 your_database_name < seeds_{table_name}.sql + +## 3. Define the class names + +```ruby +# Model class' +# (in lib/maker.rb) + +class Maker +end + +# (in lib/peep.rb) +class Peep +end + +# Repository class' +# (in lib/maker_repository.rb) +class MakerRepository +end + +# (in lib/peep_repository.rb) +class PeepRepository +end +``` + +## 4. Implement the Model class' + +``` ruby + +# Model class +# (in lib/maker.rb) + +class Maker + attr_accessor :id, :name, :username, :email_address, :password +end + +# Model class 2 +# (in lib/peep.rb) + +class Peep + attr_accessor :id, :title, :content, :date_posted, :maker_id +end + +``` + +## 5. Define the Repository Class' interface + +```ruby + +# Table name: makers +# Repository class +# (in lib/maker_repository.rb) + +class MakerRepository + + # Selecting all records + def all + # Executes the SQL query: + # SELECT * FROM makers; + # Returns all records as an array of user objects. - to be used by other methods in app.rb. + end + + # Adds a maker/user to the makers table in DB + def create(maker_obj) + # adds a user object to the database + # 'INSERT INTO makers(name, username, email_address, password) VALUES($1, $2, $3, $4);' + # params = [maker_obj.name, maker_obj.username, maker_obj.email_address, maker_obj.password] + # No return + end +end + +#Table name: peeps +#This repository class acts on order objects +# (in lib/peep_repository.rb) +class PeepRepository + + def all + # Returns an array of all peep objects + # Method can be called by other methods later (Will need to sort peeps by timestamp) + # Executes the SQL query: + # SELECT * FROM peeps; + end + + # Adds a new peep to the database: + def create(peep_obj) + # Adds a postobj to the posts table if post corresponds to existing user + # 'INSERT INTO peeps(title, content, date_posted, maker_id) VALUES($1, $2, $3, $4);' + # params = [peep_obj.title, peep_obj.content, peep_obj.date_posted, peep_obj.maker_id] + # No return + end + +# Deletes a peep from the database + def delete(id) + # 'DELETE FROM peeps WHERE id = $1;' + # params = [id] + end +end +``` + +## 6. Write Test Examples +These examples will later be encoded as RSpec tests. + +```ruby + +# EXAMPLES + +# 1. Get all Peeps +repo = PeepRepository.new +repo.all.length => # returns correct integer dependent on how many rows are in DB +repo.all.first.id => #always will return 1 +repo.all.last.id => #should return the same int as first test line +repo[2].title => #returns the title of object at index 2 (assuming there are 3 objs in array) +repo[2].date_posted => #returns the correct timestamp. + +# Get all Makers: +repo = MakerRepository.new +repo.all.length => # returns correct integer dependent on how many rows are in DB +repo.all.first.id => #always will return 1 +repo.all.last.id => #should return the same int as first test line +repo[2].name => #returns the customer name of object at index 2 (assuming there are 3 objs in array) + + +# if database is empty should return => [] + + +# 2. Add a Maker to database (Create account) +repo = MakerRepository.new +maker_1 = Maker.new # => can be a double => double :maker, name: 'name', username: 'username', email_address: 'fake_email@gmail.com', password: 'Password123' +maker_1.name = 'name' +maker_1.username = 'username' +maker_1.email_address = 'fake_email@gmail.com' +maker_1.password = 'Password123' +repo.create(maker_1) +expect(repo.all.last.name).to eq 'name' +expect(repo.all.last.username).to eq 'username' +expect(repo.all.last.email_address).to eq 'fake_email@gmail.com' +expect(repo.all.last.password).to eq 'Password123' +expect(repo.all.length) # => an integer 1 greater than our current length +expect(repo.all.last.id).to eq # => ^ same integer as previous expect line ^ + +# Add a peep to database (Create a peep) +repo = PeepRepository.new +peep = Peep.new # => can be a double +peep.title = '' +peep.content = '' +peep.date_posted = '' +peep.maker_id = 1 +repo.create(peep) +expect(repo.all.last.content).to eq +expect(repo.all.last.date_posted).to eq +expect(repo.all.length) # => an integer 1 greater than our current length +expect(repo.all.last.id).to eq # => ^ same integer as previous expect line ^ + +# Delete a peep +repo = PeepRepository.new +repo.delete(1) +expect(repo.all.length) # => an integer 1 fewer than our current length +expect(repo.all.first.id).to eq # => 2 + +``` + +## 7. Reload the SQL seeds before each test run +Running the SQL code present in the seed file will empty the table and re-insert the seed data. + +```ruby + +# file: spec/maker_repository_spec.rb + + def reset_makers_table + seed_sql = File.read('spec/chitter_seeds.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'chitter_base_test' }) + connection.exec(seed_sql) + end + + before(:each) do + reset_makers_table + end +end + +# file: spec/peep_repository_spec.rb + + def reset_peeps_table + seed_sql = File.read('spec/chitter_seeds.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'chitter_base_test' }) + connection.exec(seed_sql) + end + + before(:each) do + reset_peeps_table + end +end +``` + +## 8. Test-drive and implement the Repository class behaviour +_After each test you write, follow the test-driving process of red, green, refactor to implement the behaviour._ \ No newline at end of file diff --git a/routes_recipe.md b/routes_recipe.md new file mode 100644 index 0000000000..9812681896 --- /dev/null +++ b/routes_recipe.md @@ -0,0 +1,29 @@ + +Homepage: +> "Welcome to Chitter" - banner/header +> "Log in" - href to a log in form that allows a user to log into an existing account - GET +> "Sign Up" - href hyperlink to a sign up form which creates a new Maker obj and adds to DB - POST +> "View all peeps" - GET - Displays all existing peeps from all Makers + +Sign up page: +> form with all required fields to add a new - POST +> If successful takes to a newly created "User page" +> Failures: If username or email_address already exist within DB. + +Log in page: +> Form with username and password sections - GET +> If successful takes to an existing "User page" +> Failures: If email_address or username used for log in does not exist in database +If password does not correspond with existing username/email_address + +User page: +> "Welcome #{user/maker}" +> "Create a peep" - POST - adds a peep to the database related to this user/maker id +> "Delete a peep" - POST - deletes a peep from the database +> "View all previous peeps" - GET - Shows all peeps related to this user - params[id] +> "Log out" - GET - returns to homepage + +All peeps page: +> (Does not require an existing account to view all peeps) +> Displays all existing peeps in order of most latest timestamp. +> Each peep shows name of maker who created it, their username and a timestamp. diff --git a/spec/chitter_seeds.sql b/spec/chitter_seeds.sql new file mode 100644 index 0000000000..ee130efb27 --- /dev/null +++ b/spec/chitter_seeds.sql @@ -0,0 +1,10 @@ +TRUNCATE TABLE makers RESTART IDENTITY CASCADE; +TRUNCATE TABLE peeps RESTART IDENTITY CASCADE; + +INSERT INTO makers(name, username, email_address, password) VALUES('Matty Boi', 'MattyMooMilk', 'mattys_fake_email@tiscali.net', 'Password1!'); +INSERT INTO makers(name, username, email_address, password) VALUES('Hayley Lady', 'HayleyOk', 'another_fake_email420@gmail.com', 'DifferentPassword123.'); + +INSERT INTO peeps(title, content, date_posted, maker_id) VALUES('My first ever peep', 'Internet is crazy', '2023-07-10 04:05:06 +0000', 1); +INSERT INTO peeps(title, content, date_posted, maker_id) VALUES('Hayleys peep', 'Another test peep', '2023-07-21 12:25:12 +0000', 2); +INSERT INTO peeps(title, content, date_posted, maker_id) VALUES('A second peep', 'Theres a peep party now', '2023-06-07 13:17:26 +0000', 1); +INSERT INTO peeps(title, content, date_posted, maker_id) VALUES('Hayleys alternate peep', 'peep to be deleted', '2023-06-29 17:44:43 +0000', 2); diff --git a/spec/empty_tables.sql b/spec/empty_tables.sql new file mode 100644 index 0000000000..881d296c9c --- /dev/null +++ b/spec/empty_tables.sql @@ -0,0 +1,2 @@ +TRUNCATE TABLE makers RESTART IDENTITY CASCADE; +TRUNCATE TABLE peeps RESTART IDENTITY CASCADE; diff --git a/spec/integration/app_spec.rb b/spec/integration/app_spec.rb new file mode 100644 index 0000000000..38df86441a --- /dev/null +++ b/spec/integration/app_spec.rb @@ -0,0 +1,326 @@ +require "spec_helper" +require "rack/test" +require_relative '../../app' + +describe Application do + + include Rack::Test::Methods + let(:app) { Application.new } + + context '/' do + it 'Should return a html formatted web page including a welcome banner' do + response = get('/') + expect(response.status).to eq(200) + expect(response.body).to include ('
Please return to the login page and try again
+Total peeps: <%= @all_peeps.length %>
++ <%= @maker_repo.find(peep.maker_id).username %> +
++ <%= peep.date_posted %> +
++ -------------------------- +
+ <% end %> +To post a new peep please login or create an account
+To update your account details you must be logged in
+You have updated your name to <%= @new_name %>
++ <%= peep.date_posted %> +
++ -------------------------- +
+ <% end %> +To view all of your existing peeps please login or create an account to start peeping
+