diff --git a/.byebug_history b/.byebug_history new file mode 100644 index 0000000..34b17d4 --- /dev/null +++ b/.byebug_history @@ -0,0 +1,5 @@ +exit +params[:id] +params[:id +@user.email +@user.name diff --git a/Gemfile b/Gemfile index d0ca1fd..f0760a6 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,15 @@ source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '4.2.6' +gem 'bootstrap-sass', '3.3.6' +gem 'bcrypt', '3.1.11' +gem 'faker', '1.4.2' +gem 'carrierwave', '0.10.0' +gem 'mini_magick', '3.8.0' +gem 'fog', '1.23.0' +gem 'net-ssh' +gem 'will_paginate', '3.0.7' +gem 'bootstrap-will_paginate', '0.0.10' # Use sqlite3 as the database for Active Record gem 'sqlite3' # Use SCSS for stylesheets diff --git a/Gemfile.lock b/Gemfile.lock index 200a3f2..36690ab 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -37,10 +37,23 @@ GEM thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) arel (6.0.3) + autoprefixer-rails (6.3.6.2) + execjs + bcrypt (3.1.11) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) + bootstrap-sass (3.3.6) + autoprefixer-rails (>= 5.2.1) + sass (>= 3.3.4) + bootstrap-will_paginate (0.0.10) + will_paginate builder (3.2.2) byebug (9.0.5) + carrierwave (0.10.0) + activemodel (>= 3.2.0) + activesupport (>= 3.2.0) + json (>= 1.7) + mime-types (>= 1.16) coffee-rails (4.1.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.1.x) @@ -51,10 +64,37 @@ GEM concurrent-ruby (1.0.2) debug_inspector (0.0.2) erubis (2.7.0) + excon (0.51.0) execjs (2.7.0) + faker (1.4.2) + i18n (~> 0.5) + fog (1.23.0) + fog-brightbox + fog-core (~> 1.23) + fog-json + fog-softlayer + ipaddress (~> 0.5) + nokogiri (~> 1.5, >= 1.5.11) + fog-brightbox (0.11.0) + fog-core (~> 1.22) + fog-json + inflecto (~> 0.0.2) + fog-core (1.42.0) + builder + excon (~> 0.49) + formatador (~> 0.2) + fog-json (1.0.2) + fog-core (~> 1.0) + multi_json (~> 1.10) + fog-softlayer (1.1.2) + fog-core + fog-json + formatador (0.2.5) globalid (0.3.6) activesupport (>= 4.1.0) i18n (0.7.0) + inflecto (0.0.2) + ipaddress (0.8.3) jbuilder (2.5.0) activesupport (>= 3.0.0, < 5.1) multi_json (~> 1.2) @@ -70,9 +110,12 @@ GEM mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) + mini_magick (3.8.0) + subexec (~> 0.2.1) mini_portile2 (2.1.0) minitest (5.9.0) multi_json (1.12.1) + net-ssh (3.2.0) nokogiri (1.6.8) mini_portile2 (~> 2.1.0) pkg-config (~> 1.1.7) @@ -126,6 +169,7 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.3.11) + subexec (0.2.3) thor (0.19.1) thread_safe (0.3.5) tilt (2.0.5) @@ -141,15 +185,24 @@ GEM binding_of_caller (>= 0.7.2) railties (>= 4.0) sprockets-rails (>= 2.0, < 4.0) + will_paginate (3.0.7) PLATFORMS ruby DEPENDENCIES + bcrypt (= 3.1.11) + bootstrap-sass (= 3.3.6) + bootstrap-will_paginate (= 0.0.10) byebug + carrierwave (= 0.10.0) coffee-rails (~> 4.1.0) + faker (= 1.4.2) + fog (= 1.23.0) jbuilder (~> 2.0) jquery-rails + mini_magick (= 3.8.0) + net-ssh rails (= 4.2.6) sass-rails (~> 5.0) sdoc (~> 0.4.0) @@ -158,6 +211,7 @@ DEPENDENCIES turbolinks uglifier (>= 1.3.0) web-console (~> 2.0) + will_paginate (= 3.0.7) BUNDLED WITH 1.12.5 diff --git a/app/assets/images/kitten.jpg b/app/assets/images/kitten.jpg new file mode 100644 index 0000000..e224540 Binary files /dev/null and b/app/assets/images/kitten.jpg differ diff --git a/app/assets/images/rails.png b/app/assets/images/rails.png new file mode 100644 index 0000000..f9fa0a0 Binary files /dev/null and b/app/assets/images/rails.png differ diff --git a/app/assets/javascripts/account_activation.coffee b/app/assets/javascripts/account_activation.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/account_activation.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index e07c5a8..1019580 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,5 +12,6 @@ // //= require jquery //= require jquery_ujs +//= require bootstrap //= require turbolinks //= require_tree . diff --git a/app/assets/javascripts/password_resets.coffee b/app/assets/javascripts/password_resets.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/password_resets.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/relationships.coffee b/app/assets/javascripts/relationships.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/relationships.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/sessions.coffee b/app/assets/javascripts/sessions.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/sessions.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/account_activation.scss b/app/assets/stylesheets/account_activation.scss new file mode 100644 index 0000000..709c646 --- /dev/null +++ b/app/assets/stylesheets/account_activation.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the AccountActivation controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss new file mode 100644 index 0000000..6c1f644 --- /dev/null +++ b/app/assets/stylesheets/custom.scss @@ -0,0 +1,295 @@ +@import "bootstrap-sprockets"; +@import "bootstrap"; + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + +/* universal */ + +body { + padding-top: 60px; +} + +section { + overflow: auto; +} + +textarea { + resize: vertical; +} + +.center { + text-align: center; + h1 { + margin-bottom: 10px; + } +} + +/* typography */ + +h1, h2, h3, h4, h5, h6 { + line-height: 1; +} + +h1 { + font-size: 3em; + letter-spacing: -2px; + margin-bottom: 30px; + text-align: center; +} + +h2 { + font-size: 1.2em; + letter-spacing: -1px; + margin-bottom: 30px; + text-align: center; + font-weight: normal; + color: $gray-light; +} + +p { + font-size: 1.1em; + line-height: 1.7em; +} + + +/* header */ + +#logo { + float: left; + margin-right: 10px; + font-size: 1.7em; + color: white; + text-transform: uppercase; + letter-spacing: -1px; + padding-top: 9px; + font-weight: bold; + &:hover { + color: white; + text-decoration: none; + } +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: $gray-light; + a { + color: $gray; + &:hover { + color: $gray-darker; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + + +@mixin box_sizing { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* miscellaneous */ + +.debug_dump { + clear: both; + float: left; + width: 100%; + margin-top: 45px; + @include box_sizing; +} + +/* sidebar */ + +aside { + section.user_info { + margin-top: 20px; + } + section { + padding: 10px 0; + margin-top: 20px; + &:first-child { + border: 0; + padding-top: 0; + } + span { + display: block; + margin-bottom: 3px; + line-height: 1; + } + h1 { + font-size: 1.4em; + text-align: left; + letter-spacing: -1px; + margin-bottom: 3px; + margin-top: 0px; + } + } +} + +.gravatar { + float: left; + margin-right: 10px; +} + +.gravatar_edit { + margin-top: 15px; +} + +.gravatar { + float: left; + margin-right: 10px; +} +.gravatar_edit { + margin-top: 15px; +} +.stats { + overflow: auto; + margin-top: 0; + padding: 0; + a { + float: left; + padding: 0 10px; + border-left: 1px solid $gray-lighter; + color: gray; + &:first-child { + padding-left: 0; + border: 0; + } + &:hover { + text-decoration: none; + color: blue; + } + } + strong { + display: block; + } +} +.user_avatars { + overflow: auto; + margin-top: 10px; + .gravatar { + margin: 1px 1px; + } + a { + padding: 0; + } +} +.users.follow { + padding: 0; +} + +/* forms */ + +input, textarea, select, .uneditable-input { + border: 1px solid #bbb; + width: 100%; + margin-bottom: 15px; + @include box_sizing; +} + +input { + height: auto !important; +} + +#error_explanation { + color: red; + ul { + color: red; + margin: 0 0 30px 0; + } +} + +.field_with_errors { + @extend .has-error; + .form-control { + color: $state-danger-text; + } +} + +.checkbox { + margin-top: -10px; + margin-bottom: 10px; + span { + margin-left: 20px; + font-weight: normal; + } +} +#session_remember_me { + width: auto; + margin-left: 0; +} + +/* Users index */ +.users { + list-style: none; + margin: 0; + li { + overflow: auto; + padding: 10px 0; + border-bottom: 1px solid $gray-lighter; + } +} + +/* microposts */ +.microposts { + list-style: none; + padding: 0; + li { + padding: 10px 0; + border-top: 1px solid #e8e8e8; + } + .user { + margin-top: 5em; + padding-top: 0; + } + .content { + display: block; + margin-left: 60px; + img { + display: block; + padding: 5px 0; + } + } + .timestamp { + color: $gray-light; + display: block; + margin-left: 60px; + } + .gravatar { + float: left; + margin-right: 10px; + margin-top: 5px; + } +} +aside { + textarea { + height: 100px; + margin-bottom: 5px; + } +} +span.picture { + margin-top: 10px; + input { + border: 0; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/password_resets.scss b/app/assets/stylesheets/password_resets.scss new file mode 100644 index 0000000..eb9649c --- /dev/null +++ b/app/assets/stylesheets/password_resets.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the PasswordResets controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/relationships.scss b/app/assets/stylesheets/relationships.scss new file mode 100644 index 0000000..ca5c640 --- /dev/null +++ b/app/assets/stylesheets/relationships.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Relationships controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/sessions.scss b/app/assets/stylesheets/sessions.scss new file mode 100644 index 0000000..ccb1ed2 --- /dev/null +++ b/app/assets/stylesheets/sessions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Sessions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/account_activation_controller.rb b/app/controllers/account_activation_controller.rb new file mode 100644 index 0000000..b79c99e --- /dev/null +++ b/app/controllers/account_activation_controller.rb @@ -0,0 +1,14 @@ +class AccountActivationsController < ApplicationController + def edit + user = User.find_by(email: params[:email]) + if user && !user.activated? && user.authenticated?(:activation, params[:id]) + user.activate + log_in user + flash[:success] = "Account activated!" + redirect_to user + else + flash[:danger] = "Invalid activation link" + redirect_to root_url + end + end +end \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0f63d7e..c1546ac 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,7 +2,14 @@ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception - def hello - render html: "hello, world!" - end + include SessionsHelper + private + # Confirms a logged-in user. + def logged_in_user + unless logged_in? + store_location + flash[:danger] = "Please log in." + redirect_to login_url + end + end end diff --git a/app/controllers/microposts_controller.rb b/app/controllers/microposts_controller.rb index c36ac0b..8c647ee 100644 --- a/app/controllers/microposts_controller.rb +++ b/app/controllers/microposts_controller.rb @@ -1,74 +1,29 @@ class MicropostsController < ApplicationController - before_action :set_micropost, only: [:show, :edit, :update, :destroy] - - # GET /microposts - # GET /microposts.json - def index - @microposts = Micropost.all - end - - # GET /microposts/1 - # GET /microposts/1.json - def show - end - - # GET /microposts/new - def new - @micropost = Micropost.new - end - - # GET /microposts/1/edit - def edit - end - - # POST /microposts - # POST /microposts.json - def create - @micropost = Micropost.new(micropost_params) - - respond_to do |format| - if @micropost.save - format.html { redirect_to @micropost, notice: 'Micropost was successfully created.' } - format.json { render :show, status: :created, location: @micropost } - else - format.html { render :new } - format.json { render json: @micropost.errors, status: :unprocessable_entity } - end - end - end - - # PATCH/PUT /microposts/1 - # PATCH/PUT /microposts/1.json - def update - respond_to do |format| - if @micropost.update(micropost_params) - format.html { redirect_to @micropost, notice: 'Micropost was successfully updated.' } - format.json { render :show, status: :ok, location: @micropost } - else - format.html { render :edit } - format.json { render json: @micropost.errors, status: :unprocessable_entity } - end - end - end - - # DELETE /microposts/1 - # DELETE /microposts/1.json - def destroy - @micropost.destroy - respond_to do |format| - format.html { redirect_to microposts_url, notice: 'Micropost was successfully destroyed.' } - format.json { head :no_content } - end - end - - private - # Use callbacks to share common setup or constraints between actions. - def set_micropost - @micropost = Micropost.find(params[:id]) - end - - # Never trust parameters from the scary internet, only allow the white list through. - def micropost_params - params.require(:micropost).permit(:content, :user_id) - end + before_action :logged_in_user, only: [:create, :destroy] + before_action :correct_user, only: :destroy + def create + @micropost = current_user.microposts.build(micropost_params) + if @micropost.save + flash[:success] = "Micropost created!" + redirect_to root_url + else + @feed_items = [] + render 'static_pages/home' + end + end + + def destroy + @micropost.destroy + flash[:success] = "Micropost deleted" + redirect_to request.referrer || root_url + end + private + def micropost_params + params.require(:micropost).permit(:content, :picture) + end + + def correct_user + @micropost = current_user.microposts.find_by(id: params[:id]) + redirect_to root_url if @micropost.nil? + end end diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb new file mode 100644 index 0000000..d735d4d --- /dev/null +++ b/app/controllers/password_resets_controller.rb @@ -0,0 +1,59 @@ +class PasswordResetsController < ApplicationController + before_action :get_user, only: [:edit, :update] + before_action :valid_user, only: [:edit, :update] + before_action :check_expiration, only: [:edit, :update] + def new + end + def create + @user = User.find_by(email: params[:password_reset][:email].downcase) + if @user + @user.create_reset_digest + @user.send_password_reset_email + flash[:info] = "Email sent with password reset instructions" + redirect_to root_url + else + flash.now[:danger] = "Email address not found" + render 'new' + end + end + def edit + end + def update + if password_blank? + flash.now[:danger] = "Password can't be blank" + render 'edit' + elsif @user.update_attributes(user_params) + log_in @user + flash[:success] = "Password has been reset." + redirect_to @user + else + render 'edit' + end + end + private + def user_params + params.require(:user).permit(:password, :password_confirmation) + end + # Returns true if password is blank. + def password_blank? + params[:user][:password].blank? + end + # Before filters + def get_user + @user = User.find_by(email: params[:email]) + end + # Confirms a valid user. + def valid_user + unless (@user && @user.activated? && + @user.authenticated?(:reset, params[:id])) + redirect_to root_url + end + end + # Checks expiration of reset token. + def check_expiration + if @user.password_reset_expired? + flash[:danger] = "Password reset has expired." + redirect_to new_password_reset_url + end + end +end \ No newline at end of file diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb new file mode 100644 index 0000000..16c372a --- /dev/null +++ b/app/controllers/relationships_controller.rb @@ -0,0 +1,19 @@ +class RelationshipsController < ApplicationController + before_action :logged_in_user + def create + @user = User.find(params[:followed_id]) + current_user.follow(@user) + respond_to do |format| + format.html { redirect_to @user } + format.js + end + end + def destroy + @user = Relationship.find(params[:id]).followed + current_user.unfollow(@user) + respond_to do |format| + format.html { redirect_to @user } + format.js + end + end +end \ No newline at end of file diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..137a474 --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,26 @@ +class SessionsController < ApplicationController + def new + end + def create + user = User.find_by(email: params[:session][:email].downcase) + if user && user.authenticate(params[:session][:password]) + if user.activated? + log_in user + params[:session][:remember_me] == '1' ? remember(user) : forget(user) + redirect_back_or user + else + message = "Account not activated. " + message += "Check your email for the activation link." + flash[:warning] = message + redirect_to root_url + end + else + flash.now[:danger] = 'Invalid email/password combination' + render 'new' + end + end + def destroy + log_out if logged_in? + redirect_to root_url + end +end \ No newline at end of file diff --git a/app/controllers/static_pages_controller.rb b/app/controllers/static_pages_controller.rb index d304760..88981d2 100644 --- a/app/controllers/static_pages_controller.rb +++ b/app/controllers/static_pages_controller.rb @@ -1,7 +1,10 @@ class StaticPagesController < ApplicationController def home - end - + if logged_in? + @micropost = current_user.microposts.build + @feed_items = current_user.feed.paginate(page: params[:page]) + end + end def help end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 46b6a95..c2259e2 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,74 +1,87 @@ class UsersController < ApplicationController - before_action :set_user, only: [:show, :edit, :update, :destroy] + before_action :logged_in_user, only: [:index, :edit, :update, :destroy, + :following, :followers] + before_action :correct_user, only: [:edit, :update] + before_action :admin_user, only: :destroy + def new + end - # GET /users - # GET /users.json def index - @users = User.all + @users = User.paginate(page: params[:page]) end - # GET /users/1 - # GET /users/1.json def show + @user = User.find(params[:id]) + @microposts = @user.microposts.paginate(page: params[:page]) end - - # GET /users/new def new - @user = User.new - end - - # GET /users/1/edit - def edit + @user = User.new end - - # POST /users - # POST /users.json def create - @user = User.new(user_params) - - respond_to do |format| - if @user.save - format.html { redirect_to @user, notice: 'User was successfully created.' } - format.json { render :show, status: :created, location: @user } - else - format.html { render :new } - format.json { render json: @user.errors, status: :unprocessable_entity } - end - end + @user = User.new(user_params) + if @user.save + UserMailer.account_activation(@user).deliver_now + flash[:info] = "Please check your email to activate your account." + redirect_to root_url + else + render 'new' + end + end + def is_logged_in? + !session[:user_id].nil? + end + def edit + @user = User.find(params[:id]) end - # PATCH/PUT /users/1 - # PATCH/PUT /users/1.json def update - respond_to do |format| - if @user.update(user_params) - format.html { redirect_to @user, notice: 'User was successfully updated.' } - format.json { render :show, status: :ok, location: @user } - else - format.html { render :edit } - format.json { render json: @user.errors, status: :unprocessable_entity } - end + @user = User.find(params[:id]) + if @user.update_attributes(user_params) + # Handle a successful update. + flash[:success] = "Profile updated" + redirect_to @user + else + render 'edit' end end - # DELETE /users/1 - # DELETE /users/1.json def destroy - @user.destroy - respond_to do |format| - format.html { redirect_to users_url, notice: 'User was successfully destroyed.' } - format.json { head :no_content } - end + User.find(params[:id]).destroy + flash[:success] = "User deleted" + redirect_to users_url + end + def following + @title = "Following" + @user = User.find(params[:id]) + @users = @user.following.paginate(page: params[:page]) + render 'show_follow' end + def followers + @title = "Followers" + @user = User.find(params[:id]) + @users = @user.followers.paginate(page: params[:page]) + render 'show_follow' + end private - # Use callbacks to share common setup or constraints between actions. - def set_user + def user_params + params.require(:user).permit(:name, :email, :password,:password_confirmation) + end + # Confirms a logged-in user. + def logged_in_user + unless logged_in? + store_location + flash[:danger] = "Please log in." + redirect_to login_url + end + end + # Confirms the correct user. + def correct_user @user = User.find(params[:id]) + redirect_to(root_url) unless current_user?(@user) end - - # Never trust parameters from the scary internet, only allow the white list through. - def user_params - params.require(:user).permit(:name, :email) + # Confirms an admin user. + def admin_user + redirect_to(root_url) unless current_user.admin? end end diff --git a/app/helpers/account_activation_helper.rb b/app/helpers/account_activation_helper.rb new file mode 100644 index 0000000..5264b58 --- /dev/null +++ b/app/helpers/account_activation_helper.rb @@ -0,0 +1,2 @@ +module AccountActivationHelper +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be79..5062e8e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,12 @@ module ApplicationHelper -end + + # Returns the full title on a per-page basis. + def full_title(page_title = '') + base_title = "Ruby on Rails Tutorial Sample App" + if page_title.empty? + base_title + else + page_title + " | " + base_title + end + end +end \ No newline at end of file diff --git a/app/helpers/password_resets_helper.rb b/app/helpers/password_resets_helper.rb new file mode 100644 index 0000000..0c9d96e --- /dev/null +++ b/app/helpers/password_resets_helper.rb @@ -0,0 +1,2 @@ +module PasswordResetsHelper +end diff --git a/app/helpers/relationships_helper.rb b/app/helpers/relationships_helper.rb new file mode 100644 index 0000000..3b96a9c --- /dev/null +++ b/app/helpers/relationships_helper.rb @@ -0,0 +1,2 @@ +module RelationshipsHelper +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000..1b59ebe --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,58 @@ +module SessionsHelper + # Logs in the given user. + # Logs in the given user. + def log_in(user) + session[:user_id] = user.id + end + + def remember(user) + user.remember + cookies.permanent.signed[:user_id] = user.id + cookies.permanent[:remember_token] = user.remember_token + end + + def current_user?(user) + user == current_user + end + + # Returns the user corresponding to the remember token cookie. + def current_user + if (user_id = session[:user_id]) + @current_user ||= User.find_by(id: user_id) + elsif (user_id = cookies.signed[:user_id]) + user = User.find_by(id: user_id) + if user && user.authenticated?(:remember, cookies[:remember_token]) + log_in user + @current_user = user + end + end + end + # Returns the current logged-in user (if any). + + def logged_in? + !current_user.nil? + end + + # Forgets a persistent session. + def forget(user) + user.forget + cookies.delete(:user_id) + cookies.delete(:remember_token) + end + + def log_out + forget(current_user) + session.delete(:user_id) + @current_user = nil + end + + # Redirects to stored location (or to the default). + def redirect_back_or(default) + redirect_to(session[:forwarding_url] || default) + session.delete(:forwarding_url) + end +# Stores the URL trying to be accessed. + def store_location + session[:forwarding_url] = request.url if request.get? + end +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 2310a24..3f5b4df 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -1,2 +1,10 @@ module UsersHelper + # Returns the Gravatar for the given user. + # Returns the Gravatar (http://gravatar.com/) for the given user. + def gravatar_for(user, options = { size: 50 }) + gravatar_id = Digest::MD5::hexdigest(user.email.downcase) + size = options[:size] + gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" + image_tag(gravatar_url, alt: user.name, class: "gravatar") + end end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 0000000..6b6185f --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "noreply@example.com" + layout 'mailer' +end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb new file mode 100644 index 0000000..ae8c9cd --- /dev/null +++ b/app/mailers/user_mailer.rb @@ -0,0 +1,21 @@ +class UserMailer < ApplicationMailer + + # Subject can be set in your I18n file at config/locales/en.yml + # with the following lookup: + # + # en.user_mailer.account_activation.subject + # + def account_activation(user) + @user = user + mail to: user.email, subject: "Account activation" + end + # Subject can be set in your I18n file at config/locales/en.yml + # with the following lookup: + # + # en.user_mailer.password_reset.subject + # + def password_reset(user) + @user = user + mail to: user.email, subject: "Password reset" + end +end diff --git a/app/models/micropost.rb b/app/models/micropost.rb index 1492773..2d4b0c4 100644 --- a/app/models/micropost.rb +++ b/app/models/micropost.rb @@ -1,4 +1,15 @@ class Micropost < ActiveRecord::Base - belongs_to :user - validates :content, length: {maximum: 140}, presence: true + belongs_to :user + default_scope -> {order(created_at: :desc)} + mount_uploader :picture, PictureUploader + validates :user_id, presence: true + validates :content, presence: true, length: { maximum: 140 } + validate :picture_size + private + # Validates the size of an uploaded picture. + def picture_size + if picture.size > 5.megabytes + errors.add(:picture, "should be less than 5MB") + end + end end diff --git a/app/models/relationship.rb b/app/models/relationship.rb new file mode 100644 index 0000000..7fe0743 --- /dev/null +++ b/app/models/relationship.rb @@ -0,0 +1,6 @@ +class Relationship < ActiveRecord::Base + belongs_to :follower, class_name: "User" + belongs_to :followed, class_name: "User" + validates :follower_id, presence: true + validates :followed_id, presence: true +end diff --git a/app/models/user.rb b/app/models/user.rb index c7b120b..fbf1b72 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,104 @@ class User < ActiveRecord::Base - has_many :microposts - validates :name, presence: true - validates :email, presence: true + has_many :microposts, dependent: :destroy + has_many :active_relationships, class_name: "Relationship", + foreign_key: "follower_id", + dependent: :destroy + has_many :passive_relationships, class_name: "Relationship", + foreign_key: "followed_id", + dependent: :destroy + has_many :following, through: :active_relationships, source: :followed + has_many :followers, through: :passive_relationships, source: :follower + attr_accessor :remember_token, :activation_token, :reset_token + before_save :downcase_email + before_create :create_activation_digest + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: { case_sensitive: false } + has_secure_password + validates :password, length: { minimum: 6 }, allow_blank: true +# Returns the hash digest of the given string. + def User.digest(string) + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : + BCrypt::Engine.cost + BCrypt::Password.create(string, cost: cost) + end +# Returns a random token. + def User.new_token + SecureRandom.urlsafe_base64 + end +# Remembers a user in the database for use in persistent sessions. + def remember + self.remember_token = User.new_token + update_attribute(:remember_digest, User.digest(remember_token)) + end +# Returns true if the given token matches the digest. + def authenticated?(attribute, token) + digest = send("#{attribute}_digest") + return false if digest.nil? + BCrypt::Password.new(digest).is_password?(token) + end + # Forgets a user. + def forget + update_attribute(:remember_digest, nil) + end + +# Activates an account. + def activate + update_attribute(:activated, true) + update_attribute(:activated_at, Time.zone.now) + end + + # Sends activation email. + def send_activation_email + UserMailer.account_activation(self).deliver_now + end + + # Sets the password reset attributes. + def create_reset_digest + self.reset_token = User.new_token + update_attribute(:reset_digest, User.digest(reset_token)) + update_attribute(:reset_sent_at, Time.zone.now) + end + # Sends password reset email. + def send_password_reset_email + UserMailer.password_reset(self).deliver_now + end + # Defines a proto-feed. + # See "Following users" for the full implementation. + def feed + following_ids = "SELECT followed_id FROM relationships + WHERE follower_id = :user_id" + Micropost.where("user_id IN (#{following_ids}) + OR user_id = :user_id", user_id: id) + end + # Returns true if a password reset has expired. + def password_reset_expired? + reset_sent_at < 2.hours.ago + end + + # Follows a user. + def follow(other_user) + active_relationships.create(followed_id: other_user.id) + end + # Unfollows a user. + def unfollow(other_user) + active_relationships.find_by(followed_id: other_user.id).destroy + end + # Returns true if the current user is following the other user. + def following?(other_user) + following.include?(other_user) + end + private + # Converts email to all lower-case. + def downcase_email + self.email = email.downcase + end + # Creates and assigns the activation token and digest. + def create_activation_digest + self.activation_token = User.new_token + self.activation_digest = User.digest(activation_token) + end + end diff --git a/app/uploaders/picture_uploader.rb b/app/uploaders/picture_uploader.rb new file mode 100644 index 0000000..2e376d3 --- /dev/null +++ b/app/uploaders/picture_uploader.rb @@ -0,0 +1,62 @@ +# encoding: utf-8 +class PictureUploader < CarrierWave::Uploader::Base + include CarrierWave::MiniMagick + process resize_to_limit: [400, 400] + + # Include RMagick or MiniMagick support: + # include CarrierWave::RMagick + # include CarrierWave::MiniMagick + + # Choose what kind of storage to use for this uploader: + #storage :file + # storage :fog + + if Rails.env.production? + storage :fog + else + storage :file + end + + # Override the directory where uploaded files will be stored. + # This is a sensible default for uploaders that are meant to be mounted: + def store_dir + "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" + end + +# Add a white list of extensions which are allowed to be uploaded. + def extension_white_list + %w(jpg jpeg gif png) + end + # Provide a default URL as a default if there hasn't been a file uploaded: + # def default_url + # # For Rails 3.1+ asset pipeline compatibility: + # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) + # + # "/images/fallback/" + [version_name, "default.png"].compact.join('_') + # end + + # Process files as they are uploaded: + # process :scale => [200, 300] + # + # def scale(width, height) + # # do something + # end + + # Create different versions of your uploaded files: + # version :thumb do + # process :resize_to_fit => [50, 50] + # end + + # Add a white list of extensions which are allowed to be uploaded. + # For images you might use something like this: + # def extension_white_list + # %w(jpg jpeg gif png) + # end + + # Override the filename of the uploaded files: + # Avoid using model.id or version_name here, see uploader/store.rb for details. + # def filename + # "something.jpg" if original_filename + # end + +end diff --git a/app/views/layouts/_footer.html.erb b/app/views/layouts/_footer.html.erb new file mode 100644 index 0000000..982e1bb --- /dev/null +++ b/app/views/layouts/_footer.html.erb @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb new file mode 100644 index 0000000..8cbd222 --- /dev/null +++ b/app/views/layouts/_header.html.erb @@ -0,0 +1,29 @@ + \ No newline at end of file diff --git a/app/views/layouts/_shim.html.erb b/app/views/layouts/_shim.html.erb new file mode 100644 index 0000000..79505cc --- /dev/null +++ b/app/views/layouts/_shim.html.erb @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 90d4229..1065d12 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,14 +1,22 @@ - - <%= yield(:title) %> | Ruby on Rails Tutorial Sample App - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> - <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> - <%= csrf_meta_tags %> - - - -<%= yield %> - - - + + <%= full_title(yield(:title)) %> + <%= csrf_meta_tags %> + <%= stylesheet_link_tag 'application', media: 'all', + 'data-turbolinks-track' => true %> + <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + <%= render 'layouts/shim' %> + + + <%= render 'layouts/header' %> +
+ <% flash.each do |message_type, message| %> +
<%= message %>
+ <% end %> + <%= yield %> + <%= render 'layouts/footer' %> + <%= debug(params) if Rails.env.development? %> +
+ + \ No newline at end of file diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000..991cf0f --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,5 @@ + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000..37f0bdd --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/app/views/microposts/_micropost.html.erb b/app/views/microposts/_micropost.html.erb new file mode 100644 index 0000000..da6dfcf --- /dev/null +++ b/app/views/microposts/_micropost.html.erb @@ -0,0 +1,13 @@ +
  • + <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> + <%= link_to micropost.user.name, micropost.user %> + <%= micropost.content %> + <%= image_tag micropost.picture.url if micropost.picture? %> + + Posted <%= time_ago_in_words(micropost.created_at) %> ago. + <% if current_user?(micropost.user) %> + <%= link_to "delete", micropost, method: :delete, + data: { confirm: "You sure?" } %> + <% end %> + +
  • \ No newline at end of file diff --git a/app/views/microposts/show.html.erb b/app/views/microposts/show.html.erb index 0e55f60..237d4c5 100644 --- a/app/views/microposts/show.html.erb +++ b/app/views/microposts/show.html.erb @@ -1,14 +1,20 @@ -

    <%= notice %>

    - -

    - Content: - <%= @micropost.content %> -

    - -

    - User: - <%= @micropost.user_id %> -

    - -<%= link_to 'Edit', edit_micropost_path(@micropost) %> | -<%= link_to 'Back', microposts_path %> +<% provide(:title, @user.name) %> +
    + +
    + <% if @user.microposts.any? %> +

    Microposts (<%= @user.microposts.count %>)

    +
      + <%= render @microposts %> +
    + <%= will_paginate @microposts %> + <% end %> +
    +
    \ No newline at end of file diff --git a/app/views/password_resets/edit.html.erb b/app/views/password_resets/edit.html.erb new file mode 100644 index 0000000..6eeb28a --- /dev/null +++ b/app/views/password_resets/edit.html.erb @@ -0,0 +1,15 @@ +<% provide(:title, 'Reset password') %> +

    Reset password

    +
    +
    + <%= form_for(@user, url: password_reset_path(params[:id])) do |f| %> + <%= render 'shared/error_messages' %> + <%= hidden_field_tag :email, @user.email %> + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + <%= f.submit "Update password", class: "btn btn-primary" %> + <% end %> +
    +
    \ No newline at end of file diff --git a/app/views/password_resets/new.html.erb b/app/views/password_resets/new.html.erb new file mode 100644 index 0000000..b8aa69d --- /dev/null +++ b/app/views/password_resets/new.html.erb @@ -0,0 +1,11 @@ +<% provide(:title, "Forgot password") %> +

    Forgot password

    +
    +
    + <%= form_for(:password_reset, url: password_resets_path) do |f| %> + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + <%= f.submit "Submit", class: "btn btn-primary" %> + <% end %> +
    +
    \ No newline at end of file diff --git a/app/views/relationships/create.js.erb b/app/views/relationships/create.js.erb new file mode 100644 index 0000000..6adae73 --- /dev/null +++ b/app/views/relationships/create.js.erb @@ -0,0 +1,2 @@ +$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>") +$("#followers").html('<%= @user.followers.count %>') \ No newline at end of file diff --git a/app/views/relationships/destroy.js.erb b/app/views/relationships/destroy.js.erb new file mode 100644 index 0000000..6382b4a --- /dev/null +++ b/app/views/relationships/destroy.js.erb @@ -0,0 +1,2 @@ +$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>") +$("#followers").html('<%= @user.followers.count %>') \ No newline at end of file diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb new file mode 100644 index 0000000..ec600d8 --- /dev/null +++ b/app/views/sessions/new.html.erb @@ -0,0 +1,25 @@ +<% provide(:title, "Log in") %> +

    Log in

    + +
    +
    + <%= form_for(:session, url: login_path) do |f| %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= link_to "(forgot password)", new_password_reset_path %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :remember_me, class: "checkbox inline" do %> + <%= f.check_box :remember_me %> + Remember me on this computer + <% end %> + + <%= f.submit "Log in", class: "btn btn-primary" %> + <% end %> + +

    New user? <%= link_to "Sign up now!", signup_path %>

    +
    +
    \ No newline at end of file diff --git a/app/views/shared/_error_messages.html.erb b/app/views/shared/_error_messages.html.erb new file mode 100644 index 0000000..1da9f30 --- /dev/null +++ b/app/views/shared/_error_messages.html.erb @@ -0,0 +1,12 @@ +<% if object.errors.any? %> +
    +
    + The form contains <%= pluralize(object.errors.count, "error") %>. +
    + +
    +<% end %> \ No newline at end of file diff --git a/app/views/shared/_feed.html.erb b/app/views/shared/_feed.html.erb new file mode 100644 index 0000000..bf24156 --- /dev/null +++ b/app/views/shared/_feed.html.erb @@ -0,0 +1,6 @@ +<% if @feed_items.any? %> +
      + <%= render @feed_items %> +
    + <%= will_paginate @feed_items %> +<% end %> \ No newline at end of file diff --git a/app/views/shared/_micropost_form.html.erb b/app/views/shared/_micropost_form.html.erb new file mode 100644 index 0000000..8034ba3 --- /dev/null +++ b/app/views/shared/_micropost_form.html.erb @@ -0,0 +1,18 @@ +<%= form_for(@micropost) do |f| %> + <%= render 'shared/error_messages', object: f.object %> +
    + <%= f.text_area :content, placeholder: "Compose new micropost..." %> +
    + <%= f.submit "Post", class: "btn btn-primary" %> + + <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %> + +<% end %> + \ No newline at end of file diff --git a/app/views/shared/_stats.html.erb b/app/views/shared/_stats.html.erb new file mode 100644 index 0000000..dd5bc24 --- /dev/null +++ b/app/views/shared/_stats.html.erb @@ -0,0 +1,15 @@ +<% @user ||= current_user %> +
    + + + <%= @user.following.count %> + + following + + + + <%= @user.followers.count %> + + followers + +
    \ No newline at end of file diff --git a/app/views/shared/_user_info.html.erb b/app/views/shared/_user_info.html.erb new file mode 100644 index 0000000..e6302d8 --- /dev/null +++ b/app/views/shared/_user_info.html.erb @@ -0,0 +1,4 @@ +<%= link_to gravatar_for(current_user, size: 50), current_user %> +

    <%= current_user.name %>

    +<%= link_to "view my profile", current_user %> +<%= pluralize(current_user.microposts.count, "micropost") %> \ No newline at end of file diff --git a/app/views/static_pages/home.html.erb b/app/views/static_pages/home.html.erb index 7a569d1..bb2f943 100644 --- a/app/views/static_pages/home.html.erb +++ b/app/views/static_pages/home.html.erb @@ -1,7 +1,32 @@ <% provide(:title, "Home") %> -

    Sample App

    -

    - This is the home page for the - Ruby on Rails Tutorial - sample application. -

    \ No newline at end of file +<% if logged_in? %> +
    + +
    +

    Micropost Feed

    + <%= render 'shared/feed' %> +
    +
    +<% else %> +
    +

    Welcome to the Sample App

    +

    + This is the home page for the + Ruby on Rails Tutorial + sample application. +

    + <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> +
    + <%= link_to image_tag("rails.png", alt: "Rails logo"), + 'http://rubyonrails.org/' %> +<% end %> \ No newline at end of file diff --git a/app/views/user_mailer/account_activation.html.erb b/app/views/user_mailer/account_activation.html.erb new file mode 100644 index 0000000..603ece3 --- /dev/null +++ b/app/views/user_mailer/account_activation.html.erb @@ -0,0 +1,7 @@ +

    Sample App

    +

    Hi <%= @user.name %>,

    +

    +Welcome to the Sample App! Click on the link below to activate your account: +

    +<%= link_to "Activate", edit_account_activation_url(@user.activation_token, +email: @user.email) %> \ No newline at end of file diff --git a/app/views/user_mailer/account_activation.text.erb b/app/views/user_mailer/account_activation.text.erb new file mode 100644 index 0000000..4f11c68 --- /dev/null +++ b/app/views/user_mailer/account_activation.text.erb @@ -0,0 +1,3 @@ +Hi <%= @user.name %>, +Welcome to the Sample App! Click on the link below to activate your account: +<%= edit_account_activation_url(@user.activation_token, email: @user.email) %> \ No newline at end of file diff --git a/app/views/user_mailer/password_reset.html.erb b/app/views/user_mailer/password_reset.html.erb new file mode 100644 index 0000000..f86d184 --- /dev/null +++ b/app/views/user_mailer/password_reset.html.erb @@ -0,0 +1,9 @@ +

    Password reset

    +

    To reset your password click the link below:

    +<%= link_to "Reset password", edit_password_reset_url(@user.reset_token, +email: @user.email) %> +

    This link will expire in two hours.

    +

    +If you did not request your password to be reset, please ignore this email and +your password will stay as it is. +

    \ No newline at end of file diff --git a/app/views/user_mailer/password_reset.text.erb b/app/views/user_mailer/password_reset.text.erb new file mode 100644 index 0000000..9c35b0d --- /dev/null +++ b/app/views/user_mailer/password_reset.text.erb @@ -0,0 +1,5 @@ +To reset your password click the link below: +<%= edit_password_reset_url(@user.reset_token, email: @user.email) %> +This link will expire in two hours. +If you did not request your password to be reset, please ignore this email and +your password will stay as it is. \ No newline at end of file diff --git a/app/views/users/_follow.html.erb b/app/views/users/_follow.html.erb new file mode 100644 index 0000000..de3d08d --- /dev/null +++ b/app/views/users/_follow.html.erb @@ -0,0 +1,6 @@ +<%= form_for(current_user.active_relationships. + build(followed_id: @user.id), + remote: true) do |f| %> +
    <%= hidden_field_tag :followed_id, @user.id %>
    + <%= f.submit "Follow", class: "btn btn-primary" %> +<% end %> \ No newline at end of file diff --git a/app/views/users/_follow_form.html.erb b/app/views/users/_follow_form.html.erb new file mode 100644 index 0000000..6daf9f8 --- /dev/null +++ b/app/views/users/_follow_form.html.erb @@ -0,0 +1,9 @@ +<% unless current_user?(@user) %> +
    + <% if current_user.following?(@user) %> + <%= render 'unfollow' %> + <% else %> + <%= render 'follow' %> + <% end %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/users/_unfollow.html.erb b/app/views/users/_unfollow.html.erb new file mode 100644 index 0000000..34aaf73 --- /dev/null +++ b/app/views/users/_unfollow.html.erb @@ -0,0 +1,5 @@ +<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id), + html: { method: :delete }, + remote: true) do |f| %> + <%= f.submit "Unfollow", class: "btn" %> +<% end %> \ No newline at end of file diff --git a/app/views/users/_user.html.erb b/app/views/users/_user.html.erb new file mode 100644 index 0000000..7f9c1d2 --- /dev/null +++ b/app/views/users/_user.html.erb @@ -0,0 +1,8 @@ +
  • + <%= gravatar_for user, size: 50 %> + <%= link_to user.name, user %> + <% if current_user.admin? && !current_user?(user) %> + | <%= link_to "delete", user, method: :delete, + data: { confirm: "You sure?" } %> + <% end %> +
  • \ No newline at end of file diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index d87b2f5..4b5c789 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -1,6 +1,22 @@ -

    Editing User

    - -<%= render 'form' %> - -<%= link_to 'Show', @user %> | -<%= link_to 'Back', users_path %> +<% provide(:title, "Edit user") %> +

    Update your profile

    +
    +
    + <%= form_for(@user) do |f| %> + <%= render 'shared/error_messages', object: f.object %> + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + <%= f.submit "Save changes", class: "btn btn-primary" %> + <% end %> +
    + <%= gravatar_for @user %> + change +
    +
    +
    \ No newline at end of file diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 32c0d02..3c7738b 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -1,29 +1,10 @@ -

    <%= notice %>

    +<% provide(:title, 'All users') %> +

    All users

    -

    Listing Users

    +<%= will_paginate %> - - - - - - - - + - - <% @users.each do |user| %> - - - - - - - - <% end %> - -
    NameEmail
    <%= user.name %><%= user.email %><%= link_to 'Show', user %><%= link_to 'Edit', edit_user_path(user) %><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %>
    - -
    - -<%= link_to 'New User', new_user_path %> +<%= will_paginate %> \ No newline at end of file diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index 4854d9e..f6dd1d3 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -1,5 +1,18 @@ -

    New User

    - -<%= render 'form' %> - -<%= link_to 'Back', users_path %> +<% provide(:title, 'Sign up') %> +

    Sign up

    +
    +
    + <%= form_for(@user) do |f| %> + <%= render 'shared/error_messages', object: f.object %> + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + <%= f.submit "Create my account", class: "btn btn-primary" %> + <% end %> +
    +
    <%= notice %>

    - -

    - Name: - <%= @user.name %> -

    - -

    - Email: - <%= @user.email %> -

    - -<%= link_to 'Edit', edit_user_path(@user) %> | -<%= link_to 'Back', users_path %> +<% provide(:title, @user.name) %> +
    + +
    + <%= render 'follow_form' if logged_in? %> + <% if @user.microposts.any? %> +

    Microposts (<%= @user.microposts.count %>)

    +
      + <%= render @microposts %> +
    + <%= will_paginate @microposts %> + <% end %> +
    +
    \ No newline at end of file diff --git a/app/views/users/show_follow.html.erb b/app/views/users/show_follow.html.erb new file mode 100644 index 0000000..333d582 --- /dev/null +++ b/app/views/users/show_follow.html.erb @@ -0,0 +1,30 @@ +<% provide(:title, @title) %> +
    + +
    +

    <%= @title %>

    + <% if @users.any? %> + + <%= will_paginate %> + <% end %> +
    +
    \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 1991721..4070adb 100644 --- a/config/application.rb +++ b/config/application.rb @@ -22,5 +22,7 @@ class Application < Rails::Application # Do not swallow errors in after_commit/after_rollback callbacks. config.active_record.raise_in_transactional_callbacks = true + # Include the authenticity token in remote forms. + config.action_view.embed_authenticity_token_in_remote_forms = true end end diff --git a/config/environments/development.rb b/config/environments/development.rb index b55e214..11054bc 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -22,6 +22,12 @@ # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load + + + config.action_mailer.raise_delivery_errors = true + config.action_mailer.delivery_method = :test + host = 'localhost:3000' + config.action_mailer.default_url_options = { host: host } # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. diff --git a/config/environments/test.rb b/config/environments/test.rb index 1c19f08..58ff691 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -30,7 +30,7 @@ # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - + config.action_mailer.default_url_options = { host: 'localhost:3000' } # Randomize the order test cases are executed. config.active_support.test_order = :random diff --git a/config/initializers/carrier_wave.rb b/config/initializers/carrier_wave.rb new file mode 100644 index 0000000..c3aa2b8 --- /dev/null +++ b/config/initializers/carrier_wave.rb @@ -0,0 +1,11 @@ +if Rails.env.production? + CarrierWave.configure do |config| + config.fog_credentials = { + # Configuration for Amazon S3 + :provider => 'AWS', + :aws_access_key_id => ENV['S3_ACCESS_KEY'], + :aws_secret_access_key => ENV['S3_SECRET_KEY'] + } + config.fog_directory = ENV['S3_BUCKET'] + end +end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index c83d121..493cb86 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,12 +1,28 @@ Rails.application.routes.draw do + get 'password_resets/new' + + get 'password_resets/edit' + root 'static_pages#home' - get 'static_pages/home' - get 'static_pages/about' - get 'static_pages/help' - get 'static_pages/contact' + get 'help' => 'static_pages#help' + get 'about' => 'static_pages#about' + get 'contact' => 'static_pages#contact' + get 'signup' => 'users#new' + #post '/signup' => 'users#create' + + get 'login' => 'sessions#new' + post 'login' => 'sessions#create' + delete 'logout' => 'sessions#destroy' - resources :microposts - resources :users + resources :users do + member do + get :following, :followers + end + end + resources :account_activations, only: [:edit] + resources :microposts, only: [:create, :destroy] + resources :password_resets, only: [:new, :create, :edit, :update] + resources :relationships, only: [:create, :destroy] # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/db/migrate/20160707042523_add_index_to_users_mail.rb b/db/migrate/20160707042523_add_index_to_users_mail.rb new file mode 100644 index 0000000..5704c68 --- /dev/null +++ b/db/migrate/20160707042523_add_index_to_users_mail.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersMail < ActiveRecord::Migration + def change + add_index :users, :email, unique: true + end +end diff --git a/db/migrate/20160707043645_add_password_digest_to_users.rb b/db/migrate/20160707043645_add_password_digest_to_users.rb new file mode 100644 index 0000000..7ad1f62 --- /dev/null +++ b/db/migrate/20160707043645_add_password_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordDigestToUsers < ActiveRecord::Migration + def change + add_column :users, :password_digest, :string + end +end diff --git a/db/migrate/20160708032244_add_remember_digest_to_users.rb b/db/migrate/20160708032244_add_remember_digest_to_users.rb new file mode 100644 index 0000000..82edc16 --- /dev/null +++ b/db/migrate/20160708032244_add_remember_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddRememberDigestToUsers < ActiveRecord::Migration + def change + add_column :users, :remember_digest, :string + end +end diff --git a/db/migrate/20160708090312_add_admin_to_users.rb b/db/migrate/20160708090312_add_admin_to_users.rb new file mode 100644 index 0000000..e386d33 --- /dev/null +++ b/db/migrate/20160708090312_add_admin_to_users.rb @@ -0,0 +1,5 @@ +class AddAdminToUsers < ActiveRecord::Migration + def change + add_column :users, :admin, :boolean, default: false + end +end diff --git a/db/migrate/20160711062804_add_activation_to_users.rb b/db/migrate/20160711062804_add_activation_to_users.rb new file mode 100644 index 0000000..b7b0abf --- /dev/null +++ b/db/migrate/20160711062804_add_activation_to_users.rb @@ -0,0 +1,7 @@ +class AddActivationToUsers < ActiveRecord::Migration + def change + add_column :users, :activation_digest, :string + add_column :users, :activated, :boolean, default: false + add_column :users, :activated_at, :datetime + end +end diff --git a/db/migrate/20160707012303_create_microposts.rb b/db/migrate/20160712070613_create_microposts.rb similarity index 53% rename from db/migrate/20160707012303_create_microposts.rb rename to db/migrate/20160712070613_create_microposts.rb index 67117ba..75bcba8 100644 --- a/db/migrate/20160707012303_create_microposts.rb +++ b/db/migrate/20160712070613_create_microposts.rb @@ -2,9 +2,11 @@ class CreateMicroposts < ActiveRecord::Migration def change create_table :microposts do |t| t.text :content - t.integer :user_id + t.references :user, index: true, foreign_key: true t.timestamps null: false end + add_foreign_key :microposts, :users + add_index :microposts, [:user_id, :created_at] end end diff --git a/db/migrate/20160712093058_add_picture_to_microposts.rb b/db/migrate/20160712093058_add_picture_to_microposts.rb new file mode 100644 index 0000000..5433286 --- /dev/null +++ b/db/migrate/20160712093058_add_picture_to_microposts.rb @@ -0,0 +1,5 @@ +class AddPictureToMicroposts < ActiveRecord::Migration + def change + add_column :microposts, :picture, :string + end +end diff --git a/db/migrate/20160712155715_add_reset_to_users.rb b/db/migrate/20160712155715_add_reset_to_users.rb new file mode 100644 index 0000000..f650b49 --- /dev/null +++ b/db/migrate/20160712155715_add_reset_to_users.rb @@ -0,0 +1,6 @@ +class AddResetToUsers < ActiveRecord::Migration + def change + add_column :users, :reset_digest, :string + add_column :users, :reset_sent_at, :date_time + end +end diff --git a/db/migrate/20160713061441_create_relationships.rb b/db/migrate/20160713061441_create_relationships.rb new file mode 100644 index 0000000..530a27d --- /dev/null +++ b/db/migrate/20160713061441_create_relationships.rb @@ -0,0 +1,13 @@ +class CreateRelationships < ActiveRecord::Migration + def change + create_table :relationships do |t| + t.integer :follower_id + t.integer :followed_id + + t.timestamps null: false + end + add_index :relationships, :follower_id + add_index :relationships, :followed_id + add_index :relationships, [:follower_id, :followed_id], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 59229e2..62308c5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,20 +11,45 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160707012303) do +ActiveRecord::Schema.define(version: 20160713061441) do create_table "microposts", force: :cascade do |t| t.text "content" t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "picture" end + add_index "microposts", ["user_id", "created_at"], name: "index_microposts_on_user_id_and_created_at" + add_index "microposts", ["user_id"], name: "index_microposts_on_user_id" + + create_table "relationships", force: :cascade do |t| + t.integer "follower_id" + t.integer "followed_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "relationships", ["followed_id"], name: "index_relationships_on_followed_id" + add_index "relationships", ["follower_id", "followed_id"], name: "index_relationships_on_follower_id_and_followed_id", unique: true + add_index "relationships", ["follower_id"], name: "index_relationships_on_follower_id" + 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.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "password_digest" + t.string "remember_digest" + t.boolean "admin", default: false + t.string "activation_digest" + t.boolean "activated", default: false + t.datetime "activated_at" + t.string "reset_digest" + t.time "reset_sent_at" end + add_index "users", ["email"], name: "index_users_on_email", unique: true + end diff --git a/db/seeds.rb b/db/seeds.rb index 4edb1e8..500bda1 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,35 @@ # # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) # Mayor.create(name: 'Emanuel', city: cities.first) +User.create!(name: "Example User", + email: "example@railstutorial.org", + password: "foobar", + password_confirmation: "foobar", + admin: true, + activated: true, + activated_at: Time.zone.now) +99.times do |n| + name = Faker::Name.name + email = "example-#{n+1}@railstutorial.org" + password = "password" + User.create!(name: name, + email: email, + password: password, + password_confirmation: password, + activated: true, + activated_at: Time.zone.now) +end + +users = User.order(:created_at).take(6) +50.times do + content = Faker::Lorem.sentence(5) + users.each { |user| user.microposts.create!(content: content) } +end + +# Following relationships +users = User.all +user = users.first +following = users[2..50] +followers = users[3..40] +following.each { |followed| user.follow(followed) } +followers.each { |follower| follower.follow(user) } \ No newline at end of file diff --git a/public/uploads/micropost/picture/301/HBLAB.png b/public/uploads/micropost/picture/301/HBLAB.png new file mode 100644 index 0000000..c0c6ebb Binary files /dev/null and b/public/uploads/micropost/picture/301/HBLAB.png differ diff --git a/public/uploads/micropost/picture/303/1yeartruoc.png b/public/uploads/micropost/picture/303/1yeartruoc.png new file mode 100644 index 0000000..2615ca0 Binary files /dev/null and b/public/uploads/micropost/picture/303/1yeartruoc.png differ diff --git a/public/uploads/micropost/picture/306/treat.png b/public/uploads/micropost/picture/306/treat.png new file mode 100644 index 0000000..955ce64 Binary files /dev/null and b/public/uploads/micropost/picture/306/treat.png differ diff --git a/public/uploads/micropost/picture/307/cover.jpg b/public/uploads/micropost/picture/307/cover.jpg new file mode 100644 index 0000000..6cba581 Binary files /dev/null and b/public/uploads/micropost/picture/307/cover.jpg differ diff --git a/public/uploads/tmp/1468396517-3921-6458/bomb.png b/public/uploads/tmp/1468396517-3921-6458/bomb.png new file mode 100644 index 0000000..6be49bb Binary files /dev/null and b/public/uploads/tmp/1468396517-3921-6458/bomb.png differ diff --git a/public/uploads/tmp/1468396533-3921-7387/treat.png b/public/uploads/tmp/1468396533-3921-7387/treat.png new file mode 100644 index 0000000..258827c Binary files /dev/null and b/public/uploads/tmp/1468396533-3921-7387/treat.png differ diff --git a/test/controllers/account_activation_controller_test.rb b/test/controllers/account_activation_controller_test.rb new file mode 100644 index 0000000..1a66567 --- /dev/null +++ b/test/controllers/account_activation_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class AccountActivationControllerTest < ActionController::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/microposts_controller_test.rb b/test/controllers/microposts_controller_test.rb index be8f376..a876fa6 100644 --- a/test/controllers/microposts_controller_test.rb +++ b/test/controllers/microposts_controller_test.rb @@ -1,49 +1,31 @@ require 'test_helper' class MicropostsControllerTest < ActionController::TestCase - setup do - @micropost = microposts(:one) - end - - test "should get index" do - get :index - assert_response :success - assert_not_nil assigns(:microposts) - end - - test "should get new" do - get :new - assert_response :success - end - - test "should create micropost" do - assert_difference('Micropost.count') do - post :create, micropost: { content: @micropost.content, user_id: @micropost.user_id } - end - - assert_redirected_to micropost_path(assigns(:micropost)) - end - - test "should show micropost" do - get :show, id: @micropost - assert_response :success - end - - test "should get edit" do - get :edit, id: @micropost - assert_response :success - end - - test "should update micropost" do - patch :update, id: @micropost, micropost: { content: @micropost.content, user_id: @micropost.user_id } - assert_redirected_to micropost_path(assigns(:micropost)) - end - - test "should destroy micropost" do - assert_difference('Micropost.count', -1) do - delete :destroy, id: @micropost - end - - assert_redirected_to microposts_path - end + # test "the truth" do + # assert true + # end + def setup + @micropost = microposts(:orange) + end + test "should redirect create when not logged in" do + assert_no_difference 'Micropost.count' do + post :create, micropost: { content: "Lorem ipsum" } + end + assert_redirected_to login_url + end + test "should redirect destroy when not logged in" do + assert_no_difference 'Micropost.count' do + delete :destroy, id: @micropost + end + assert_redirected_to login_url + end + + test "should redirect destroy for wrong micropost" do + log_in_as(users(:michael)) + micropost = microposts(:ants) + assert_no_difference 'Micropost.count' do + delete :destroy, id: micropost + end + assert_redirected_to root_url + end end diff --git a/test/controllers/relationships_controller_test.rb b/test/controllers/relationships_controller_test.rb new file mode 100644 index 0000000..28b6975 --- /dev/null +++ b/test/controllers/relationships_controller_test.rb @@ -0,0 +1,15 @@ +require 'test_helper' +class RelationshipsControllerTest < ActionController::TestCase + test "create should require logged-in user" do + assert_no_difference 'Relationship.count' do + post :create + end + assert_redirected_to login_url + end + test "destroy should require logged-in user" do + assert_no_difference 'Relationship.count' do + delete :destroy, id: relationships(:one) + end + assert_redirected_to login_url + end +end \ No newline at end of file diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb new file mode 100644 index 0000000..517bd41 --- /dev/null +++ b/test/controllers/sessions_controller_test.rb @@ -0,0 +1,5 @@ +require 'test_helper' + +class SessionsControllerTest < ActionController::TestCase + +end diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 91b2342..876b1fc 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -1,49 +1,60 @@ require 'test_helper' class UsersControllerTest < ActionController::TestCase - setup do - @user = users(:one) + def setup + @user = users(:michael) + @other_user = users(:archer) end - - test "should get index" do - get :index - assert_response :success - assert_not_nil assigns(:users) + test "should redirect index when not logged in" do + get :index + assert_redirected_to login_url end - test "should get new" do - get :new - assert_response :success + test "should redirect edit when not logged in" do + get :edit, id: @user + assert_not flash.empty? + assert_redirected_to login_url end - test "should create user" do - assert_difference('User.count') do - post :create, user: { email: @user.email, name: @user.name } - end - - assert_redirected_to user_path(assigns(:user)) + test "should redirect update when not logged in" do + patch :update, id: @user, user: { name: @user.name, email: @user.email } + assert_not flash.empty? + assert_redirected_to login_url end - test "should show user" do - get :show, id: @user - assert_response :success + test "should redirect edit when logged in as wrong user" do + log_in_as(@other_user) + get :edit, id: @user + assert flash.empty? + assert_redirected_to root_url end - test "should get edit" do - get :edit, id: @user - assert_response :success + test "should redirect update when logged in as wrong user" do + log_in_as(@other_user) + patch :update, id: @user, user: { name: @user.name, email: @user.email } + assert flash.empty? + assert_redirected_to root_url end - test "should update user" do - patch :update, id: @user, user: { email: @user.email, name: @user.name } - assert_redirected_to user_path(assigns(:user)) + test "should redirect destroy when not logged in" do + assert_no_difference 'User.count' do + delete :destroy, id: @user + end + assert_redirected_to login_url end - - test "should destroy user" do - assert_difference('User.count', -1) do - delete :destroy, id: @user - end - - assert_redirected_to users_path + test "should redirect destroy when logged in as a non-admin" do + log_in_as(@other_user) + assert_no_difference 'User.count' do + delete :destroy, id: @user + end + assert_redirected_to root_url + end + test "should redirect following when not logged in" do + get :following, id: @user + assert_redirected_to login_url + end + test "should redirect followers when not logged in" do + get :followers, id: @user + assert_redirected_to login_url end end diff --git a/test/fixtures/microposts.yml b/test/fixtures/microposts.yml index a5153b6..2a1fcaa 100644 --- a/test/fixtures/microposts.yml +++ b/test/fixtures/microposts.yml @@ -1,9 +1,46 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - content: MyText - user_id: +orange: + content: "I just ate an orange!" + created_at: <%= 10.minutes.ago %> + user: michael + +tau_manifesto: + content: "Check out the @tauday site by @mhartl: http://tauday.com" + created_at: <%= 3.years.ago %> + user: michael + +cat_video: + content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk" + created_at: <%= 2.hours.ago %> + user: michael + +most_recent: + content: "Writing a short test" + created_at: <%= Time.zone.now %> + user: michael + +<% 30.times do |n| %> +micropost_<%= n %>: + content: <%= Faker::Lorem.sentence(5) %> + created_at: <%= 42.days.ago %> + user: michael +<% end %> + +ants: + content: "Oh, is that what you want? Because that's how you get ants!" + created_at: <%= 2.years.ago %> + user: archer +zone: + content: "Danger zone!" + created_at: <%= 3.days.ago %> + user: archer +tone: + content: "I'm sorry. Your words made sense, but your sarcastic tone did not." + created_at: <%= 10.minutes.ago %> + user: lana +van: + content: "Dude, this van's, like, rolling probable cause." + created_at: <%= 4.hours.ago %> + user: lana -two: - content: MyText - user_id: diff --git a/test/fixtures/relationships.yml b/test/fixtures/relationships.yml new file mode 100644 index 0000000..1c4f106 --- /dev/null +++ b/test/fixtures/relationships.yml @@ -0,0 +1,13 @@ +# empty +one: + follower: Johnny Corkery + followed: lana +two: + follower: michael + followed: mallory +three: + follower: lana + followed: michael +four: + follower: archer + followed: michael \ No newline at end of file diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 5dc4ddf..c515071 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -1,9 +1,33 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html - -one: - name: MyString - email: MyString - -two: - name: MyString - email: MyString +michael: + name: Michael Example + email: michael@example.com + password_digest: <%= User.digest('password') %> + admin: true + activated: true + activated_at: <%= Time.zone.now %> +archer: + name: Sterling Archer + email: duchess@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> +lana: + name: Lana Kane + email: hands@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> +mallory: + name: Mallory Archer + email: boss@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> +<% 30.times do |n| %> +user_<%= n %>: + name: <%= "User #{n}" %> + email: <%= "user-#{n}@example.com" %> + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> +<% end %> \ No newline at end of file diff --git a/test/helpers/ application_helper_test.rb b/test/helpers/ application_helper_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/test/helpers/session_helper_test.rb b/test/helpers/session_helper_test.rb new file mode 100644 index 0000000..354fc04 --- /dev/null +++ b/test/helpers/session_helper_test.rb @@ -0,0 +1,15 @@ +require 'test_helper' +class SessionsHelperTest < ActionView::TestCase + def setup + @user = users(:michael) + remember(@user) + end + test "current_user returns right user when session is nil" do + assert_equal @user, current_user + assert is_logged_in? + end + test "current_user returns nil when remember digest is wrong" do + @user.update_attribute(:remember_digest, User.digest(User.new_token)) + assert_nil current_user + end +end \ No newline at end of file diff --git a/test/integration/following_test.rb b/test/integration/following_test.rb new file mode 100644 index 0000000..08396fc --- /dev/null +++ b/test/integration/following_test.rb @@ -0,0 +1,48 @@ +require 'test_helper' +class FollowingTest < ActionDispatch::IntegrationTest + def setup + @user = users(:michael) + @other = users(:archer) + log_in_as(@user) + end + test "following page" do + get following_user_path(@user) + assert_not @user.following.empty? + assert_match @user.following.count.to_s, response.body + @user.following.each do |user| + assert_select "a[href=?]", user_path(user) + end + end + test "followers page" do + get followers_user_path(@user) + assert_not @user.followers.empty? + assert_match @user.followers.count.to_s, response.body + @user.followers.each do |user| + assert_select "a[href=?]", user_path(user) + end + end + test "should follow a user the standard way" do + assert_difference '@user.following.count', 1 do + post relationships_path, followed_id: @other.id + end + end + test "should follow a user with Ajax" do + assert_difference '@user.following.count', 1 do + xhr :post, relationships_path, followed_id: @other.id + end + end + test "should unfollow a user the standard way" do + @user.follow(@other) + relationship = @user.active_relationships.find_by(followed_id: @other.id) + assert_difference '@user.following.count', -1 do + delete relationship_path(relationship) + end + end + test "should unfollow a user with Ajax" do + @user.follow(@other) + relationship = @user.active_relationships.find_by(followed_id: @other.id) + assert_difference '@user.following.count', -1 do + xhr :delete, relationship_path(relationship) + end + end +end \ No newline at end of file diff --git a/test/integration/microposts_interface_test.rb b/test/integration/microposts_interface_test.rb new file mode 100644 index 0000000..39bacb3 --- /dev/null +++ b/test/integration/microposts_interface_test.rb @@ -0,0 +1,37 @@ +require 'test_helper' + +class MicropostsInterfaceTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end + def setup + @user = users(:michael) + end + test "micropost interface" do + log_in_as(@user) + get root_path + assert_select 'div.pagination' + # Invalid submission + assert_no_difference 'Micropost.count' do + post microposts_path, micropost: { content: "" } + end + assert_select 'div#error_explanation' + # Valid submission + content = "This micropost really ties the room together" + assert_difference 'Micropost.count', 1 do + post microposts_path, micropost: { content: content } + end + assert_redirected_to root_url + follow_redirect! + assert_match content, response.body + # Delete a post. + assert_select 'a', text: 'delete' + first_micropost = @user.microposts.paginate(page: 1).first + assert_difference 'Micropost.count', -1 do + delete micropost_path(first_micropost) + end + # Visit a different user. + get user_path(users(:archer)) + assert_select 'a', text: 'delete', count: 0 + end +end diff --git a/test/integration/password_reset_test.rb b/test/integration/password_reset_test.rb new file mode 100644 index 0000000..d2b91de --- /dev/null +++ b/test/integration/password_reset_test.rb @@ -0,0 +1,59 @@ +require 'test_helper' +class PasswordResetsTest < ActionDispatch::IntegrationTest + def setup + ActionMailer::Base.deliveries.clear + @user = users(:michael) + end + test "password resets" do + get new_password_reset_path + assert_template 'password_resets/new' + # Invalid email + post password_resets_path, password_reset: { email: "" } + assert_not flash.empty? + assert_template 'password_resets/new' + # Valid email + post password_resets_path, password_reset: { email: @user.email } + assert_not_equal @user.reset_digest, @user.reload.reset_digest + assert_equal 1, ActionMailer::Base.deliveries.size + assert_not flash.empty? + assert_redirected_to root_url + # Password reset form + user = assigns(:user) + # Wrong email + get edit_password_reset_path(user.reset_token, email: "") + assert_redirected_to root_url + # Inactive user + user.toggle!(:activated) + get edit_password_reset_path(user.reset_token, email: user.email) + assert_redirected_to root_url + user.toggle!(:activated) + # Right email, wrong token + get edit_password_reset_path('wrong token', email: user.email) + assert_redirected_to root_url + # Right email, right token + get edit_password_reset_path(user.reset_token, email: user.email) + assert_template 'password_resets/edit' + assert_select "input[name=email][type=hidden][value=?]", user.email + # Invalid password & confirmation + patch password_reset_path(user.reset_token), + email: user.email, + user: { password: "foobaz", + password_confirmation: "barquux" } + assert_select 'div#error_explanation' + # Blank password + patch password_reset_path(user.reset_token), + email: user.email, + user: { password: " ", + password_confirmation: "foobar" } + assert_not flash.empty? + assert_template 'password_resets/edit' + # Valid password & confirmation + patch password_reset_path(user.reset_token), + email: user.email, + user: { password: "foobaz", + password_confirmation: "foobaz" } + assert is_logged_in? + assert_not flash.empty? + assert_redirected_to user + end +end \ No newline at end of file diff --git a/test/integration/site_layout_test.rb b/test/integration/site_layout_test.rb new file mode 100644 index 0000000..f6da20e --- /dev/null +++ b/test/integration/site_layout_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' + +class SiteLayoutTest < ActionDispatch::IntegrationTest + + test "layout links" do + get root_path + assert_template 'static_pages/home' + assert_select "a[href=?]", root_path, count: 2 + assert_select "a[href=?]", help_path + end +end \ No newline at end of file diff --git a/test/integration/users_edit_test.rb b/test/integration/users_edit_test.rb new file mode 100644 index 0000000..68b0115 --- /dev/null +++ b/test/integration/users_edit_test.rb @@ -0,0 +1,54 @@ +require 'test_helper' + +class UsersEditTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end + def setup + @user = users(:michael) + end + test "unsuccessful edit" do + log_in_as(@user) + get edit_user_path(@user) + assert_template 'users/edit' + patch user_path(@user), user: { name: "", + email: "foo@invalid", + password: "foo", + password_confirmation: "bar" } + assert_template 'users/edit' + end + test "successful edit" do + log_in_as(@user) + get edit_user_path(@user) + assert_template 'users/edit' + name = "Foo Bar" + email = "foo@bar.com" + patch user_path(@user), user: { name: name, + email: email, + password: "", + password_confirmation: "" } + assert_not flash.empty? + assert_redirected_to @user + @user.reload + assert_equal @user.name, name + assert_equal @user.email, email + end + + + test "successful edit with friendly forwarding" do + get edit_user_path(@user) + log_in_as(@user) + assert_redirected_to edit_user_path(@user) + name = "Foo Bar" + email = "foo@bar.com" + patch user_path(@user), user: { name: name, + email: email, + password: "foobar", + password_confirmation: "foobar" } + assert_not flash.empty? + assert_redirected_to @user + @user.reload + assert_equal @user.name, name + assert_equal @user.email, email + end +end diff --git a/test/integration/users_index_test.rb b/test/integration/users_index_test.rb new file mode 100644 index 0000000..91b7058 --- /dev/null +++ b/test/integration/users_index_test.rb @@ -0,0 +1,33 @@ +require 'test_helper' + +class UsersIndexTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end + def setup + @admin = users(:michael) + @non_admin = users(:archer) + end + test "index as admin including pagination and delete links" do + log_in_as(@admin) + get users_path + assert_template 'users/index' + assert_select 'div.pagination' + first_page_of_users = User.paginate(page: 1) + first_page_of_users.each do |user| + assert_select 'a[href=?]', user_path(user), text: user.name + unless user == @admin + assert_select 'a[href=?]', user_path(user), text: 'delete', + method: :delete + end + end + assert_difference 'User.count', -1 do + delete user_path(@non_admin) + end + end + test "index as non-admin" do + log_in_as(@non_admin) + get users_path + assert_select 'a', text: 'delete', count: 0 + end +end diff --git a/test/integration/users_login_test.rb b/test/integration/users_login_test.rb new file mode 100644 index 0000000..016e23e --- /dev/null +++ b/test/integration/users_login_test.rb @@ -0,0 +1,21 @@ +require 'test_helper' + +class UsersLoginTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end + def setup + @user = users(:michael) + end + + + + test "login with remembering" do + log_in_as(@user, remember_me: '1') + assert_not_nil cookies['remember_token'] + end + test "login without remembering" do + log_in_as(@user, remember_me: '0') + assert_nil cookies['remember_token'] + end +end diff --git a/test/integration/users_profile_test.rb b/test/integration/users_profile_test.rb new file mode 100644 index 0000000..c7af388 --- /dev/null +++ b/test/integration/users_profile_test.rb @@ -0,0 +1,19 @@ +require 'test_helper' +class UsersProfileTest < ActionDispatch::IntegrationTest + include ApplicationHelper + def setup + @user = users(:michael) + end + test "profile display" do + get user_path(@user) + assert_template 'users/show' + assert_select 'title', full_title(@user.name) + assert_select 'h1', text: @user.name + assert_select 'h1>img.gravatar' + assert_match @user.microposts.count.to_s, response.body + assert_select 'div.pagination' + @user.microposts.paginate(page: 1).each do |micropost| + assert_match micropost.content, response.body + end + end +end \ No newline at end of file diff --git a/test/integration/users_signup_test.rb b/test/integration/users_signup_test.rb new file mode 100644 index 0000000..f45bf95 --- /dev/null +++ b/test/integration/users_signup_test.rb @@ -0,0 +1,45 @@ +require 'test_helper' +class UsersSignupTest < ActionDispatch::IntegrationTest + def setup + ActionMailer::Base.deliveries.clear + end + test "invalid signup information" do + get signup_path + assert_no_difference 'User.count' do + post users_path, user: { name: "", + email: "user@invalid", + password: "foo", + password_confirmation: "bar" } + end + assert_template 'users/new' + assert_select 'div#error_explanation' + assert_select 'div.field_with_errors' + end + test "valid signup information with account activation" do + get signup_path + assert_difference 'User.count', 1 do + post users_path, user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } + end + assert_equal 1, ActionMailer::Base.deliveries.size + user = assigns(:user) + assert_not user.activated? + # Try to log in before activation. + log_in_as(user) + assert_not is_logged_in? + # Invalid activation token + get edit_account_activation_path("invalid token") + assert_not is_logged_in? + # Valid token, wrong email + get edit_account_activation_path(user.activation_token, email: 'wrong') + assert_not is_logged_in? + # Valid activation token + get edit_account_activation_path(user.activation_token, email: user.email) + assert user.reload.activated? + follow_redirect! + assert_template 'users/show' + assert is_logged_in? + end +end \ No newline at end of file diff --git a/test/mailers/previews/user_mailer_preview.rb b/test/mailers/previews/user_mailer_preview.rb new file mode 100644 index 0000000..a3a53b7 --- /dev/null +++ b/test/mailers/previews/user_mailer_preview.rb @@ -0,0 +1,17 @@ +# Preview all emails at http://localhost:3000/rails/mailers/user_mailer +class UserMailerPreview < ActionMailer::Preview +# Preview this email at +# http://localhost:3000/rails/mailers/user_mailer/account_activation + def account_activation + user = User.first + user.activation_token = User.new_token + UserMailer.account_activation(user) + end +# Preview this email at +# http://localhost:3000/rails/mailers/user_mailer/password_reset + def password_reset + user = User.first + user.reset_token = User.new_token + UserMailer.password_reset(user) + end +end \ No newline at end of file diff --git a/test/mailers/user_mailer_test.rb b/test/mailers/user_mailer_test.rb new file mode 100644 index 0000000..71dcb4e --- /dev/null +++ b/test/mailers/user_mailer_test.rb @@ -0,0 +1,24 @@ +require 'test_helper' +class UserMailerTest < ActionMailer::TestCase + test "account_activation" do + user = users(:michael) + user.activation_token = User.new_token + mail = UserMailer.account_activation(user) + assert_equal "Account activation", mail.subject + assert_equal [user.email], mail.to + assert_equal ["noreply@example.com"], mail.from + assert_match user.name, mail.body.encoded + assert_match user.activation_token, mail.body.encoded + assert_match CGI::escape(user.email), mail.body.encoded + end + test "password_reset" do + user = users(:michael) + user.reset_token = User.new_token + mail = UserMailer.password_reset(user) + assert_equal "Password reset", mail.subject + assert_equal [user.email], mail.to + assert_equal ["noreply@example.com"], mail.from + assert_match user.reset_token, mail.body.encoded + assert_match CGI::escape(user.email), mail.body.encoded + end +end \ No newline at end of file diff --git a/test/models/micropost_test.rb b/test/models/micropost_test.rb index def8e93..dc7e172 100644 --- a/test/models/micropost_test.rb +++ b/test/models/micropost_test.rb @@ -4,4 +4,28 @@ class MicropostTest < ActiveSupport::TestCase # test "the truth" do # assert true # end + def setup + @user = users(:michael) + # This code is not idiomatically correct. + @micropost = @user.microposts.build(content: "Lorem ipsum") + end + test "should be valid" do + assert @micropost.valid? + end + test "user id should be present" do + @micropost.user_id = nil + assert_not @micropost.valid? + end + + test "content should be present " do + @micropost.content = " " + assert_not @micropost.valid? + end + test "content should be at most 140 characters" do + @micropost.content = "a" * 141 + assert_not @micropost.valid? + end + test "order should be most recent first" do + assert_equal Micropost.first, microposts(:most_recent) + end end diff --git a/test/models/relationship_test.rb b/test/models/relationship_test.rb new file mode 100644 index 0000000..700cc41 --- /dev/null +++ b/test/models/relationship_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class RelationshipTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/user_test.rb b/test/models/user_test.rb index 82f61e0..a6d832c 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -4,4 +4,105 @@ 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 = " " + assert_not @user.valid? + end + + test "email should be present" do + @user.email = " " + 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.jp 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 addresses should be unique" do + duplicate_user = @user.dup + duplicate_user.email = @user.email.upcase + @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 (nonblank)" 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 = "a" * 5 + assert_not @user.valid? + end + + test "authenticated? should return false for a user with nil digest" do + assert_not @user.authenticated?(:remember, '') + end + + test "associated microposts should be destroyed" do + @user.save + @user.microposts.create!(content: "Lorem ipsum") + assert_difference 'Micropost.count', -1 do + @user.destroy + end + end + test "should follow and unfollow a user" do + michael = users(:michael) + archer = users(:archer) + assert_not michael.following?(archer) + michael.follow(archer) + assert michael.following?(archer) + assert archer.followers.include?(michael) + michael.unfollow(archer) + assert_not michael.following?(archer) + end + + test "feed should have the right posts" do + michael = users(:michael) + archer = users(:archer) + lana = users(:lana) + # Posts from followed user + lana.microposts.each do |post_following| + assert michael.feed.include?(post_following) + end + # Posts from self + michael.microposts.each do |post_self| + assert michael.feed.include?(post_self) + end + # Posts from unfollowed user + archer.microposts.each do |post_unfollowed| + assert_not michael.feed.include?(post_unfollowed) + end + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 92e39b2..cf98607 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,6 +5,26 @@ class ActiveSupport::TestCase # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. fixtures :all + def is_logged_in? + !session[:user_id].nil? + end +# Logs in a test user. + def log_in_as(user, options = {}) + password = options[:password] || 'password' + remember_me = options[:remember_me] || '1' + if integration_test? + post login_path, session: { email: user.email, + password: password, + remember_me: remember_me } + else + session[:user_id] = user.id + end + end + private +# Returns true inside an integration test. + def integration_test? + defined?(post_via_redirect) + end # Add more helper methods to be used by all tests here... end