Advertisement
  1. Code
  2. CMS

Xây dựng một CMS: rubyPress

Scroll to top
Read Time: 18 min

Vietnamese (Tiếng Việt) translation by Dai Phong (you can also view the original English article)

Sau khi tạo ra cấu trúc cơ bản của Hệ thống Quản lý Nội dung (CMS) và máy chủ thật sự bằng GoNode.js, bạn đã sẵn sàng để thử một ngôn ngữ khác.

Lần này, tôi đang sử dụng ngôn ngữ Ruby để tạo ra máy chủ. Tôi thấy rằng, bằng cách tạo ra cùng một chương trình trong nhiều ngôn ngữ khác nhau, bạn bắt đầu có được những cái nhìn sâu sắc và mới mẻ về những cách tốt hơn để cài đặt chương trình. Bạn cũng thấy được nhiều cách để thêm chức năng vào chương trình. Hãy bắt đầu nào.

Thiết lập và Nạp Thư viện

Để lập trình trong Ruby, bạn sẽ cần phải có phiên bản mới nhất được cài đặt trên hệ thống của bạn. Ngày nay, nhiều hệ điều hành được cài đặt sẵn Ruby (Linux và OS X), nhưng chúng thường có phiên bản cũ hơn. Hướng dẫn này giả sử rằng bạn đã cài đặt Ruby phiên bản 2.4.

Cách dễ nhất để nâng cấp phiên bản ruby ​​mới nhất là sử dụng RVM. Để cài đặt RVM trên Linux hoặc Mac OS X, hãy gõ lệnh sau trong terminal:

1
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
2
curl -sSL https://get.rvm.io | bash -s stable

Việc này sẽ tạo một kết nối an toàn để tải về và cài đặt RVM. Điều này sẽ cài đặt phiên bản ổn định mới nhất của Ruby. Bạn sẽ phải load lại shell của bạn để hoàn tất quá trình cài đặt.

Đối với Windows, bạn có thể tải về Windows Ruby Installer. Hiện tại, gói này được cập nhật lên phiên bản Ruby 2.2.2, một phiên bản phù hợp để chạy các thư viện và các script trong hướng dẫn này.

Một khi ngôn ngữ Ruby đã được cài đặt thành công, bây giờ bạn có thể cài đặt các thư viện. Ruby, cũng giống như Go và Node, có một trình quản lý gói để cài đặt các thư viện của bên thứ ba. Trong terminal, gõ lệnh sau:

1
gem install sinatra
2
gem install ruby-handlebars
3
gem install kramdown
4
gem install slim

Thao tác này sẽ cài đặt các thư viện Sinatra, Ruby Handlebars, Kramdown, và Slim. Sinatra là một framework ứng dụng web. Ruby Handlebars cài đặt công cụ xử lý template trong Ruby. Kramdown là thư viện chuyển đổi Markdown sang HTML. Slim là thư viện làm việc tương tự như Jade, nhưng nó không bao gồm các định nghĩa macro của Jade. Do đó, các macro được sử dụng trong các chỉ mục của News and Blog hôm nay là Jade như bình thường.

Tạo Tập tin rubyPress.rb

Trong thư mục cấp cao nhất, tạo tập tin rubyPress.rb và thêm code sau đây. Tôi sẽ chú thích từng phần khi nó được thêm vào tập tin.

1
#

2
# Load the Libraries.

3
#

4
require 'sinatra'  		# http://www.sinatrarb.com/

5
require 'ruby-handlebars' 	# https://github.com/vincent-psarga/ruby-handlebars

6
require 'kramdown' 			# http://kramdown.gettalong.org

7
require 'slim' 				# http://slim-lang.com/

8
require 'json'
9
require 'date'

Điều đầu tiên cần làm là nạp các thư viện. Không giống với Node.js, những thư viện này không được nạp vào một biến. Các thư viện Ruby thêm các hàm của chúng vào phạm vi chương trình.

1
#

2
# Setup the Handlebars engine.

