สร้างแอพ Rails 6 ทำความรู้จัก webpacker และทดลองนำ javascript ไลบรารี่ Flatpickr มาใช้

Rails เวอร์ชั่นที่เราจะลองใช้ในโพสนี้คือ 6.0.0.rc1 ซึ่งเป็นเวอร์ชั่นล่าสุด ณ ตอนที่เขียนโพส เนื่องจาก Rails 6.0 ต้องการ Ruby เวอร์ชั่น 2.5.0 ขึ้นไป ถ้าตอนนี้ยังไม่มีให้ไปดาวโหลดและลงให้เรียบร้อยก่อนนะครับ สามารถดูขั้นตอนการติดตั้ง Ruby 2.5.5 ได้จากโพส ติดตั้ง สร้างโปรเจคและรันแอพ Ruby On Rails บน Windows 10

เมื่อพร้อมแล้ว เราจะเริ่มกันเลย รันคำสั่งด้านล่างเพื่อติดตั้ง rails 6.0

$ gem install rails --pre

หลังจากติดตั้งแล้วเมื่อรันคำสั่ง $ rails -v เราจะเห็นว่าเวอร์ชั่นกลายเป็น 6.0.0 แล้ว

ผมจะลองสร้างแอพตัวอย่างขึ้นมา สมมติว่าแอพนี้ชื่อ memberwoot ทำหน้าที่ใช้เก็บข้อมูล ชื่อ วันเดือนปีเกิด เบอร์มือถือ ของพนักงานลงในฐานข้อมูล

เนื่องจากจุดประสงค์หลักของโพสนี้ เพื่อทดลองใช้ Rails 6 สร้างแอพง่ายๆ ดูการเซตอัพ PostgreSQL ภายใต้ Rails 6 และทำความเข้าใจกับนำ javascript package มาใช้ผ่านทาง webpacker เท่านั้น ในส่วนของโค้ดที่เกี่ยวข้องกับ Model, View และ Controller ของ Rails โค้ดบางส่วนผมจะพูดถึงและแสดงเอาไว้ในโพสนี้ ส่วนโค้ดทั้งหมดของแอพ memberwoot สามารถเข้าไปดูได้ที่ repository ของ memberwoot ที่ github ครับ

ทั้งนี้ผมขออนุมานว่าคุณผู้อ่านพอที่จะทราบการทำงานพื้นฐานของ Model, View, Controller และ โค้ดในส่วนของ CRUD operation มาบ้างแล้ว ดังนั้นผมขออนุญาตไม่อธิบายถึงรายละเอียดของโค้ดในส่วนนั้น แต่จะโฟกัสตรงส่วนของการนำ javascript package มาใช้และ webpacker ซึ่งเป็นของใหม่ใน Rails 6 แทนนะครับ

เรื่องการทำงานของโค้ดใน Rails นั้น ผมมีโครงการที่จะทำ guide แบบ step by step เป็นภาษาไทยอยู่แล้ว หวังว่าจะทยอยเอามาลงได้เร็วๆ นี้

โอเค เริ่มสร้างแอพเลยแล้วกัน cd เข้าไปที่ไดเรกทอรี่ที่ต้องการเก็บโปรเจคงานแล้วรันคำสั่งด้านล่าง

$ rails new memberwoot

สำหรับ Rails 6 นั้น หลังจากรันคำสั่งข้างต้นแล้ว จะทำการสร้างโฟลเดอร์และไฟล์ของโปรเจคและทำการลง gem ที่เกี่ยวข้องทั้งหมดเหมือนกับ Rails เวอร์ชั่นก่อนๆ แต่ที่ต่างไปคือ Rails 6 จะทำการลง webpacker ซึ่งเป็น gem ที่ใช้เชือมต่อ webpack กับ Rails และ yarn ซึ่งเป็น javascript package manager ให้โดยอัตโนมัติ พอลง webpacker เสร็จ มันจะทำการรัน yarn เพื่อลง javascript package ตามรายการที่ปรากฏอยู่ในไฟล์ package.json ต่อทันที

ซึ่ง webpacker จะทำการคอนฟิกและเรียกใช้ package อื่นๆ เพื่อให้เราสามารถใช้งาน ES6 ได้โดยอัตโนมัติ

ให้เรา cd เข้าไปที่โฟลเดอร์ helloapp แล้วลองรัน rails s เปิดเบราเซอร์แล้วเข้าไปที่ localhost:3000 ถ้าไม่มีอะไรผิดพลาด เราจะเห็นหน้า welcome ของ Rails ตามรูปด้านล่าง หน้า welcome ของ Rails 6

