12/08/2018, 13:04

Hướng dẫn tạo chức năng comment bằng Gem private_pub

Trong bài viết này tôi sẽ hướng dẫn tạo 1 web đơn giản với chức năng tạo status và comment. Sử dụng gem private_pub để có thể hiển thị comment khi có comment mới của mình hoặc của người khác comment từ nơi khác mà không cần load lại trang. Trước khi làm những hướng dẫn dưới đây, bận cần làm trước ...

Trong bài viết này tôi sẽ hướng dẫn tạo 1 web đơn giản với chức năng tạo status và comment. Sử dụng gem private_pub để có thể hiển thị comment khi có comment mới của mình hoặc của người khác comment từ nơi khác mà không cần load lại trang.

Trước khi làm những hướng dẫn dưới đây, bận cần làm trước việc tạo user và đăng ký, đăng nhập bằng devise. Có thể xem hướng dẫn tại: https://github.com/plataformatec/devise

Thêm vào Gemfile

gem “private_pub”
gem “thin”

Chạy

$ bundle install

Sau đó chạy lệnh

$ rails g private_pub:install

để tạo file init cho private_pub

Tạo model Status

$ ails generate model Status content:text user:references

Tạo model StatusComment

$ rails generate model StatusComment content:text user:references status:references

Thêm và sửa các file model:

File user.rb

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  has_many :statuses, dependent: :destroy
  has_many :status_comments, dependent: :destroy
end

File status.rb

class Status < ActiveRecord::Base
  belongs_to :user

  has_many :status_comments, dependent: :destroy
  accepts_nested_attributes_for :status_comments, allow_destroy: :true

  default_scope -> {order(created_at: :desc)}

  validates :user_id, presence: true
  validates :content, presence: true, length: {maximum: 2000}

end

File status_comment.rb

class StatusComment < ActiveRecord::Base
  belongs_to :user
  belongs_to :status

  validates :user_id, presence: true
  validates :content, presence: true, length: {maximum: 140}

end

File config/routes.rb

Rails.application.routes.draw do
  root "statuses#index"

  devise_for :users
  devise_scope :user do
    get "sign_out", to: "devise/sessions#destroy"
    get "sign_in", to: "devise/sessions#new"
    get "sign_up", to: "devise/registrations#new"
  end

  resources :statuses
  resources :status_comments
end

File statuses_controller.rb

class StatusesController < ApplicationController
  respond_to :html, :json

  def index
    @statuses = Status.all
    @status = Status.new
  end

  def create
    @status = current_user.statuses.build status_params
    if @status.save
      flash[:success] = "Status created!"
      redirect_to root_url
    else
      @feed_items = []
      render "static_pages/home"
    end
  end

  def destroy
    @status.destroy
    flash[:success] = "Status deleted"
    redirect_to request.referrer || root_url
  end

  private
  def status_params
    params.require(:status).permit :content
  end
end

status_comments_controller.rb

class StatusCommentsController < ApplicationController
  respond_to :html, :json

  def create
    @status_comment = StatusComment.create! status_comment_params
    respond_to do |format|
      format.html { }
      format.js
    end
  end

  def destroy
    @status_comment = StatusComment.find params[:id]
    @status_comment.destroy
    redirect_to :back
  end

  private
  def status_comment_params
    params.require(:status_comment).permit :status_id, :user_id, :content
  end
end

Trang index của status index.html.erb

User: <%= current_user.name %>
<%= link_to "Sign out", sign_out_path %></br>
----------------------------------------------------------
</br>
<%= form_for @status, html: {multipart: true} do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new status...", class: "form-control" %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
<% end %>

<%= render @statuses %>

File views/statuses/_status.html.erb

<li id="<%= status.id %>">
  <span class="user">
    <%= status.user.name %>
  </span>
  <br>
  <span class="timestamp text-success">
    <%= "Posted #{time_ago_in_words(status.created_at)} ago." %>
    <% if current_user == status.user %>
      <%= link_to "Delete", status, method: :delete, data: {confirm: "You sure?"} %>
    <% end %>
  </span></br>
  <span class="content">
    <%= status.content %></br>
  </span></br>
  Comments:</br>
  <% @status_comments = status.status_comments %>
  <span id="status-comment<%= status.id %>">
    <%= render @status_comments %>
  </span>
  <span id="form-status">
    <%= form_for StatusComment.new , remote: true do |f| %>
      <%= render 'shared/error_messages', object: f.object %>
        <div class="field">
          <%= f.hidden_field :status_id, value: status.id %>
          <%= f.hidden_field :user_id, value: current_user.id %>
          <%= f.text_area :content, placeholder: "Compose new comment...", class: "form-control" %>
        </div>
      <%= f.submit "Comment", class: "btn btn-primary" %>
    <% end %>
  </span>
</li>
</br>

File views/status_comments/_status_comment.html.erb

<li>
  <% if status_comment.id %>
    <%= status_comment.user.name %>:
    <%= status_comment.content %></br>
    <%= time_ago_in_words(status_comment.created_at) %>
    <%= link_to "Delete", status_comment_path(status_comment), method: :delete, data: {confirm: "You sure?"} if current_user == status_comment.user%>
    </br></br>
  <% end %>
</li>

Thêm vào file application.js

//= require private_pub

Thêm file private_pub.js và add vào layout

<%= javascript_include_tag "private_pub" %>

Thêm vào file config/initializers/assets.rb

Rails.application.config.assets.precompile += %w( private_pub.js )

Chạy Faye.

$ rackup private_pub.ru -s thin -E production

Dùng subscribe_to trong trang mà minh muốn bắt thay đổi để subscribe một kênh.

<%= subscribe_to "/comments/new" %>

Dùng publish_to để gửi JS đến kênh đó. Ta sẽ viết trong file create.js.erb và để trong thư mục views/status_comments/

<% publish_to "/comments/new" do %>
  $("#status-comment<%= @status_comment.status_id %>").append("<%= j render(@status_comment) %>");
<% end %>

Thêm

$("#form-status form textarea").val("");

để xóa text khi đã comment xong.

Như vậy ta đã hoàn thành. Chạy rails s và thử kết quả. Screenshot from 2015-12-25 15:47:48.png

Ứng dụng chạy thử trên heroku: https://protected-plateau-5161.herokuapp.com/

  • https://github.com/ryanb/private_pub
  • https://github.com/plataformatec/devise
0