3
#

4
$hbs = Handlebars::Handlebars.new
5
6
#

7
# HandleBars Helper:   date

8
#

9
# Description:         This helper returns the current date

10
#                      based on the format given.

11
#

12
$hbs.register_helper('date') {|context, format|
13
	now = Date.today
14
	now.strftime(format)
15
}
16
17
#

18
# HandleBars Helper:   cdate

19
#

20
# Description:         This helper returns the given date

21
#                      based on the format given.

22
#

23
$hbs.register_helper('cdate') {|context, date, format|
24
	day = Date.parse(date)
25
	day.strftime(format)
26
}
27
28
#

29
# HandleBars Helper:   save

30
#

31
# Description:         This helper expects a

32
#                      "|" where the name

33
#                      is saved with the value for future

34
#                      expansions. It also returns the

35
#                      value directly.

36
#

37
$hbs.register_helper('save') {|context, name, text|
38
	#

39
	# If the text parameter isn't there, then it is the 

40
	# goPress format all combined into the name. Split it 

41
	# out. The parameters are not String objects. 

42
	# Therefore, they need converted first.

43
	#

44
	name = String.try_convert(name)
45
	if name.count("|") > 0
46
		parts = name.split('|')
47
		name = parts[0]
48
		text = parts[1]
49
	end
50
51
	#

52
	# Register the new helper.

53
	#

54
	$hbs.register_helper(name) {|context, value|
55
		text
56
	}
57
58
	#

59
	# Return the text.

60
	#

61
	text
62
}

Thư viện Handlebars được khởi tạo với các hàm trợ giúp khác nhau đã được định nghĩa. Các hàm trợ giúp được định nghĩa bao gồm date, cdate, và save.

Hàm trợ giúp date lấy ngày và giờ hiện tại, và định dạng nó theo chuỗi định dạng truyền vào hàm trợ giúp. cdate thì tương tự ngoại trừ để truyền ngày trước tiên. Hàm trợ giúp save cho phép bạn chỉ định một tên và giá trị. Nó tạo ra một hàm trợ giúp mới với tên name và truyền ngược về giá trị. Điều này cho phép bạn tạo các biến được chỉ định một lần và có hiệu lực ở nhiều vị trí. Hàm này cũng lấy phiên bản Go, trong đó nhận ​​một chuỗi với name, '|' như một dấu phân tách, và value.

1
#

2
# Load Server Data.

3
#

4
$parts = {}
5
$parts = JSON.parse(File.read './server.json')
6
$styleDir = Dir.getwd + '/themes/styling/' + $parts['CurrentStyling']
7
$layoutDir = Dir.getwd + '/themes/layouts/' + $parts['CurrentLayout']
8
9
#

10
# Load the layouts and styles defaults.

11
#

12
$parts["layout"] = File.read $layoutDir + '/template.html'
13
$parts["404"] = File.read $styleDir + '/404.html'
14
$parts["footer"] = File.read $styleDir + '/footer.html'
15
$parts["header"] = File.read $styleDir + '/header.html'
16
$parts["sidebar"] = File.read $styleDir + '/sidebar.html'
17
18
#

19
# Load all the page parts in the parts directory.

20
#

21
Dir.entries($parts["Sitebase"] + '/parts/').select {|f|
22
	if !File.directory? f
23
		$parts[File.basename(f, ".*")] = File.read $parts["Sitebase"] + '/parts/' + f
24
	end
25
}
26
27
#

28
# Setup server defaults:

29
#

30
port = $parts["ServerAddress"].split(":")[2]
31
set :port, port

Phần tiếp theo của code là để nạp các yếu tố có thể được lưu vào bộ nhớ đệm của trang web. Đây là tất cả mọi thứ bên trong styles và layout cho theme của bạn và các phần tử bên trong thư mục parts. Một biến toàn cục, $parts, được nạp trước tiên từ tập tin server.json. Thông tin đó sau này được sử dụng để nạp các yếu tố thích hợp cho bố cục và theme cụ thể. Công cụ xử lý template Handlebars sử dụng thông tin này để điền vào các template.