เปลี่ยน Database จาก SQLite3 เป็น PostgreSQL

Rails 6 ยังคงใช้ SQLite3 เป็น database ตั้งต้น แต่สำหรับโปรเจคนี้เราจะเปลี่ยนไปเป็น PostgreSQL แทน สมมติว่าเราลง PostgreSQL ไว้เรียบร้อยแล้ว

ก่อนอื่นเราจะทำการสร้าง database ที่จะใช้ในสภาวะแวดล้อมที่เป็น development บนเครื่อง local ของเราก่อน database ชื่อ memberwoot_development โดยพิมพ์คำสั่งด้านล่าง

$ createdb memberwoot_development

เปิดไฟล์ Gemfile ขึ้นมาแก้ไขโดยเอาบรรทัดที่มี gem sqlite3 ออกไปแล้วใส่ gem 'pg' เข้ามา

# gem 'sqlite3', '~>1.4'
gem 'pg', '~> 1.1', '>= 1.1.4'

จากนั้นรันคำสั่ง bundle บน command line ที่ root ของโปรเจค

เสร็จแล้วเปิดไฟล์ config/database.yml ขึ้นมาแล้วทำการแก้ไข ให้มีหน้าตาประมาณนี้

default: &default
  adapter: postgresql
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000
  encoding: utf8

development:
  <<: *default
  database: memberwoot_development

production:
  <<: *default
  database: memberwoot_production

โอเค ตอนนี้เราเซตอัพฐานข้อมูลเรียบร้อย พร้อมไปต่อได้

สร้าง Model, View, Controller สำหรับ Member

เราจะเริ่มจากการสร้างโมเดล Member ที่เก็บข้อมูล ชื่อ(name) วันเดือนปีเกิด (birth_date) และเบอร์มือถือ (mobile) ของผู้ใช้

$ rails g model Member name:string birth_date:date mobile:string

จากนั้นรัน bundle exec rails db:migrate

สร้าง controller MembersController

$ bundle exec rails g controller Members

จากนั้นทำการแก้ไขไฟล์ app/controllers/members_controller.rb โดยกำหนดแอคชั่น (เมธอด) เพื่อทำกระบวนการ create, read, update และ destroy สำหรับ Member สร้าง ไฟล์ Views ที่เกี่ยวข้องกับแอคชั่นของ Member ใน app/views/Member และเพิ่ม routing ในไฟล์ config/routes.rb โดยผมได้กำหนดให้หน้า index ของแอพวิ่งไปที่แอคชั่น index ของ MembersController นั่นคือจะนำไฟล์ app/views/member/index.html.erb มาแสดงผลเป็นหน้าแรกนั่นเอง

คุณสามารถเข้าไปดูโค้ดในส่วนนี้ได้ใน repository ของ memberwoot

จากนั้นทดลองรัน $ bundle exec rails server เปิดเบราเซอร์แล้วเข้าไปที่ localhost:3000 เราจะเห็นแอพหน้าตาประมาณนี้ ซึ่งเราจะสามารถเพิ่ม แก้ไข อัพเดตและลบ member ได้ หน้า index ของ Member สามารถ create, read, update และ destroy member ได้

Note สำหรับ Rails 6.0.0.rc1 ที่ผมใช้ในโพสนี้ พบว่ามันจะแสดง error ที่หน้า debug console ของเบราเซอร์ ซึ่งเป็นเป็นหาที่คนอื่นๆ ก็เจอเหมือนกันอ้างอิงจาก Rails 6.0.0.rc1: Error on fresh app ปัญหานี้สามารถแก้ไขได้โดย ให้แก้ไขไฟล์ babel.config.js โดยลบบรรทัดที่มี corejs: 3 ออกไป (มีอยู่ทั้งหมด 2 ที่) จากนั้นรันคำสั่ง rails tmp:clear แล้วรัน rails server อีกรอบก็จะแก้ปัญหาได้

Webpacker และ package.json

