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