1
#

2
# Define the routes for the CMS.

3
#

4
get '/' do
5
  page "main"
6
end
7
8
get '/favicon.ico', :provides => 'ico' do
9
	File.read "#{$parts['Sitebase']}/images/favicon.ico"
10
end
11
12
get '/stylesheets.css', :provides => 'css'  do
13
	File.read "#{$parts["Sitebase"]}/css/final/final.css"
14
end
15
16
get '/scripts.js', :provides => 'js'  do
17
	File.read "#{$parts["Sitebase"]}/js/final/final.js"
18
end
19
20
get '/images/:image', :provides => 'image' do
21
	File.read "#{$parts['Sitebase']}/images/#{parms['image']}"
22
end
23
24
get '/posts/blogs/:blog' do
25
	post 'blogs', params['blog'], 'index'
26
end
27
28
get '/posts/blogs/:blog/:post' do
29
	post 'blogs', params['blog'], params['post']
30
end
31
32
get '/posts/news/:news' do
33
	post 'news', params['news'], 'index'
34
end
35
36
get '/posts/news/:news/:post' do
37
	post 'news', params['news'], params['post']
38
end
39
40
get '/:page' do
41
	page params['page']
42
end

Phần tiếp theo chứa các định nghĩa cho tất cả các route. Sinatra là một máy chủ hoàn toàn tương thích với REST. Nhưng đối với CMS này, tôi sẽ chỉ sử dụng động từ get. Mỗi route sẽ nhận các yếu tố từ route để truyền vào các hàm để tạo ra trang chính xác. Trong Sinatra, một name được đặt trước bởi dấu hai chấm xác định một phần của route để truyền đến trình xử lý route. Các yếu tố này nằm trong bảng băm params.

1
#

2
# Various functions used in the making of the server:

3
#

4
5
#

6
# Function:    page

7
#

8
# Description: This function is for processing a page

9
#              in the CMS.

10
#

11
# Inputs:

12
# 				pg 	The page name to lookup

13
#

14
def page(pg)
15
	processPage $parts["layout"], "#{$parts["Sitebase"]}/pages/#{pg}"
16
end

Hàm page lấy tên của một trang từ route và truyền bố cục trong biến $parts cùng với đường dẫn đầy đủ đến tập tin trang web cần thiết cho hàm processPage. Hàm processPage nhận thông tin này và tạo ra các trang thích hợp mà sau đó nó sẽ trả về. Trong Ruby, đầu ra của hàm trước là giá trị trả về của hàm.

1
#

2
# Function:    post

3
#

4
# Description: This function is for processing a post type

5
#              page in the CMS. All blog and news pages are

6
#    				post type pages.

7
#

8
# Inputs:

9
#              type   The type of the post

10
#              cat    The category of the post (blog, news)

11
#              post   The actual page of the post

12
#

13
def post(type, cat, post)
14
	processPage $parts["layout"], "#{$parts["Sitebase"]}/posts/#{type}/#{cat}/#{post}"
15
end

Hàm post cũng giống như hàm page, ngoại trừ rằng hàm này hoạt động cho tất cả các trang kiểu post. Hàm này nhận type (kiểu), cat (danh mục) và bản thân post. Những điều này sẽ tạo ra địa chỉ để hiển thị trang phù hợp.

1
#

2
# Function:    figurePage

3
#

4
# Description: This function is to figure out the page

5
#              type (ie: markdown, HTML, jade, etc), read

6
#              the contents, and translate it to HTML.

7
#

8
# Inputs:

9
#              page      The address of the page 

10
#                        without its extension.

11
#

12
def figurePage(page)
13
	result = ""
14
15
	if File.exist? page + ".html"
16
		#

17
		# It's an HTML file.

18
		#

19
		result = File.read page + ".html"
20
	elsif File.exist? page + ".md"
21
		#