webpacker เป็น javascript bundler ที่ถูกกำหนดให้ใช้เป็นค่าเริ่มต้นสำหรับ Rails 6 แทนที่ของ asset pipeline ที่ใช้ใน Rails เวอร์ชั่นก่อนๆ (asset pipeline ยังคงมีอยู่ใน Rails 6 แต่จะเน้นไปที่การใช้กับ assets ที่เป็น รูป, ไฟล์ หรือ css แทน) โดย javascript bundler จะทำหน้าที่รวบรวม javascript ไลบรารี่ทั้งหมดที่เราเรียกใช้ภายในโปรเจค แล้วทำการคอมไฟล์ออกมาเป็นไฟล์ .js ไฟล์เดียว webpacker ถูกกำหนดค่ามาตั้งแต่แรกให้รองรับการเขียนโค้ดในรูปของ ES6 (ES2015) ได้ดังนั้นเราจึงไม่ต้องเสียเวลามาคอนฟิก package เพื่อให้รัน ES6 อีก

จริงๆ แล้ว webpacker ไม่ใช้สิ่งใหม่สำหรับ Rails 6 เพราะมีให้ใช้กันตั้งแต่ Rails 5.1 แล้ว และตัวของ webpacker เองก็มีการพัฒนาอัพเดตมาเรื่อยตามแพกเกจ webpack ของ javscript ที่มาจนถึง เวอร์ชั่น 4 แล้วสำหรับ Rails 6

การทำงานของ webpacker และตัว webpack เองนั้นค่อนข้างซับซ้อนและใช้งานไม่ง่ายเท่าไหร่ในความเห็นของผม แถมขยันอัพเดตบ่อยมาก พออัพเดตทีโค้ดของเก่าก็พังทีเจอบ่อยๆ แอบบ่นไปงั้น ส่วนดีก็มีเยอะอยู่โดยคุณสามารถดูรายละเอียดเกี่ยวกับ webpacker ได้จาก official repository

Note javascript ไลบรารี่ และ javascript package ที่ผมจะพูดถึงต่อจากนี้ถือว่ามีความหมายเดียว ดังนั้นผมจะขอเรียก และอาจใช้ทั้งสองชื่อสลับกันตามบริบทต่างๆ ยกตัวอย่างเช่น turbolink เป็น javascript ไลบรารี่ และก็เป็น javascript package ที่ถูกติดตั้งมากับ Rails 6 ตั้งแต่ต้น

รัน javascript ภายใต้ webpacker ด้วย javascript_pack_tag

อย่างที่บอกไว้ตั้งแต่ตอนแรกว่า Rails 6 จะใช้ webpacker ในการคอมไพล์ javascript แทนที่ asset pipeline

โดย webpacker จะมีสิ่งที่เรียกว่า entry file ทำหน้าที่เป็นไฟล์ต้นทางหรือไฟล์หลัก โค้ด javascript ของเราจะเริ่มรันจากไฟล์นี้ก่อน ดังนั้นไฟล์นี้จะเป็นจุดเริ่มต้นในการ import หรือ require โค้ด javascript จากไฟล์หรือโมดูลอื่นๆ ขึ้นมารันต่อๆ กันไป

ไฟล์ javascript ทั้งหมดที่ถูกเรียกใช้ด้วยการimport หรือ require จากไฟล์ entry รวมทั้ง dependecies ทั้งหมด จะถูก webpacker รวบรวมและคอมไพล์ออกมาเป็นไฟล์เดียว เรียกว่าไฟล์ bundled ซึ่งไฟล์นี้จะถูกเก็บไว้ที่ไดเรกทอรี่ public/packs/js โดยไฟล์ bundled ที่ถูกคอมไพล์แล้วจะมีชื่อเดียวกับไฟล์ entry แต่มี hash code ตามหลังชื่อด้วย

ในส่วนของคนเขียนโค้ด การทำงานกับ webpacker ก็ง่ายๆ เราจะใช้เมธอด javascript_pack_tag ในการระบุไฟล์ entry โดยเรียกใช้ javascript_pack_tag จาก <header> tag ในไฟล์ app/views/layouts/application.html.erb (แทนที่ javascript_include_tag ที่เคยใช้ใน Rails เวอร์ชั่นก่อนๆ) พอ Rails คอมไพล์เจอ javascript_pack_tag มันจะทำการ bundle javascript ไฟล์ที่เกี่ยวข้องทั้งหมดจากไฟล์ entry แล้วแปลงเป็น <script> tag ที่เรียกใช้ไฟล์ bundled ใช้เราโดยอัตโนมัติ

<!DOCTYPE html>
<html>
  <head>
    <title>Memberwoot</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

จากโค้ดในไฟล์ app/views/layouts/application.html.erb ข้างต้น โค้ด <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> เป็นการบอกว่าให้ทำการ bundle ไฟล์ entry ที่ชื่อ applicatino.js ซึ่งไฟล์นี้จะถูกเก็บไว้ที่ไดเรกทอรี่ตั้งต้นของ webpacker คือ app/javascript/packs

