From 11d5608089f3d9a8ccb4dfea14ac12db3b0b7c8e Mon Sep 17 00:00:00 2001 From: leviethung123 Date: Fri, 25 Sep 2020 14:40:00 +0700 Subject: [PATCH 1/2] chapter 13 User microposts --- .gitignore | 1 + Gemfile | 4 ++ Gemfile.lock | 18 ++++++- app/assets/stylesheets/custom.scss | 50 +++++++++++++++++++ .../account_activations_controller.rb | 14 ++++++ app/controllers/application_controller.rb | 8 +++ app/controllers/microposts_controller.rb | 33 ++++++++++++ app/controllers/password_resets_controller.rb | 49 ++++++++++++++++++ app/controllers/sessions_controller.rb | 14 ++++-- app/controllers/static_pages_controller.rb | 7 ++- app/controllers/users_controller.rb | 10 ++-- app/helpers/microposts_helper.rb | 2 + app/helpers/password_resets_helper.rb | 2 + app/helpers/sessions_helper.rb | 3 +- app/mailers/application_mailer.rb | 2 +- app/mailers/user_mailer.rb | 11 ++++ app/models/micropost.rb | 13 +++++ app/models/user.rb | 48 +++++++++++++++++- app/views/microposts/_micropost.html.erb | 14 ++++++ app/views/password_resets/edit.html.erb | 15 ++++++ app/views/password_resets/new.html.erb | 11 ++++ app/views/sessions/new.html.erb | 1 + app/views/shared/_error_messages.html.erb | 6 +-- app/views/shared/_feed.html.erb | 6 +++ app/views/shared/_micropost_form.html.erb | 19 +++++++ app/views/shared/_user_info.html.erb | 4 ++ app/views/static_pages/home.html.erb | 18 ++++++- .../user_mailer/account_activation.html.erb | 8 +++ .../user_mailer/account_activation.text.erb | 7 +++ app/views/user_mailer/password_reset.html.erb | 7 +++ app/views/user_mailer/password_reset.text.erb | 6 +++ app/views/users/_form.html.erb | 4 +- app/views/users/show.html.erb | 19 +++++-- config/environments/development.rb | 18 +++++-- config/environments/production.rb | 16 +++++- config/locales/en.yml | 24 +++++++++ config/locales/vi.yml | 15 ++++++ config/routes.rb | 11 ++++ config/settings/development.yml | 3 ++ .../20200930024226_add_activation_to_users.rb | 7 +++ .../20200930152853_add_reset_to_users.rb | 6 +++ .../20200930160008_create_microposts.rb | 11 ++++ ...te_active_storage_tables.active_storage.rb | 27 ++++++++++ db/schema.rb | 8 ++- db/seeds.rb | 15 +++++- .../account_activations_controller_test.rb | 7 +++ .../controllers/microposts_controller_test.rb | 7 +++ .../password_resets_controller_test.rb | 14 ++++++ test/fixtures/microposts.yml | 9 ++++ test/mailers/previews/user_mailer_preview.rb | 14 ++++++ test/mailers/user_mailer_test.rb | 20 ++++++++ test/models/micropost_test.rb | 7 +++ 52 files changed, 639 insertions(+), 34 deletions(-) create mode 100644 app/controllers/account_activations_controller.rb create mode 100644 app/controllers/microposts_controller.rb create mode 100644 app/controllers/password_resets_controller.rb create mode 100644 app/helpers/microposts_helper.rb create mode 100644 app/helpers/password_resets_helper.rb create mode 100644 app/mailers/user_mailer.rb create mode 100644 app/models/micropost.rb create mode 100644 app/views/microposts/_micropost.html.erb create mode 100644 app/views/password_resets/edit.html.erb create mode 100644 app/views/password_resets/new.html.erb create mode 100644 app/views/shared/_feed.html.erb create mode 100644 app/views/shared/_micropost_form.html.erb create mode 100644 app/views/shared/_user_info.html.erb create mode 100644 app/views/user_mailer/account_activation.html.erb create mode 100644 app/views/user_mailer/account_activation.text.erb create mode 100644 app/views/user_mailer/password_reset.html.erb create mode 100644 app/views/user_mailer/password_reset.text.erb create mode 100644 db/migrate/20200930024226_add_activation_to_users.rb create mode 100644 db/migrate/20200930152853_add_reset_to_users.rb create mode 100644 db/migrate/20200930160008_create_microposts.rb create mode 100644 db/migrate/20201001033623_create_active_storage_tables.active_storage.rb create mode 100644 test/controllers/account_activations_controller_test.rb create mode 100644 test/controllers/microposts_controller_test.rb create mode 100644 test/controllers/password_resets_controller_test.rb create mode 100644 test/fixtures/microposts.yml create mode 100644 test/mailers/previews/user_mailer_preview.rb create mode 100644 test/mailers/user_mailer_test.rb create mode 100644 test/models/micropost_test.rb diff --git a/.gitignore b/.gitignore index 7fe04b9..a695304 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ yarn-debug.log* config/settings.local.yml config/settings/*.local.yml config/environments/*.local.yml +config/application.yml diff --git a/Gemfile b/Gemfile index 774eee8..6d42c8a 100644 --- a/Gemfile +++ b/Gemfile @@ -9,6 +9,7 @@ gem "bootstrap-sass", "3.4.1" gem "bootstrap-will_paginate", "1.0.0" gem "config" gem "faker", "2.1.2" +gem "figaro" gem "jbuilder", "~> 2.7" gem "puma", "~> 4.1" gem "rails", "~> 6.0.3", ">= 6.0.3.3" @@ -18,6 +19,9 @@ gem "sqlite3", "~> 1.4" gem "turbolinks", "~> 5" gem "webpacker", "~> 4.0" gem "will_paginate", "3.1.8" +gem "image_processing", "1.9.3" +gem "mini_magick", "4.9.5" +gem "active_storage_validations", "0.8.2" group :development, :test do gem "byebug", platforms: [:mri, :mingw, :x64_mingw] diff --git a/Gemfile.lock b/Gemfile.lock index 5e3b49c..a600a44 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -37,6 +37,8 @@ GEM erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) + active_storage_validations (0.8.2) + rails (>= 5.2.0) activejob (6.0.3.3) activesupport (= 6.0.3.3) globalid (>= 0.3.6) @@ -59,7 +61,7 @@ GEM addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) ast (2.4.1) - autoprefixer-rails (10.0.0.2) + autoprefixer-rails (10.0.1.0) execjs bcrypt (3.1.13) bindex (0.8.1) @@ -98,7 +100,7 @@ GEM concurrent-ruby (~> 1.0) dry-equalizer (0.3.0) dry-inflector (0.2.0) - dry-initializer (3.0.3) + dry-initializer (3.0.4) dry-logic (1.0.8) concurrent-ruby (~> 1.0) dry-core (~> 0.2) @@ -130,10 +132,15 @@ GEM faker (2.1.2) i18n (>= 0.8) ffi (1.13.1) + figaro (1.2.0) + thor (>= 0.14.0, < 2) globalid (0.4.2) activesupport (>= 4.2.0) i18n (1.8.5) concurrent-ruby (~> 1.0) + image_processing (1.9.3) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.13, < 3) jaro_winkler (1.5.4) jbuilder (2.10.1) activesupport (>= 5.0.0) @@ -149,6 +156,7 @@ GEM mimemagic (~> 0.3.2) method_source (1.0.0) mimemagic (0.3.5) + mini_magick (4.9.5) mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.14.2) @@ -215,6 +223,8 @@ GEM rack (>= 1.1) rubocop (>= 0.72.0) ruby-progressbar (1.10.1) + ruby-vips (2.0.17) + ffi (~> 1.9) rubyzip (2.3.0) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) @@ -283,6 +293,7 @@ PLATFORMS web-unknown DEPENDENCIES + active_storage_validations (= 0.8.2) bcrypt (= 3.1.13) bootsnap (>= 1.4.2) bootstrap-sass (= 3.4.1) @@ -291,8 +302,11 @@ DEPENDENCIES capybara (>= 2.15) config faker (= 2.1.2) + figaro + image_processing (= 1.9.3) jbuilder (~> 2.7) listen (~> 3.2) + mini_magick (= 4.9.5) puma (~> 4.1) rails (~> 6.0.3, >= 6.0.3.3) rails-i18n diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss index bc8ee01..ea2c193 100644 --- a/app/assets/stylesheets/custom.scss +++ b/app/assets/stylesheets/custom.scss @@ -146,3 +146,53 @@ input { border-bottom: 1px solid $gray-lighter; } } + +.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; + width: 8%; + } +} +aside { + textarea { + height: 100px; + margin-bottom: 5px; + } +} +span.image { + margin-top: 10px; + width: 20px; + input { + border: 0; + } +} + +input[type="file"] { + display: block; + position: absolute; +} diff --git a/app/controllers/account_activations_controller.rb b/app/controllers/account_activations_controller.rb new file mode 100644 index 0000000..3cb0aa4 --- /dev/null +++ b/app/controllers/account_activations_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 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c5f5898..edde33a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -11,4 +11,12 @@ def set_locale def default_url_options {locale: I18n.locale} end + + def logged_in_user + unless logged_in? + store_location + flash[:danger] = t("login_in.messages") + redirect_to login_url + end + end end diff --git a/app/controllers/microposts_controller.rb b/app/controllers/microposts_controller.rb new file mode 100644 index 0000000..8a8068b --- /dev/null +++ b/app/controllers/microposts_controller.rb @@ -0,0 +1,33 @@ +class MicropostsController < ApplicationController + before_action :logged_in_user, only: [:create, :destroy] + before_action :correct_user, only: :destroy + + def create + @micropost = current_user.microposts.build micropost_params + @micropost.image.attach(params[:micropost][:image]) + if @micropost.save + flash[:success] = t("micropost.messages1") + redirect_to root_url + else + @feed_items = current_user.feed.paginate(page: params[:page]) + render "static_pages/home" + end + end + + def destroy + @micropost.destroy + flash[:success] = t("micropost.messages2") + 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 unless @micropost + end +end diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb new file mode 100644 index 0000000..3ea2bbc --- /dev/null +++ b/app/controllers/password_resets_controller.rb @@ -0,0 +1,49 @@ +class PasswordResetsController < ApplicationController + before_action :get_user, only: [:edit, :update] + before_action :valid_user, 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 update + if params[:user][:password].empty? + @user.errors.add(:password, "can't be empty") + render 'edit' + elsif @user.update(user_params) + log_in @user + @user.update_attribute(:reset_digest, nil) + flash[:success] = "Password has been reset." + redirect_to @user + else + render :edit + end + end + + def edit; end + + private + + def get_user + @user = User.find_by email: params[:email] + end + def valid_user + return if (@user && @user.activated? && @user.authenticated?(:reset, params[:id])) + redirect_to root_url + end + + def user_params + params.require(:user).permit :password, :password_confirmation + end +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 01a4ae5..9e28da6 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -4,10 +4,16 @@ def new; end def create user = User.find_by(email: params[:session][:email].downcase) if user&.authenticate(params[:session][:password]) - log_in user - params[:session][:remember_me] == "1" ? remember(user) : forget(user) - remember user - redirect_back_or user + if user.activated? + log_in user + params[:session][:remember_me] == "1" ? remember(user) : forget(user) + remember user + redirect_back_or user + else + message = "Account not activated. Check your email for the activation link." + flash[:warning] = message + redirect_to root_url + end else flash.now[:danger] = t("session.flash") render :new diff --git a/app/controllers/static_pages_controller.rb b/app/controllers/static_pages_controller.rb index 5268023..b0e8676 100644 --- a/app/controllers/static_pages_controller.rb +++ b/app/controllers/static_pages_controller.rb @@ -1,5 +1,10 @@ class StaticPagesController < ApplicationController - def home; end + def home + if logged_in? + @micropost = current_user.microposts.build + @feed_items = current_user.feed.paginate(page: params[:page]) + end + end def help; end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 0ed694b..ffdef00 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -4,7 +4,9 @@ class UsersController < ApplicationController before_action :correct_user, only: %i(edit update) before_action :admin_user, only: :destroy - def show; end + def show + @microposts = @user.microposts.paginate(page: params[:page]) + end def edit; end @@ -15,9 +17,9 @@ def new def create @user = User.new user_params if @user.save - flash[:success] = t ".success" - log_in @user - redirect_to @user + @user.send_activation_email + flash[:info] = t "email.messages" + redirect_to root_url else render :new end diff --git a/app/helpers/microposts_helper.rb b/app/helpers/microposts_helper.rb new file mode 100644 index 0000000..f08aad2 --- /dev/null +++ b/app/helpers/microposts_helper.rb @@ -0,0 +1,2 @@ +module MicropostsHelper +end 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/sessions_helper.rb b/app/helpers/sessions_helper.rb index b2505a6..ef14bb6 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -14,7 +14,8 @@ def current_user @current_user ||= User.find_by id: user_id elsif (user_id = cookies.signed[:user_id]) user = User.find_by id: user_id - if user&.authenticated?(cookies[:remember_token]) + + if user&.authenticated?(:remember, cookies[:remember_token]) log_in user @current_user = user end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 3c34c81..2dfdba2 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,4 +1,4 @@ class ApplicationMailer < ActionMailer::Base - default from: "from@example.com" + 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..aaae448 --- /dev/null +++ b/app/mailers/user_mailer.rb @@ -0,0 +1,11 @@ +class UserMailer < ApplicationMailer + def account_activation user + @user = user + mail to: user.email, subject: "Account activation" + end + + 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 new file mode 100644 index 0000000..ce88392 --- /dev/null +++ b/app/models/micropost.rb @@ -0,0 +1,13 @@ +class Micropost < ApplicationRecord + belongs_to :user + # default_scope -> { order(created_at: :desc) } + scope :recent_posts, -> {order created_at: :desc} + has_one_attached :image + validates :user_id, presence: true + validates :content, presence: true, length: { maximum: 140 } + validates :image, content_type: { in: %w[image/jpeg image/gif image/png], message: "must be a valid image format" }, size: { less_than: 5.megabytes, message: "should be less than 5MB" } + + def display_image + image.variant(resize_to_limit: [500, 500]) + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 99ab078..37f9636 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,7 +1,8 @@ class User < ApplicationRecord - attr_accessor :remember_token + attr_accessor :remember_token, :activation_token, :reset_token VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i.freeze - + before_save :downcase_email + before_create :create_activation_digest before_save{email.downcase!} validates :name, presence: true, length: {maximum: Settings.user.name} @@ -11,6 +12,7 @@ class User < ApplicationRecord has_secure_password validates :password, presence: true, length: {minimum: Settings.user.password}, allow_nil: true + has_many :microposts, dependent: :destroy def self.digest string cost = if ActiveModel::SecurePassword.min_cost @@ -34,9 +36,51 @@ def authenticated? remember_token return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) + + end + + def authenticated? attribute, token + digest = send "#{attribute}_digest" + return false if digest.nil? + + BCrypt::Password.new(digest).is_password? token + end + + def activate + update_attribute :activated, true + update_attribute :activated_at, DateTime.now + end + + def send_activation_email + UserMailer.account_activation(self).deliver_now + end + + def create_reset_digest + self.reset_token = User.new_token + update_attributes reset_digest: User.digest(reset_token), reset_sent_at: DateTime.now + end + + def send_password_reset_email + UserMailer.password_reset(self).deliver_now + end def forget update_attribute(:remember_digest, nil) end + + def feed + Micropost.where("user_id = ?", id) + end + + private + + def downcase_email + self.email.downcase! + end + + def create_activation_digest + self.activation_token = User.new_token + self.activation_digest = User.digest(activation_token) + end end diff --git a/app/views/microposts/_micropost.html.erb b/app/views/microposts/_micropost.html.erb new file mode 100644 index 0000000..cb6c91b --- /dev/null +++ b/app/views/microposts/_micropost.html.erb @@ -0,0 +1,14 @@ +
  • + <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> + <%= link_to micropost.user.name, micropost.user %> + + <%= micropost.content %> + <%= image_tag micropost.display_image if micropost.image.attached? %> + + + Posted <%= time_ago_in_words(micropost.created_at) %> ago. + <% if current_user? micropost.user %> + <%= link_to t("micropost.button"), micropost, method: :delete, data: {confirm: t("micropost.confirm")} %> + <% end %> + +
  • diff --git a/app/views/password_resets/edit.html.erb b/app/views/password_resets/edit.html.erb new file mode 100644 index 0000000..1292fbe --- /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', object: f.object %> + <%= 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 %> +
    +
    diff --git a/app/views/password_resets/new.html.erb b/app/views/password_resets/new.html.erb new file mode 100644 index 0000000..4c3799c --- /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 %> +
    +
    diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index a51c500..c3415c2 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -6,6 +6,7 @@ <%= f.label :email %> <%= f.email_field :email, class: "form-control" %> <%= f.label :password, t("users.new.lable_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 %> diff --git a/app/views/shared/_error_messages.html.erb b/app/views/shared/_error_messages.html.erb index 6ae0a98..10fa4c8 100644 --- a/app/views/shared/_error_messages.html.erb +++ b/app/views/shared/_error_messages.html.erb @@ -1,10 +1,10 @@ -<% if @user.errors.any? %> +<% if object.errors.any? %>
    - <%= t("users.messages") %> <%= pluralize(@user.errors.count, t("users.error")) %>. + <%= t("users.messages") %> <%= pluralize object.errors.count, t("users.error") %>.
    diff --git a/app/views/shared/_feed.html.erb b/app/views/shared/_feed.html.erb new file mode 100644 index 0000000..f15a53c --- /dev/null +++ b/app/views/shared/_feed.html.erb @@ -0,0 +1,6 @@ +<% if @feed_items.any? %> +
      + <%= render @feed_items %> +
    + <%= will_paginate @feed_items, params: { controller: :static_pages, action: :home } %> +<% end %> diff --git a/app/views/shared/_micropost_form.html.erb b/app/views/shared/_micropost_form.html.erb new file mode 100644 index 0000000..7ebc65b --- /dev/null +++ b/app/views/shared/_micropost_form.html.erb @@ -0,0 +1,19 @@ +<%= form_for(@micropost, html: { multipart: true }) 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 :image %> + +<% end %> + + diff --git a/app/views/shared/_user_info.html.erb b/app/views/shared/_user_info.html.erb new file mode 100644 index 0000000..b9c43ba --- /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 t("micropost.title"), current_user %> +<%= pluralize(current_user.microposts.count, "micropost") %> diff --git a/app/views/static_pages/home.html.erb b/app/views/static_pages/home.html.erb index 8431798..488cae7 100644 --- a/app/views/static_pages/home.html.erb +++ b/app/views/static_pages/home.html.erb @@ -1,4 +1,19 @@ -<% provide :title, t("home") %> +<% if logged_in? %> +
    + +
    +

    Micropost Feed

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

    <%= t("title") %>

    @@ -8,3 +23,4 @@

    <%= link_to t("static_pages.home.button"), '#', class: "btn btn-lg btn-primary" %>
    +<% end %> 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..f64fe84 --- /dev/null +++ b/app/views/user_mailer/account_activation.html.erb @@ -0,0 +1,8 @@ +

    <%= t("logo") %>

    +

    Hi <%= @user.name %>,

    +

    + <%= t("email.sendemail")%> + <%= t("email.account")%> +

    +<%= link_to t("email.activate"), +edit_account_activation_url(id: @user.activation_token, email: @user.email) %> 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..4f396b4 --- /dev/null +++ b/app/views/user_mailer/account_activation.text.erb @@ -0,0 +1,7 @@ +Hi <%= @user.name %>, + +<%= t("email.sendemail")%> +<%= t("email.account")%> + +<%= edit_account_activation_url(id:@user.activation_token, email: +@user.email) %> 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..f65dbb0 --- /dev/null +++ b/app/views/user_mailer/password_reset.html.erb @@ -0,0 +1,7 @@ +

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

    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..6d29feb --- /dev/null +++ b/app/views/user_mailer/password_reset.text.erb @@ -0,0 +1,6 @@ +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. diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb index 9021e90..6f19a6e 100644 --- a/app/views/users/_form.html.erb +++ b/app/views/users/_form.html.erb @@ -1,5 +1,5 @@ -<%= form_for(@user) do |f| %> - <%= render "shared/error_messages", object: @user %> +<%= form_for (@user) do |f| %> + <%= render 'shared/error_messages', object: f.object %> <%= f.label :name, t("users.new.lable_name") %> <%= f.text_field :name, class: "form-control" %> <%= f.label :email %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 1460e92..c018c54 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,11 +1,20 @@ -<% provide :title, t("users.user") %> +<% provide(:title, @user.name) %>
    +
    + <% if @user.microposts.any? %> +

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

    +
      + <%= render @microposts %> +
    + <%= will_paginate @microposts %> + <% end %> +
    diff --git a/config/environments/development.rb b/config/environments/development.rb index 7648e65..e8d8764 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -11,7 +11,6 @@ # Show full error reports. config.consider_all_requests_local = true - # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. if Rails.root.join("tmp", "caching-dev.txt").exist? @@ -28,11 +27,23 @@ config.cache_store = :null_store end + + # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :local - # Don"t care if the mailer can"t send. - config.action_mailer.raise_delivery_errors = false + config.action_mailer.raise_delivery_errors = true + config.action_mailer.delivery_method = :smtp + config.action_mailer.default_url_options = {host: "localhost:3000" } + # SMTP settings for gmail + config.action_mailer.smtp_settings = { + address: "smtp.gmail.com", + port: 587, + user_name: ENV["USER_NAME"], + password: ENV["PASS_WORD"], + authentication: "plain", + enable_starttls_auto: true + } config.action_mailer.perform_caching = false @@ -58,5 +69,4 @@ # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. - config.file_watcher = ActiveSupport::EventedFileUpdateChecker end diff --git a/config/environments/production.rb b/config/environments/production.rb index 0ebb667..89a7808 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -16,7 +16,7 @@ # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). - # config.require_master_key = true + config.require_master_key = true # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. @@ -109,4 +109,18 @@ # config.active_record.database_selector = { delay: 2.seconds } # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session + + config.action_mailer.raise_delivery_errors = true + config.action_mailer.delivery_method = :smtp + host = '.herokuapp.com' + config.action_mailer.default_url_options = {host: host} + ActionMailer::Base.smtp_settings = { + address: 'smtp.sendgrid.net', + port: '587', + authentication: :plain, + user_name: ENV['SENDGRID_USERNAME'], + password: ENV['SENDGRID_PASSWORD'], + domain: 'heroku.com', + enable_starttls_auto: true + } end diff --git a/config/locales/en.yml b/config/locales/en.yml index 02ff24e..21c7c15 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -58,7 +58,10 @@ en: messages: "User deleted" messages1: "You sure?" view: "delete" +<<<<<<< HEAD messages3: "delete error" +======= +>>>>>>> chapter 12: Password reset login_in: messages: "Please log in." @@ -68,3 +71,24 @@ en: button: "Save changes" header: "Update your profile" link: "change" +<<<<<<< HEAD +======= + + email: + messages: "Please check your email to activate your account." + messagerb: "Account activation" + sendemail: " Welcome to the Sample App! Click on the link below to activate your" + account: "account:" + activate: "Activate" +<<<<<<< HEAD +>>>>>>> chapter 12: Password reset +======= + + micropost: + messages1: "Micropost created!" + messages2: "Micropost deleted" + button: "delete" + confirm: "You sure?" + messages3: "Maximum file size is 5MB. Please choose a smaller file." + title: "view my profile" +>>>>>>> chapter 13 User microposts diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 915ce05..bfecf90 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -68,3 +68,18 @@ vi: button: "Lưu Thay đổi" header: "Cập nhật hồ sơ của bạn" link: "Thay đổi" + + email: + messages: "Hãy kiểm tra email của bạn để kích hoạt tài khoản của bạn." + messagesrb: "Kích hoạt tài khoản" + sendemail: " Chào mừng bạn đến với Ứng dụng mẫu! Nhấp vào liên kết bên dưới để kích hoạt" + account: "tài khoản:" + activate: "Kích hoạt" + + micropost: + messages1: "Đã tạo micropost!" + messages2: "Micropost đã bị xóa" + button: "Xóa" + confirm: "Bạn chắc chắn?" + messages3: "Kích thước tệp tối đa là 5MB. Vui lòng chọn một tệp nhỏ hơn." + title: "xem hồ sơ của tôi" diff --git a/config/routes.rb b/config/routes.rb index 6ea0181..b7b71b1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,6 @@ Rails.application.routes.draw do + get 'password_resets/new' + get 'password_resets/edit' root "static_pages#home" get "/help", to: "static_pages#help" get "/contact", to: "static_pages#contact" @@ -10,4 +12,13 @@ delete "/logout", to: "sessions#destroy" post "/edit", to: "users#edit" resources :users +<<<<<<< HEAD +======= + resources :account_activations, only: :edit + resources :password_resets, only: [:new, :create, :edit, :update] +<<<<<<< HEAD +>>>>>>> chapter 12: Password reset +======= + resources :microposts, only: [:create, :destroy] +>>>>>>> chapter 13 User microposts end diff --git a/config/settings/development.yml b/config/settings/development.yml index 416fe7c..ba60224 100644 --- a/config/settings/development.yml +++ b/config/settings/development.yml @@ -3,5 +3,8 @@ user: email: 250 size: 80 password: 3 +<<<<<<< HEAD page: 10 +======= +>>>>>>> chapter 12: Password reset config.time_zone: "Hanoi" diff --git a/db/migrate/20200930024226_add_activation_to_users.rb b/db/migrate/20200930024226_add_activation_to_users.rb new file mode 100644 index 0000000..fdf40e8 --- /dev/null +++ b/db/migrate/20200930024226_add_activation_to_users.rb @@ -0,0 +1,7 @@ +class AddActivationToUsers < ActiveRecord::Migration[6.0] + def change + add_column :users, :activation_digest, :string + add_column :users, :activated, :boolean + add_column :users, :activated_at, :datetime + end +end diff --git a/db/migrate/20200930152853_add_reset_to_users.rb b/db/migrate/20200930152853_add_reset_to_users.rb new file mode 100644 index 0000000..03d33ed --- /dev/null +++ b/db/migrate/20200930152853_add_reset_to_users.rb @@ -0,0 +1,6 @@ +class AddResetToUsers < ActiveRecord::Migration[6.0] + def change + add_column :users, :reset_digest, :string + add_column :users, :reset_sent_at, :datetime + end +end diff --git a/db/migrate/20200930160008_create_microposts.rb b/db/migrate/20200930160008_create_microposts.rb new file mode 100644 index 0000000..2a370a2 --- /dev/null +++ b/db/migrate/20200930160008_create_microposts.rb @@ -0,0 +1,11 @@ +class CreateMicroposts < ActiveRecord::Migration[6.0] + def change + create_table :microposts do |t| + t.text :content + t.references :user, null: false, foreign_key: true + + t.timestamps + end + add_index :microposts, [:user_id, :created_at] + end +end diff --git a/db/migrate/20201001033623_create_active_storage_tables.active_storage.rb b/db/migrate/20201001033623_create_active_storage_tables.active_storage.rb new file mode 100644 index 0000000..0b2ce25 --- /dev/null +++ b/db/migrate/20201001033623_create_active_storage_tables.active_storage.rb @@ -0,0 +1,27 @@ +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[5.2] + def change + create_table :active_storage_blobs do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.bigint :byte_size, null: false + t.string :checksum, null: false + t.datetime :created_at, null: false + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false + t.references :blob, null: false + + t.datetime :created_at, null: false + + t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end +end diff --git a/db/schema.rb b/db/schema.rb index de286f1..cf90b1d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,8 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_09_29_064821) do + +ActiveRecord::Schema.define(version: 2020_09_30_152853) do create_table "users", force: :cascade do |t| t.string "name" @@ -20,6 +21,11 @@ t.string "password_digest" t.string "remember_digest" t.boolean "admin" + t.string "activation_digest" + t.boolean "activated" + t.datetime "activated_at" + t.string "reset_digest" + t.datetime "reset_sent_at" t.index ["email"], name: "index_users_on_email", unique: true end diff --git a/db/seeds.rb b/db/seeds.rb index 5d4bf72..ebcdb27 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -11,7 +11,10 @@ "example@railstutorial.org", password: "12345", password_confirmation: "12345", - admin: true) + admin: true, + activated: true, + activated_at: DateTime.now) + 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" @@ -19,5 +22,13 @@ User.create!(name: name, email: email, password: password, - password_confirmation: password) + password_confirmation: password, + activated: true, + activated_at: DateTime.now) +end + +users = User.order(:created_at).take(6) +50.times do +content = Faker::Lorem.sentence(word_count: 5) +users.each { |user| user.microposts.create!(content: content) } end diff --git a/test/controllers/account_activations_controller_test.rb b/test/controllers/account_activations_controller_test.rb new file mode 100644 index 0000000..6840b2e --- /dev/null +++ b/test/controllers/account_activations_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class AccountActivationsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/microposts_controller_test.rb b/test/controllers/microposts_controller_test.rb new file mode 100644 index 0000000..1127b0e --- /dev/null +++ b/test/controllers/microposts_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class MicropostsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/password_resets_controller_test.rb b/test/controllers/password_resets_controller_test.rb new file mode 100644 index 0000000..eeb93ef --- /dev/null +++ b/test/controllers/password_resets_controller_test.rb @@ -0,0 +1,14 @@ +require 'test_helper' + +class PasswordResetsControllerTest < ActionDispatch::IntegrationTest + test "should get new" do + get password_resets_new_url + assert_response :success + end + + test "should get edit" do + get password_resets_edit_url + assert_response :success + end + +end diff --git a/test/fixtures/microposts.yml b/test/fixtures/microposts.yml new file mode 100644 index 0000000..33f7a30 --- /dev/null +++ b/test/fixtures/microposts.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + content: MyText + user: one + +two: + content: MyText + user: two diff --git a/test/mailers/previews/user_mailer_preview.rb b/test/mailers/previews/user_mailer_preview.rb new file mode 100644 index 0000000..0d5b43a --- /dev/null +++ b/test/mailers/previews/user_mailer_preview.rb @@ -0,0 +1,14 @@ +# 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 + UserMailer.account_activation + end + + # Preview this email at http://localhost:3000/rails/mailers/user_mailer/password_reset + def password_reset + UserMailer.password_reset + end + +end diff --git a/test/mailers/user_mailer_test.rb b/test/mailers/user_mailer_test.rb new file mode 100644 index 0000000..8a8f654 --- /dev/null +++ b/test/mailers/user_mailer_test.rb @@ -0,0 +1,20 @@ +require 'test_helper' + +class UserMailerTest < ActionMailer::TestCase + test "account_activation" do + mail = UserMailer.account_activation + assert_equal "Account activation", mail.subject + assert_equal ["to@example.org"], mail.to + assert_equal ["from@example.com"], mail.from + assert_match "Hi", mail.body.encoded + end + + test "password_reset" do + mail = UserMailer.password_reset + assert_equal "Password reset", mail.subject + assert_equal ["to@example.org"], mail.to + assert_equal ["from@example.com"], mail.from + assert_match "Hi", mail.body.encoded + end + +end diff --git a/test/models/micropost_test.rb b/test/models/micropost_test.rb new file mode 100644 index 0000000..def8e93 --- /dev/null +++ b/test/models/micropost_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class MicropostTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 6c5048f7fe5195b2a7a316db767abc44ec009ffc Mon Sep 17 00:00:00 2001 From: leviethung123 Date: Mon, 5 Oct 2020 17:01:43 +0700 Subject: [PATCH 2/2] Following users --- app/assets/stylesheets/custom.scss | 48 +++++++++++++++++++ app/controllers/relationships_controller.rb | 21 ++++++++ app/controllers/users_controller.rb | 16 +++++++ app/models/micropost.rb | 2 +- app/models/relationship.rb | 7 +++ app/models/user.rb | 19 +++++++- app/views/microposts/_micropost.html.erb | 2 +- app/views/relationships/create.js.erb | 2 + app/views/relationships/destroy.js.erb | 2 + app/views/shared/_micropost_form.html.erb | 4 +- 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 | 3 ++ app/views/users/index.html.erb | 2 +- app/views/users/show.html.erb | 1 + app/views/users/show_follow.html.erb | 31 ++++++++++++ config/application.rb | 2 + config/locales/en.yml | 22 +++++---- config/locales/vi.yml | 12 +++++ config/routes.rb | 13 ++--- config/settings/development.yml | 3 -- .../20201002035955_create_relationships.rb | 13 +++++ db/schema.rb | 43 ++++++++++++++++- db/seeds.rb | 7 +++ test/fixtures/relationships.yml | 9 ++++ test/models/relationship_test.rb | 7 +++ 28 files changed, 297 insertions(+), 25 deletions(-) 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/20201002035955_create_relationships.rb create mode 100644 test/fixtures/relationships.yml create mode 100644 test/models/relationship_test.rb diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss index ea2c193..6ff0d25 100644 --- a/app/assets/stylesheets/custom.scss +++ b/app/assets/stylesheets/custom.scss @@ -196,3 +196,51 @@ input[type="file"] { display: block; position: absolute; } + +.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; + } +} diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb new file mode 100644 index 0000000..910249a --- /dev/null +++ b/app/controllers/relationships_controller.rb @@ -0,0 +1,21 @@ +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 + 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 + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index ffdef00..67758db 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,6 +3,7 @@ class UsersController < ApplicationController before_action :load_user, except: %i(new index create) before_action :correct_user, only: %i(edit update) before_action :admin_user, only: :destroy + before_action :logged_in_user, only: [:index, :edit, :update, :destroy,:following, :followers] def show @microposts = @user.microposts.paginate(page: params[:page]) @@ -47,6 +48,21 @@ def destroy redirect_to users_url end + def following + @title = "Following" + @user = User.find_by(id: params[:id]) + @users = @user.following.paginate(page: params[:page]) + render "show_follow" + end + + def followers + @title = "Followers" + @user = User.find_by(id: params[:id]) + @users = @user.followers + .paginate(page: params[:page]) + render "show_follow" + end + private def user_params diff --git a/app/models/micropost.rb b/app/models/micropost.rb index ce88392..a678d26 100644 --- a/app/models/micropost.rb +++ b/app/models/micropost.rb @@ -1,7 +1,7 @@ class Micropost < ApplicationRecord belongs_to :user # default_scope -> { order(created_at: :desc) } - scope :recent_posts, -> {order created_at: :desc} + scope :recent_posts, -> {order(created_at: :desc) } has_one_attached :image validates :user_id, presence: true validates :content, presence: true, length: { maximum: 140 } diff --git a/app/models/relationship.rb b/app/models/relationship.rb new file mode 100644 index 0000000..39489b2 --- /dev/null +++ b/app/models/relationship.rb @@ -0,0 +1,7 @@ +class Relationship < ApplicationRecord + belongs_to :follower, class_name: User.name + belongs_to :followed, class_name: User.name + + validates :follower_id, presence: true + validates :followed_id, presence: true +end diff --git a/app/models/user.rb b/app/models/user.rb index 37f9636..ee393d4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -12,7 +12,12 @@ class User < ApplicationRecord has_secure_password validates :password, presence: true, length: {minimum: Settings.user.password}, allow_nil: true + 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 def self.digest string cost = if ActiveModel::SecurePassword.min_cost @@ -70,7 +75,19 @@ def forget end def feed - Micropost.where("user_id = ?", id) + Micropost.where("user_id IN (?) OR user_id = ?", following_ids, 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 diff --git a/app/views/microposts/_micropost.html.erb b/app/views/microposts/_micropost.html.erb index cb6c91b..a02d782 100644 --- a/app/views/microposts/_micropost.html.erb +++ b/app/views/microposts/_micropost.html.erb @@ -6,7 +6,7 @@ <%= image_tag micropost.display_image if micropost.image.attached? %> - Posted <%= time_ago_in_words(micropost.created_at) %> ago. + <%= t("micropost.posted") %> <%= time_ago_in_words(micropost.created_at) %> <%= t("micropost.ago") %>. <% if current_user? micropost.user %> <%= link_to t("micropost.button"), micropost, method: :delete, data: {confirm: t("micropost.confirm")} %> <% end %> diff --git a/app/views/relationships/create.js.erb b/app/views/relationships/create.js.erb new file mode 100644 index 0000000..9a2277b --- /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..27bf26b --- /dev/null +++ b/app/views/relationships/destroy.js.erb @@ -0,0 +1,2 @@ +$("#follow_form").html("<%= j render "users/follow" %>"); +$("#followers").html("<%= @user.followers.count %>"); diff --git a/app/views/shared/_micropost_form.html.erb b/app/views/shared/_micropost_form.html.erb index 7ebc65b..3f829fe 100644 --- a/app/views/shared/_micropost_form.html.erb +++ b/app/views/shared/_micropost_form.html.erb @@ -1,9 +1,9 @@ <%= form_for(@micropost, html: { multipart: true }) do |f| %> <%= render "shared/error_messages", object: f.object %>
    - <%= f.text_area :content, placeholder: "Compose new micropost..." %> + <%= f.text_area :content, placeholder: t("micropost.compose_new") %>
    - <%= f.submit "Post", class: "btn btn-primary" %> + <%= f.submit t("micropost.post"), class: "btn btn-primary" %> <%= f.file_field :image %> diff --git a/app/views/shared/_stats.html.erb b/app/views/shared/_stats.html.erb new file mode 100644 index 0000000..c597a87 --- /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 488cae7..060a4db 100644 --- a/app/views/static_pages/home.html.erb +++ b/app/views/static_pages/home.html.erb @@ -4,6 +4,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..6ad39b9 --- /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 t("follow.button_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..d4221b1 --- /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..6e4dbc6 --- /dev/null +++ b/app/views/users/_unfollow.html.erb @@ -0,0 +1,3 @@ +<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id), html: { method: :delete , remote: true}) do |f| %> + <%= f.submit t("follow.unfollow"), class: "btn" %> +<% end %> diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 911cfc8..31fd762 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -1,4 +1,4 @@ -<% provide(:title, 'All users') %> +<% provide(:title, t("users.new.all_users")) %>

    <%= t("session.title") %>

    <%= will_paginate %>
      diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index c018c54..9a30066 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -9,6 +9,7 @@
      + <%= render "follow_form" if logged_in? %> <% if @user.microposts.any? %>

      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..3c4374d --- /dev/null +++ b/app/views/users/show_follow.html.erb @@ -0,0 +1,31 @@ +<% provide(:title, @title) %> +
        + +
        +

        <%= @title %>

        + <% if @users.any? %> + + <%= will_paginate %> + <% end %> +
        +
        diff --git a/config/application.rb b/config/application.rb index 72d4a91..2591f9a 100644 --- a/config/application.rb +++ b/config/application.rb @@ -22,6 +22,8 @@ class Application < Rails::Application config.i18n.default_locale = :en config.time_zone = Settings.time_zone + + config.action_view.embed_authenticity_token_in_remote_forms = true end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 21c7c15..2b7626e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -38,6 +38,7 @@ en: lable_password: password confirmation: confirmation create_my_account: create my account + all_users: "All users" success: "Welcome to the sample" errors: "Not Found." @@ -58,10 +59,8 @@ en: messages: "User deleted" messages1: "You sure?" view: "delete" -<<<<<<< HEAD messages3: "delete error" -======= ->>>>>>> chapter 12: Password reset + login_in: messages: "Please log in." @@ -71,8 +70,6 @@ en: button: "Save changes" header: "Update your profile" link: "change" -<<<<<<< HEAD -======= email: messages: "Please check your email to activate your account." @@ -80,9 +77,6 @@ en: sendemail: " Welcome to the Sample App! Click on the link below to activate your" account: "account:" activate: "Activate" -<<<<<<< HEAD ->>>>>>> chapter 12: Password reset -======= micropost: messages1: "Micropost created!" @@ -91,4 +85,14 @@ en: confirm: "You sure?" messages3: "Maximum file size is 5MB. Please choose a smaller file." title: "view my profile" ->>>>>>> chapter 13 User microposts + compose_new: "Compose new micropost..." + post: "Post" + posted: "Posted" + ago: "ago" + + follow: + button_follow: "Follow" + unfollow: "Unfollow" + microposts: "Microposts" + following: "following" + followers: "followers" diff --git a/config/locales/vi.yml b/config/locales/vi.yml index bfecf90..5bc449b 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -38,6 +38,7 @@ vi: lable_password: Mật khẩu confirmation: xác thục mật khẩu create_my_account: Tạo tài khoản + all_users: "Tất cả các người dùng" success: "chào mừng đến với trang web" errors: "Thông Báo Lỗi" @@ -83,3 +84,14 @@ vi: confirm: "Bạn chắc chắn?" messages3: "Kích thước tệp tối đa là 5MB. Vui lòng chọn một tệp nhỏ hơn." title: "xem hồ sơ của tôi" + compose_new: "Soạn micropost mới ..." + post: "Đăng" + posted: "Bài đăng" + ago: "trước" + + follow: + button_follow: "Theo dõi" + unfollow: "Bỏ theo dõi" + microposts: "Bài Đăng" + following: "theo dõi" + followers: "Người theo dõi" diff --git a/config/routes.rb b/config/routes.rb index b7b71b1..540ef2f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,14 +11,15 @@ post "/login", to: "sessions#create" delete "/logout", to: "sessions#destroy" post "/edit", to: "users#edit" + + resources :users do + member do + get :following, :followers + end + end resources :users -<<<<<<< HEAD -======= resources :account_activations, only: :edit resources :password_resets, only: [:new, :create, :edit, :update] -<<<<<<< HEAD ->>>>>>> chapter 12: Password reset -======= resources :microposts, only: [:create, :destroy] ->>>>>>> chapter 13 User microposts + resources :relationships, only: [:create, :destroy] end diff --git a/config/settings/development.yml b/config/settings/development.yml index ba60224..416fe7c 100644 --- a/config/settings/development.yml +++ b/config/settings/development.yml @@ -3,8 +3,5 @@ user: email: 250 size: 80 password: 3 -<<<<<<< HEAD page: 10 -======= ->>>>>>> chapter 12: Password reset config.time_zone: "Hanoi" diff --git a/db/migrate/20201002035955_create_relationships.rb b/db/migrate/20201002035955_create_relationships.rb new file mode 100644 index 0000000..c84b4d5 --- /dev/null +++ b/db/migrate/20201002035955_create_relationships.rb @@ -0,0 +1,13 @@ +class CreateRelationships < ActiveRecord::Migration[6.0] + 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 cf90b1d..f5a190e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,8 +10,47 @@ # # It's strongly recommended that you check this file into your version control system. +ActiveRecord::Schema.define(version: 2020_10_02_035955) do -ActiveRecord::Schema.define(version: 2020_09_30_152853) do + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.integer "record_id", null: false + t.integer "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.bigint "byte_size", null: false + t.string "checksum", null: false + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "microposts", force: :cascade do |t| + t.text "content" + t.integer "user_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["user_id", "created_at"], name: "index_microposts_on_user_id_and_created_at" + 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", precision: 6, null: false + t.datetime "updated_at", precision: 6, 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" @@ -29,4 +68,6 @@ t.index ["email"], name: "index_users_on_email", unique: true end + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "microposts", "users" end diff --git a/db/seeds.rb b/db/seeds.rb index ebcdb27..1642d78 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -32,3 +32,10 @@ content = Faker::Lorem.sentence(word_count: 5) users.each { |user| user.microposts.create!(content: content) } end + +users = User.all +user = users.first +following = users[2..20] +followers = users[3..15] +following.each{|followed| user.follow(followed)} +followers.each{|follower| follower.follow(user)} diff --git a/test/fixtures/relationships.yml b/test/fixtures/relationships.yml new file mode 100644 index 0000000..d70987c --- /dev/null +++ b/test/fixtures/relationships.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + follower_id: 1 + followed_id: 1 + +two: + follower_id: 1 + followed_id: 1 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