22
		# It's a markdown file.

23
		#

24
		result = Kramdown::Document.new(File.read page + ".md").to_html
25
26
		#

27
		# Fix the fancy quotes from Kramdown. It kills

28
		# the Handlebars parser.

29
		#

30
		result.gsub!("“","\"")
31
		result.gsub!("”","\"")
32
	elsif File.exist? page + ".amber"
33
		#

34
		# It's a jade file. Slim doesn't support

35
		# macros. Therefore, not as powerful as straight jade.

36
		# Also, we have to render any Handlebars first

37
		# since the Slim engine dies on them.

38
		#

39
		File.write("./tmp.txt",$hbs.compile(File.read page + ".amber").call($parts))
40
		result = Slim::Template.new("./tmp.txt").render()
41
	else
42
		#

43
		# Doesn't exist. Give the 404 page.

44
		#

45
		result = $parts["404"]
46
	end
47
48
	#

49
	# Return the results.

50
	#

51
	return result
52
end

Hàm figurePage sử dụng hàm processPage để đọc nội dung của trang từ hệ thống tập tin. Hàm này nhận đường dẫn hoàn chỉnh đến tập tin không cần có phần mở rộng. figurePage sau đó kiểm tra tập tin với tên đã cho với phần mở rộng html để đọc một tập tin HTML. Lựa chọn thứ hai là phần mở rộng md đối với một tập tin Markdown.

Cuối cùng, nó kiểm tra phần mở rộng amber đối với một tập tin Jade. Hãy nhớ: Amber là tên của thư viện để xử lý tập tin có cú pháp Jade trong Go. Tôi giữ nguyên nó cho chức năng trung gian. Một tập tin HTML chỉ đơn thuần được truyền ngược lại, trong khi tất cả các tập tin Markdown và Jade được chuyển đổi sang HTML trước khi truyền ngược trở lại.

Nếu không tìm thấy tập tin, người dùng sẽ nhận được trang 404. Bằng cách này, trang "không tìm thấy trang" của bạn trông giống như bất kỳ trang nào khác ngoại trừ nội dung.

1
#

2
# Function:    processPage

3
#

4
# Description: The function processes a page by getting

5
#              its contents, combining with all the page

6
#              parts using Handlebars, and processing the

7
#              shortcodes.

8
#

9
# Inputs:

10
#              layout  The layout structure for the page

11
#              page    The complete path to the desired

12
#                      page without its extension.

13
#

14
def processPage(layout, page)
15
	#

16
	# Get the page contents and name.

17
	#

18
	$parts["content"] = figurePage page
19
	$parts["PageName"] = File.basename page
20
21
	#

22
	# Run the page through Handlebars engine.

23
	#

24
	begin
25
		pageHB = $hbs.compile(layout).call($parts)
26
	rescue
27
		pageHB = "

28
Render Error

29


30
"
31
	end
32
33
	#

34
	# Run the page through the shortcodes processor.

35
	#

36
	pageSH = processShortCodes pageHB
37
38
	#

39
	# Run the page through the Handlebar engine again.

40
	#

41
	begin
42
		pageFinal = $hbs.compile(pageSH).call($parts)
43
	rescue
44
		pageFinal = "

45
Render Error

46


47
" + pageSH
48
	end
49
50
	#

51
	# Return the results.

52
	#

53
	return pageFinal
54
end

Hàm processPage thực hiện tất cả các việc khai triển template dựa trên dữ liệu trang. Nó bắt đầu bằng cách gọi hàm figurePage để lấy nội dung của trang. Sau đó, nó xử lý bố cục được truyền cho nó với Handlebars để khai triển template.

Sau đó, hàm processShortCode sẽ tìm và xử lý tất cả các shortcode bên trong trang. Kết quả sau đó được truyền cho Handlebars một lần nữa để xử lý bất kỳ macro còn lại nào bởi các shortcode. Người dùng nhận được kết quả cuối cùng.

1
#

2
# Function:    processShortCodes

3
#