เพื่อให้เห็นภาพการทำงานของ webpacker ให้ชัดขึ้น ลองแก้ไขไฟล์ app/javascript/packs/application.js โดยใส่โค้ดด้านล่างลงไป ที่บรรทัดสุดท้ายของไฟล์

console.log('webpacker is running')

พอรีเฟรซเบราเซอร์และเปิด debug console ดู จะเห็นข้อความจากโค้ดใน console.log ของเรา แสดงว่า javascript_pack_tag ทำการคอมไฟล์และเพิ่ม <script> tag ที่ชี้ไปยังไฟล์ bundled ให้เรา ทำให้โค้ดที่อยู่ใน application.js หรือไฟล์ entry ถูกเรียกใช้ ข้อความจาก console.log แสดงให้เห็นว่า javascript_pack_tag ทำงาน

รายการของไลบรารี่ javascript ใน package.json

ตอนเราสร้างแอพ Rails 6 ใหม่ขึ้นมาตอนแรกจะมี javascript package ที่ถูกกำหนดให้ใช้มาตั้งแต่แรกอยู่แล้ว ซึ่งปรากฏอยู่ในไฟล์ package.json

{
  "name": "memberwoot",
  "private": true,
  "dependencies": {
    "@rails/actioncable": "^6.0.0-alpha",
    "@rails/activestorage": "^6.0.0-alpha",
    "@rails/ujs": "^6.0.0-alpha",
    "@rails/webpacker": "^4.0.2",
    "turbolinks": "^5.2.0"
  },
  "version": "0.1.0",
  "devDependencies": {
    "webpack-dev-server": "^3.4.1"
  }
}

ไฟล์ package.json เป็นไฟล์สำหรับระบุรายการของ javascript ไลบรารี่ที่เราจะเอามาใช้ในโปรเจค ซึ่ง เราสามารถสั่งให้ javascript package manager ดาวโหลดและติดตั้ง javascript ไลบรารี่เหล่านั้น ลงไปในโปรเจคของเราโดยอ้างอิงจากรายการที่ปรากฏอยู่ใน package.json ได้ ซึ่ง javascript ไลบรารี่ที่เราเห็นในไฟล์ package.json ข้างต้นนั้น ถูกติดตั้งลงในโปรเจคของเราเรียบร้อยแล้วตอนที่เราสร้างแอพ memberwoot (สังเกตจากการที่โฟลเดอร์ node_modules ถูกสร้างขึ้นแล้วเช่นกัน)

สำหรับการทำโปรเจค Rails นั้นเราจะใช้ javascript package manager ที่ชื่อว่า yarn

Note โดยทั่วไปนั้นหลังจากสั่งให้ javascript package manager ทำงานแล้ว javascript package ที่ถูกระบุอยู่ใน package.json รวมทั้ง package อื่นๆ ที่ javascript package แต่ละตัวเรียกใช้ จะถูกติดตั้งเอาไว้ในโฟลเดอร์ชื่อ node_modules

ติดตั้ง javascript ไลบรารี่ใหม่ Flatpickr บน Rails 6

Flatpickr เป็นไลบรารี่ javascript ที่ใช้สำหรับรับ input ที่เป็น date (date picker) โดยแสดงผลเป็น GUI ในรูปของปฏิธิน เราสามารถจิ้มวันที่ที่เราต้องการลงไปตรงปฏิธินที่โชว์ขึ้นมาได้เลย โดย output ที่ได้จะเป็นสตริงตาม format ที่เราต้องการ

เราจะเอา flatpickr เข้ามาใช้ในโปรเจค ไปที่ command line windows แล้วรันคำสั่ง yarn add เพื่อสั่งให้ yarn ดาวโหลดและติดตั้ง flatpickr ดังนี้

$ yarn add flatpickr

เมื่อดาวโหลดและติดตั้งเสร็จแล้ว ให้เราเปิดไฟล์ package.json ขึ้นมาจะพบว่า มีรายการของ flatpickr เพิ่มเข้ามา

{
  "dependencies": {
    "@rails/actioncable": "^6.0.0-alpha",
    "@rails/activestorage": "^6.0.0-alpha",
    "@rails/ujs": "^6.0.0-alpha",
    "@rails/webpacker": "^4.0.2",
    "flatpickr": "^4.5.7",
    "turbolinks": "^5.2.0"
  },
}

