From 5a41810b2ca55cc0cc712c509cc427f79dbc017a Mon Sep 17 00:00:00 2001 From: ngango0205 Date: Mon, 16 Jul 2018 15:55:00 +0700 Subject: [PATCH 1/2] chapter_13 part 2 --- app/controllers/users_controller.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 692caa8..a8c8846 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -6,7 +6,7 @@ class UsersController < ApplicationController def index @users = User.where(activated: true).page(params[:page]) - .per(Settings.page.per_page) + .per(Settings.page.per_page) end def new @@ -69,10 +69,10 @@ def find_user end def logged_in_user - unless logged_in? - flash[:danger] = t "title2" - redirect_to login_url - end + unless logged_in? + flash[:danger] = t "title2" + redirect_to login_url + end end def correct_user @@ -84,4 +84,3 @@ def admin_user redirect_to root_url unless current_user.admin? end end - From 6e3577e86652f061d454cb68e59b5bd682c5af23 Mon Sep 17 00:00:00 2001 From: ngango0205 Date: Wed, 18 Jul 2018 08:57:07 +0700 Subject: [PATCH 2/2] Chap 14 --- app/assets/javascripts/relationships.coffee | 3 ++ app/assets/stylesheets/custom.scss | 39 ++++++++++++++ app/assets/stylesheets/relationships.scss | 3 ++ app/controllers/relationships_controller.rb | 23 ++++++++ app/controllers/static_pages_controller.rb | 2 +- app/controllers/users_controller.rb | 14 ++++- app/models/relationship.rb | 4 ++ app/models/user.rb | 53 ++++++++++++++----- app/views/relationships/create.js.erb | 2 + app/views/relationships/destroy.js.erb | 2 + app/views/shared/_stats.html.erb | 15 ++++++ app/views/static_pages/home.html.erb | 3 ++ app/views/users/_follow.html.erb | 4 ++ app/views/users/_follow_form.html.erb | 9 ++++ app/views/users/_unfollow.html.erb | 5 ++ app/views/users/show.html.erb | 4 ++ app/views/users/show_follow.html.erb | 30 +++++++++++ config/application.rb | 1 + config/locales/en.yml | 4 +- config/routes.rb | 6 +++ .../20180718013926_create_relationships.rb | 13 +++++ db/schema.rb | 12 ++++- db/seeds.rb | 6 +++ 23 files changed, 239 insertions(+), 18 deletions(-) create mode 100644 app/assets/javascripts/relationships.coffee create mode 100644 app/assets/stylesheets/relationships.scss create mode 100644 app/controllers/relationships_controller.rb create mode 100644 app/models/relationship.rb create mode 100644 app/views/relationships/create.js.erb create mode 100644 app/views/relationships/destroy.js.erb create mode 100644 app/views/shared/_stats.html.erb create mode 100644 app/views/users/_follow.html.erb create mode 100644 app/views/users/_follow_form.html.erb create mode 100644 app/views/users/_unfollow.html.erb create mode 100644 app/views/users/show_follow.html.erb create mode 100644 db/migrate/20180718013926_create_relationships.rb 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/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss index b2a993a..3e57548 100644 --- a/app/assets/stylesheets/custom.scss +++ b/app/assets/stylesheets/custom.scss @@ -147,6 +147,45 @@ aside { .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; + } + a { + padding: 0; + } +} + +.users{ + .follow { + padding: 0; + } +} input, textarea, select, .uneditable-input { border: 1px solid $colour6; 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/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb new file mode 100644 index 0000000..c5c4f9c --- /dev/null +++ b/app/controllers/relationships_controller.rb @@ -0,0 +1,23 @@ +class RelationshipsController < ApplicationController + before_action :logged_in_user + + def create + user = User.find_by id: params[:followed_id] + current_user.follow user + respond_to do |format| + format.html { redirect_to @user } + format.js + end + redirect_to user + end + + def destroy + user = Relationship.find_by(id: params[:id]).followed + current_user.unfollow user + respond_to do |format| + format.html { redirect_to @user } + format.js + end + redirect_to user + end +end diff --git a/app/controllers/static_pages_controller.rb b/app/controllers/static_pages_controller.rb index ee39a52..7a7eefd 100644 --- a/app/controllers/static_pages_controller.rb +++ b/app/controllers/static_pages_controller.rb @@ -2,7 +2,7 @@ class StaticPagesController < ApplicationController def home return unless logged_in? @micropost = current_user.microposts.build - @feed_items = current_user.microposts.micropost_desc.page(params[:page]).per Settings.page.per_page + @feed_items = current_user.feed.page(params[:page]).per Settings.page.per_page end def help diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index a8c8846..95c4c60 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,12 +1,12 @@ class UsersController < ApplicationController - before_action :find_user, only: [:show, :edit, :update] + before_action :find_user, only: [:show, :edit, :update, :following] before_action :logged_in_user, only: [:index, :edit, :update, :show, :destroy] before_action :correct_user, only: [:edit, :update] before_action :admin_user, only: :destroy def index @users = User.where(activated: true).page(params[:page]) - .per(Settings.page.per_page) + .per Settings.page.per_page end def new @@ -55,6 +55,16 @@ def destroy end end + def following + @users = @user.following.page(params[:page]) + render :show_follow + end + + def followers + @users = @user.followers.page(params[:page]) + render :show_follow + end + private def user_params diff --git a/app/models/relationship.rb b/app/models/relationship.rb new file mode 100644 index 0000000..d362152 --- /dev/null +++ b/app/models/relationship.rb @@ -0,0 +1,4 @@ +class Relationship < ApplicationRecord + belongs_to :follower, class_name: User.name + belongs_to :followed, class_name: User.name +end diff --git a/app/models/user.rb b/app/models/user.rb index ce3301c..fb33701 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,22 +1,30 @@ class User < ApplicationRecord has_many :microposts, dependent: :destroy + has_many :active_relationships, class_name: Relationship.name, + foreign_key: "follower_id", + dependent: :destroy + has_many :passive_relationships, class_name: Relationship.name, + 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: Settings.maximum.length_name } + validates :name, presence: true, length: {maximum: Settings.maximum.length_name} VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i - validates :email, presence: true, length: { maximum: Settings.maximum.length_email }, - format: { with: VALID_EMAIL_REGEX }, - uniqueness: { case_sensitive: false } + validates :email, presence: true, length: {maximum: Settings.maximum.length_email}, + format: {with: VALID_EMAIL_REGEX}, + uniqueness: {case_sensitive: false} has_secure_password - validates :password, presence: true, length: { minimum: Settings.minimum.length_pass } + validates :password, presence: true, length: {minimum: Settings.minimum.length_pass} class << self def digest string - cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : - BCrypt::Engine.cost - BCrypt::Password.create(string, cost: cost) + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : + BCrypt::Engine.cost + BCrypt::Password.create(string, cost: cost) end def new_token @@ -31,7 +39,7 @@ def remember def authenticated? attribute, token digest = send "#{attribute}_digest" return false if digest.nil? - BCrypt::Password.new(digest).is_password? token + BCrypt::Password.new(digest).is_password? token end def forget @@ -46,7 +54,7 @@ def send_activation_email UserMailer.account_activation(self).deliver_now end - def create_reset_digest + 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) @@ -60,6 +68,25 @@ def password_reset_expired? reset_sent_at < Settings.expired_time end + 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 + + def follow other_user + following << other_user + end + + def unfollow other_user + following.delete other_user + end + + def following? other_user + following.include? other_user + end + private def downcase_email @@ -67,7 +94,7 @@ def downcase_email end def create_activation_digest - self.activation_token = User.new_token - self.activation_digest = User.digest activation_token - end + self.activation_token = User.new_token + self.activation_digest = User.digest activation_token + end end diff --git a/app/views/relationships/create.js.erb b/app/views/relationships/create.js.erb new file mode 100644 index 0000000..f4faf45 --- /dev/null +++ b/app/views/relationships/create.js.erb @@ -0,0 +1,2 @@ +$("#follow_form").html("<%= j render "users/unfollow" %>"); +$("#followers").html("<%= @user.followers.count %>" diff --git a/app/views/relationships/destroy.js.erb b/app/views/relationships/destroy.js.erb new file mode 100644 index 0000000..9a2277b --- /dev/null +++ b/app/views/relationships/destroy.js.erb @@ -0,0 +1,2 @@ +$("#follow_form").html("<%= j render "users/unfollow" %>"); +$("#followers").html("<%= @user.followers.count %>"); diff --git a/app/views/shared/_stats.html.erb b/app/views/shared/_stats.html.erb new file mode 100644 index 0000000..d60c3f4 --- /dev/null +++ b/app/views/shared/_stats.html.erb @@ -0,0 +1,15 @@ +<% @user ||= current_user %> + diff --git a/app/views/static_pages/home.html.erb b/app/views/static_pages/home.html.erb index 5205d79..c8b1841 100644 --- a/app/views/static_pages/home.html.erb +++ b/app/views/static_pages/home.html.erb @@ -5,6 +5,9 @@ +
+ <%= render 'shared/stats' %> +
<%= render "shared/micropost_form" %>
diff --git a/app/views/users/_follow.html.erb b/app/views/users/_follow.html.erb new file mode 100644 index 0000000..db28aeb --- /dev/null +++ b/app/views/users/_follow.html.erb @@ -0,0 +1,4 @@ +<%= form_for current_user.active_relationships.build, remote: true do |f| %> +
<%= hidden_field_tag :followed_id, @user.id %>
+ <%= f.submit "Follow", class: "btn btn-primary" %> +<% end %> diff --git a/app/views/users/_follow_form.html.erb b/app/views/users/_follow_form.html.erb new file mode 100644 index 0000000..77e5e80 --- /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 %> diff --git a/app/views/users/_unfollow.html.erb b/app/views/users/_unfollow.html.erb new file mode 100644 index 0000000..af445d2 --- /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 %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 23a0ec2..05b7de1 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -7,8 +7,12 @@ <%= @user.name %> +
+ <%= render "shared/stats" %> +
+ <%= render "follow_form" if logged_in? %> <% if @user.microposts.any? %>

<%= t(".microposts") %><%= @user.microposts.count %>

    diff --git a/app/views/users/show_follow.html.erb b/app/views/users/show_follow.html.erb new file mode 100644 index 0000000..5ed4b25 --- /dev/null +++ b/app/views/users/show_follow.html.erb @@ -0,0 +1,30 @@ +<% provide :title, t(".follow") %> +
    + +
    +

    <%= @title %>

    + <% if @users.any? %> + + <%= paginate @users, theme: "twitter-bootstrap-3" %> + <% end %> +
    +
    diff --git a/config/application.rb b/config/application.rb index 2c1e565..506efea 100644 --- a/config/application.rb +++ b/config/application.rb @@ -12,6 +12,7 @@ class Application < Rails::Application config.load_defaults 5.1 config.i18n.load_path += Dir[Rails.root.join("my", "locales", "*.{rb,yml}").to_s] config.i18n.available_locales = [:en, :vi, :jp] + config.action_view.embed_authenticity_token_in_remote_forms = true # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers diff --git a/config/locales/en.yml b/config/locales/en.yml index 6846120..140003a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -86,7 +86,9 @@ en: title: "About" title1: "Contact" title2: "News" - + show_follow: + follow: "Following" + follower: "Follower" sessions: new: title: "Log In" diff --git a/config/routes.rb b/config/routes.rb index a87ad9a..d6b463e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,4 +15,10 @@ resources :account_activations, only: :edit resources :password_resets, only: [:new, :create, :edit, :update] resources :microposts, only: [:create, :destroy] + resources :users do + member do + get :following, :followers + end + end + resources :relationships, only: [:create, :destroy] end diff --git a/db/migrate/20180718013926_create_relationships.rb b/db/migrate/20180718013926_create_relationships.rb new file mode 100644 index 0000000..dd01cdc --- /dev/null +++ b/db/migrate/20180718013926_create_relationships.rb @@ -0,0 +1,13 @@ +class CreateRelationships < ActiveRecord::Migration[5.1] + def change + create_table :relationships do |t| + t.integer :follower_id + t.integer :followed_id + + t.timestamps + 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 071b56b..ed6652d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180717061103) do +ActiveRecord::Schema.define(version: 20180718013926) do create_table "microposts", force: :cascade do |t| t.text "content" @@ -22,6 +22,16 @@ t.index ["user_id"], name: "index_microposts_on_user_id" end + 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 + t.index ["followed_id"], name: "index_relationships_on_followed_id" + t.index ["follower_id", "followed_id"], name: "index_relationships_on_follower_id_and_followed_id", unique: true + t.index ["follower_id"], name: "index_relationships_on_follower_id" + end + create_table "users", force: :cascade do |t| t.string "name" t.string "email" diff --git a/db/seeds.rb b/db/seeds.rb index 1e932f3..b87bda7 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -22,3 +22,9 @@ content = Faker::Lorem.sentence(5) users.each { |user| user.microposts.create!(content: content) } end +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) }