4
# Description: This function takes the page and processes

5
#              all of the shortcodes in the page.

6
#

7
# Inputs:

8
#              page     The contents of the page to 

9
#                       process.

10
#

11
def processShortCodes(page)
12
	#

13
	# Initialize the result variable for returning.

14
	#

15
	result = ""
16
17
	#

18
	# Find the first shortcode

19
	#

20
	scregFind = /\-\[([^\]]*)\]\-/
21
	match1 = scregFind.match(page)
22
	if match1 != nil
23
		#

24
		# We found one! get the text before it

25
		# into the result variable and initialize

26
		# the name, param, and contents variables.

27
		#

28
		name = ""
29
		param = ""
30
		contents = ""
31
		nameLine = match1[1]
32
		loc1 = scregFind =~ page
33
		result = page[0, loc1]
34
35
		#

36
		# Separate out the nameLine into a shortcode

37
		# name and parameters.

38
		#

39
		match2 = /(\w+)(.*)*/.match(nameLine)
40
		if match2.length == 2
41
			#

42
			# Just a name was found.

43
			#

44
			name = match2[1]
45
		else
46
			#

47
			# A name and parameter were found.

48
			#

49
			name = match2[1]
50
			param = match2[2]
51
		end
52
53
		#

54
		# Find the closing shortcode

55
		#

56
		rest = page[loc1+match1[0].length, page.length]
57
		regEnd = Regexp.new("\\-\\[\\/#{name}\\]\\-")
58
		match3 = regEnd.match(rest)
59
		if match3 != nil
60
			#

61
			# Get the contents the tags enclose.

62
			#

63
			loc2 = regEnd =~ rest
64
			contents = rest[0, loc2]
65
66
			#

67
			# Search the contents for shortcodes.

68
			#

69
			contents = processShortCodes(contents)
70
71
			#

72
			# If the shortcode exists, run it and include

73
			# the results. Otherwise, add the contents to

74
			# the result.

75
			#

76
			if $shortcodes.include?(name)
77
				result += $shortcodes[name].call(param, contents)
78
			else
79
				result += contents
80
			end
81
82
			#

83
			# process the shortcodes in the rest of the

84
			# page.

85
			#

86
			rest = rest[loc2 + match3[0].length, page.length]
87
			result += processShortCodes(rest)
88
		else
89
			#

90
			# There wasn't a closure. Therefore, just 

91
			# send the page back.

92
			#

93
			result = page
94
		end
95
	else
96
		#

97
		# No shortcodes. Just return the page.

98
		#

99
		result = page
100
	end
101
102
	return result
103
end

Hàm processShortCodes nhận chuỗi đã cho, tìm kiếm từng shortcode, và chạy shortcode cụ thể với các đối số và nội dung của shortcode. Tôi cũng sử dụng thủ tục shortcode để xử lý nội dung cho các shortcode.

Một shortcode là một thẻ tương tự như HTML sử dụng -[]- để tách thẻ mở và thẻ đóng -[/]-. Thẻ mở cũng chứa các thông số cho shortcode. Do đó, ví dụ một shortcode:

1
-[box]-
2
This is inside a box.
3
-[/box]-

Shortcode này định nghĩa shortcode box mà không có bất kỳ tham số nào với nội dung <p>This is inside a box.</p>. Shortcode box bao quanh nội dung trong HTML thích hợp để tạo ra một hộp xung quanh văn bản với văn bản được canh giữa bên trong hộp. Nếu sau đó này bạn muốn thay đổi cách box được kết xuất, thì bạn chỉ cần thay đổi định nghĩa của shortcode. Điều này tiết kiệm được rất nhiều công sức.

1
#

2
# Data Structure:  $shortcodes

3
#

4
# Description:     This data structure contains all

5
#                  the valid shortcodes names and the

6
#                  function. All shortcodes should

7
#                  receive the arguments and the

8
#                  that the shortcode encompasses.

9
#