Note yarn จะทำหน้าที่คล้ายๆ กับ bundler gem ของ ruby เวลาเรารันคำสั่ง bundle install เพื่อดาวโหลด ruby gem ทั้งหมดที่ระบุอยู่ใน Gemfile นั้นคือเราสามารถสั่งให้ yarn ติดตั้ง javascript package ทั้งหมดตามที่ระบุอยู่ใน package.json ได้โดยการรันคำสั่ง yarn install ได้เช่นเดียวกัน (หรือสั่ง yarn เฉยๆ ก็ได้)

เราจะเปลี่ยน input สำหรับเลือกวันเดือนปีเกิดของ member ให้เป็น UI ของ flatpickr โดยผมจะอ้างอิงวิธีใช้ Flatpickr จาก document ของ Flatpickr เว็บไซต์ ซึ่งคุณสามารถเข้าไปศีกษาต่อยอดได้เลย

ก่อนอื่นทำการ import Flatpickr ในส่วนของไลบรารี่ javascript และ stylesheet ที่ใช้สำหรับ flatpickr โดยเพิ่มโค้ดต่อไปนี้ลงในด้านบนของไฟล์ app/javascript/packs/application.js จะได้โค้ดหน้าตาประมาณนี้

require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
import flatpickr from "flatpickr"
import 'flatpickr/dist/flatpickr.min.css'

ทำการเรียกใช้ flatpickr โดยระบุ input field ที่ต้องการให้แสดงผลด้วย Flatpickr ซึ่งก็คือ field ชื่อ member_birth_date ใส่โค้ดนี้ลงไปที่บรรทัดสุดท้ายของไฟล์ app/javascript/packs/application.js

document.addEventListener('turbolinks:load', function(){
  const fp = flatpickr('#member_birth_date', {})
})

จากนั้นแก้ไข field ของไฟล์ app/views/members/_form.html.erb จาก date_field เป็น text_field

  <div class="field-group">
    <%= form.label :birth_date %>
    <%= form.text_field :birth_date %>
  </div>

ลองเข้าไปที่หน้าของการ Add หรือ Edit Member พอเราคลิ๊กที่ field Birth date จะเห็นว่ามีเมนูปฏิทินของ Flatpickr โผล่ขึ้นมาให้เลือกวันที่ได้เลย

Flatpickr ทำงานได้แล้ว

Note ถึงแม้ว่าเราเปลี่ยนเมธอด field ของ birh_date จาก date_field ไปเป็น text_field โดยที่ไม่ได้แก้ไขอะไรอย่างอื่น Rails ยังคงสามารถเก็บข้อมูลใน field birth_date ลงฐานข้อมูลได้อย่างถูกต้อง นั่นเป็นเพราะว่า Rails (ActiveRecord) จะทำการแปลง String ที่อยู่ในรูปแบบของ Date ให้ไปเป็น Date โดยอัตโนมัตินั่นเอง

ตอนนี้เราสามารถใช้ Flatpickr ได้แล้ว อย่างไรก็ตามผมขอถือโอกาส ยกตัวอย่างการจัดโค้ด javascript สำหรับไลบรารี่หรือโค้ดที่เราเพิ่มเข้าไปเสียใหม่เพื่อเป็นแนวทางในการจัดการการกับโค้ดภายใต้โฟลเดอร์ app/javascript ของคุณในอนาคต

ให้เราสร้างโฟลเดอร์และไฟล์ app/javascript/components/member/member.js เพื่อแยกโค้ดที่เกี่ยวกับ Member ซึ่งในที่นี้คือการใช้ Flatpickr ใน Member Add/Edit form ย้ายโค้ดในส่วนของการ import และเรียกใช้ Flatpickr จาก app/javascript/packs/application.js ไปยัง app/javascript/components/member/member.js แทน ดังนี้

import flatpickr from "flatpickr"
import 'flatpickr/dist/flatpickr.min.css'

document.addEventListener('turbolinks:load', function(){
  const fp = flatpickr('#member_birth_date', {})
})

แล้วทำการ import member.js จากไฟล์ app/javascript/packs/application.js ดังนี้


require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

import '../components/member/member.js'

เมื่อรีเฟรซเบราเซอร์แล้วเข้าไปที่หน้า Add หรือ Edit ของ Member จะเห็นว่า Flatpickr ก็ยังคงทำงานได้เป็นปกติ

คุณสามารถเข้าไปดูรายละเอียดของโค้ด ณ จุดนี้ได้ที่ repository ของ memberwoot บน Github ครับ

แชร์โพสได้จากลิ้งด้านล่าง ขอบคุณครับ