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ả.
Ứ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