10
$shortcodes = {
11
	"box" => lambda { |args, contents|
12
		return("#{contents}")
13
	},
14
   'Column1'=> lambda { |args, contents|
15
		return("#{contents}")
16
   },
17
   'Column2' => lambda { |args, contents|
18
      return("#{contents}")
19
   },
20
   'Column1of3' => lambda { |args, contents|
21
      return("#{contents}")
22
   },
23
   'Column2of3' => lambda { |args, contents|
24
      return("#{contents}")
25
   },
26
   'Column3of3' => lambda { |args, contents|
27
      return("#{contents}")
28
   },
29
   'php' => lambda { |args, contents|
30
      return("#{contents}") }, 'js' => lambda { |args, contents| return("#{contents}") }, "html" => lambda { |args, contents| return("#{contents}") }, 'css' => lambda {|args, contents| return("#{contents}") } }

Điều cuối cùng trong tập tin là bảng băm $shortcodes chứa các thủ tục shortcode. Đây là các shortcode đơn giản, nhưng bạn có thể tạo ra các shortcode khác phức tạp nếu bạn muốn.

Tất cả các shortcode phải chấp nhận hai tham số: argscontents. Các chuỗi này chứa các tham số của shortcode và nội dung mà shortcode bao quanh. Vì các shortcode nằm bên trong một bảng băm, nên tôi đã sử dụng một hàm lambda để định nghĩa chúng. Một hàm lambda là một hàm không có tên. Cách duy nhất để chạy các hàm này là từ mảng băm.

Chạy Máy chủ

Một khi bạn đã tạo ra tập tin rubyPress.rb với các nội dung ở trên, bạn có thể chạy máy chủ với:

1
ruby rubyPress.rb

Vì framework Sinatra hoạt động với cấu trúc Rack của Ruby on Rails, nên bạn có thể sử dụng Pow để chạy máy chủ. Pow sẽ cài đặt các tập tin lưu trữ của hệ thống để chạy máy chủ của bạn tại cục bộ tương tự như trên một host. Bạn có thể cài đặt Pow với Powder bằng các lệnh sau:

1
gem install powder
2
powder install

Powder là một thủ tục dòng lệnh để quản lý các trang web Pow trên máy tính của bạn. Để Pow có thể nhìn thấy trang web của bạn, bạn phải tạo một liên kết mềm đến thư mục dự án của bạn trong thư mục ~/.pow. Nếu máy chủ nằm trong thư mục /Users/test/Documents/rubyPress, bạn sẽ thực thi các lệnh sau:

1
cd ~/.pow
2
ln -s /Users/test/Documents/rubyPress rubyPress

ln -s tạo một liên kết mềm đến thư mục được chỉ định trước nhất, với tên được chỉ định thứ hai. Pow sau đó sẽ thiết lập một tên miền trên hệ thống của bạn bằng tên của liên kết mềm. Trong ví dụ ở trên, truy cập trang http://rubyPress.dev trong trình duyệt sẽ tải trang từ máy chủ.

Để khởi động máy chủ, hãy gõ lệnh sau đây sau khi tạo liên kết mềm:

1
powder start

Để tải lại máy chủ sau khi thực hiện một số thay đổi code, hãy gõ lệnh như sau:

1
powder restart
rubyPress Main PagerubyPress Main PagerubyPress Main Page
Trang chủ của rubyPress

Truy cập trang web trong trình duyệt sẽ cho ra kết quả như hình ảnh ở trên. Pow sẽ thiết lập trang web tại http://rubyPress.dev. Cho dù bạn sử dụng phương pháp nào để khởi chạy trang web, thì bạn cũng sẽ có được một trang kết quả tương tự.

Tóm tắt

Vâng, bạn đã làm được. Một CMS khác, nhưng lần này bằng Ruby. Phiên bản này là phiên bản ngắn nhất trong tất cả các CMS được tạo ra trong loạt bài này. Hãy thực hành với code và xem bạn có thể mở rộng framework cơ bản này như thế nào nhé.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.