Sử dụng MongoDB trong Ruby on Rails
Bài đăng này đã không được cập nhật trong 9 năm
Trong bàì viết này, tôi cố gắng đưa ra những thông tin cần thiết về NoSQL. NoSQL là gì? NoSQL để làm gì? Tại sao phải dùng NoSQL? ... và giới thiệu về MongoDB. Ở đây tôi chỉ tập trung vào những điều cốt lõi để tạo bước đẩy cho chúng ta thao tác với NoSQL và MongoDB khi làm việc với Ruby on Rails.
Do đó, nếu bạn chưa biết tới NoSQL hay lần đầu làm việc với MongoDB thì trước tiên bạn nên tìm kiếm và đọc những tài liệu liên quan tới nó.
1. NoSQL là gì?
NoSQL là 1 dạng cở sở dữ liệu (CSDL) mã nguồn mở không sử dụng T-SQL để truy vấn thông tin. NoSQL viết tắt bởi: None-Relational SQL, hay có nơi thường gọi là Not-only SQL.
NoSQL ra đời như một mảnh vá cho những khuyết điểm và thiếu xót cũng như hạn chế của mô hình dữ liệu quan hệ RDBMS về tốc độ, tính năng, khả năng mở rộng, memory cache, ...
Chắc hẳn, bạn đã sử dụng một dạng CSDL quan hệ nào đó trước khi đọc bài viết này, có thể là: SQL Server, MySQL. Và tất nhiên không ít lần bạn vất vả trong việc chỉnh sửa các bảng dữ liệu khi liên quan tới khóa chính và khóa ngoài, hay một loạt rắc rối khác trong qua trình làm việc. Bời vì đó là CSDL quan hệ.
Với NoSQL bạn có thể mở rộng dữ liệu mà không lo tới những việc như tạo khóa ngoài, khóa chính, kiểm tra ràng buộc, v.v... Vì NoSQL không hạn ché việc mở rộng dữ liệu nên tồn tại nhiều nhược điểm như: sự phục thuộc vào từng bản ghi, tính nhất quán, toàn vẹn dữ liệu,... nhưng chúng ta có thể chấp nhận những nhược điểm này để khiến ứng dụng cải thiện hiệu suất cao hơn khi giải quyết những bài toán lớn về hệ thống thông tin, phân tán hay lưu trữ dữ liệu.
NoSQL được sử dụng ở đâu? NoSQL được sử dụng ở rất nhiều công ty, tập đoàn lớn, ví dụ như Facebook sử dụng Cassandra do Facebook phát triển, Google phát triển và sử dụng BigTable, ....
2. MongoDB là gì?
MongoDB là 1 hệ thống CSDL mã nguồn mở được phát triển và hỗ trợ bởi 10gen, là CSDL NoSQL hàng đầu được hàng triệu người sử dụng.
Thay vì lưu dữ liệu dưới dạng bảng và các tuple như trong các CSDL quan hệ thì nó lưu dữ liệu dưới dạng JSON (trong MongoDB được gọi là dạng BSON vì nó lưu trữ dưới dạng binary từ 1 JSON document). Ưu điểm của BSON là hiệu quả hơn các dạng format trung gian như XML hay JSON cả hiệu tiêu thụ bộ nhớ lẫn hiệu năng xử lý. BSON hỗ trợ toàn bộ dạng dữ liệu mà JSON hỗ trợ (sting, integer, double, boolean, aray, object, null) và thêm một số dạng dữ liệu đặc biệt như regular expression, object ID, data, binary, code.
3. Cài đặt và sử dụng MongoDB
Trong terminal bạn lần lượt chạy các câu lệnh sau:
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
sudo echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | tee -a /etc/apt/sources.list.d/10gen.list
sudo apt-get -y update
sudo apt-get -y install mongodb-10gen
4. Tạo một ứng dụng Rails với Mongoid
Generate một ứng dụng Rails:
rails new rail4_mongoid --skip-active-record
--skip-active-record
là quan trọng bởi vì nó không thêm ActiveRecord trong ứng dụng được tạo. Ta cần sửa Gemfile để bỏ sqlite3 và thêm Mongoid.
Tìm và xóa dòng sau trong Gemfile:
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
Và thêm các dòng sau:
gem 'mongoid', '~> 4', github: 'mongoid/mongoid'
gem 'bson_ext'
Sau khi chạy lệnh bundle cài đặt gem xong. Ta chạy lệnh config mongoid để tạo ra file /config/mongoid.yml
rails g mongoid:config
Mọi thứ đã sẵn sàng để xây dựng một ứng dụng. Đầu tiên tạo một Article model với name, content và published_on bằng lệnh scaffolding:
rails g scaffold article name:string content:text published_on:date
Do từ Mongoid 4.0 đã gỡ bỏ MultiParameterAttributes nên khi tạo một acticle sẽ phát sinh lỗi vì published_on
là multi attributes. Để khắc phục lỗi này chúng ta tạo file multi_parameter_attributes.rb
trong thư mục lib/mongoid
# This class is needed since Mongoid doesn't support Multi-parameter
# attributes in version 4.0 before it's moved to active model in rails 4
#
# https://github.com/mongoid/mongoid/issues/2954
#
# encoding: utf-8
module Mongoid
# Adds Rails' multi-parameter attribute support to Mongoid
#
# @todo: Durran: This module needs an overhaul
module MultiParameterAttributes
module Errors
# Raised when an error occurred while doing a mass assignment to an
# attribute through the <tt>attributes=</tt> method. The exception
# has an +attribute+ property that is the name of the offending attribute
class AttributeAssignmentError < Mongoid::Errors::MongoidError
attr_reader :exception, :attribute
def initialize(message, exception, attribute)
@exception = exception
@attribute = attribute
@message = message
end
end
# Raised when there are multiple errors while doing a mass assignment
# through the +attributes+ method. The exception has an +errors+
# property that contains an array of AttributeAssignmentError
# objects, each corresponding to the error while assigning to an
# attribute
class MultiparameterAssignmentErrors < Mongoid::Errors::MongoidError
attr_reader :errors
def initialize(errors)
@errors = errors
end
end
end
# Process the provided attributes casting them to their proper values if a
# field exists for them on the document. This will be limited to only the
# attributes provided in the suppied +Hash+ so that no extra nil values get
# put into the document's attributes
#
# @example Process the attributes
# person.process_attributes(:title => "sir", :age => 40)
#
# @param [ Hash ] attrs The attributes to set
#
# @since 2.0.0.rc.7
def process_attributes(attrs = nil)
if attrs
errors = []
attributes = attrs.class.new
attributes.permit! if attrs.respond_to?(:permitted?) && attrs.permitted?
multi_parameter_attributes = {}
attrs.each_pair do |key, value|
if key =~ /\A([^\(]+)\((\d+)([if])\)$/
key, index = $1, $2.to_i
(multi_parameter_attributes[key] ||= {})[index] = value.empty? ? nil : value.send("to_#{$3}")
else
attributes[key] = value
end
end
multi_parameter_attributes.each_pair do |key, values|
begin
values = (values.keys.min..values.keys.max).map { |i| values[i] }
field = self.class.fields[database_field_name(key)]
attributes[key] = instantiate_object(field, values)
rescue => e
errors << Errors::AttributeAssignmentError.new(
"error on assignment #{values.inspect} to #{key}", e, key
)
end
end
unless errors.empty?
raise Errors::MultiparameterAssignmentErrors.new(errors),
"#{errors.size} error(s) on assignment of multiparameter attributes"
end
super(attributes)
else
super
end
end
protected
def instantiate_object(field, values_with_empty_parameters)
return nil if values_with_empty_parameters.all? { |v| v.nil? }
values = values_with_empty_parameters.collect { |v| v.nil? ? 1 : v }
klass = field.type
if klass == DateTime || klass == Date || klass == Time
field.mongoize(values)
elsif klass
klass.new(*values)
else
values
end
end
end
end
Thêm config.autoload_paths += %W(#{config.root}/lib)
vào /config/application.rb
để autoload.
Sau đó thêm dòng include Mongoid::MultiParameterAttributes
vào /app/models/article.rb
class Article
include Mongoid::Document
include Mongoid::MultiParameterAttributes
field :name, type: String
field :content, type: String
field :published_on, type: Date
validates :name, presence: true
end
Tiếp theo tạo model Comment:
rails g model comment name:string content:text
Định nghĩa quan hệ giữa article và comment. Thêm dòng embeds_many :comments
trong /app/models/article.rb
. Thêm dòng embedded_in :article, inverse_of: :comments
trong /app/models/comment.rb
Trong /config/routes.rb
:
Rails.application.routes.draw do
resources :articles do
resources :comments
end
end
Generate comments_controller.rb
:
rails g controller comments
Sửa file comments_controller.rb
như sau:
class CommentsController < ApplicationController
def create
@article = Article.find params[:article_id]
@comment = @article.comments.create!(comment_params)
redirect_to @article, notice: "Comment created!"
end
private
def comment_params
params.require(:comment).permit(:name, :content)
end
end
Đồng thời sửa file /app/views/articles/show.html.erb
:
<p id="notice"><%= notice %></p>
<p>
<strong>Name:</strong>
<%= @article.name %>
</p>
<p>
<strong>Content:</strong>
<%= @article.content %>
</p>
<p>
<strong>Published on:</strong>
<%= @article.published_on %>
</p>
<% if @article.comments.size > 0 %>
<h2>Comments</h2>
<% for comment in @article.comments %>
<h3><%= comment.name %></h3>
<p><%= comment.content %></p>
<% end %>
<% end %>
<h2> New Comment</h2>
<%= form_for [@article, Comment.new] do |f| %>
<p><%= f.label :name %> <%= f.text_field :name %></p>
<p><%= f.text_area :content, rows: 10 %></p>
<p><%= f.submit %></p>
<% end %>
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>
Như vậy là chúng ta đã tạo được một ứng dụng đơn giản sử dụng mongoid. Ta thử kiểm tra kết quả trong rails console
:
2.1.1 :002 > Article.last
MOPED: 127.0.0.1:27017 QUERY database=rails4_mongoid_development collection=articles selector={"$query"=>{}, "$orderby"=>{:_id=>-1}} flags=[] limit=-1 skip=0 batch_size=nil fields=nil runtime: 1.0883ms
=> #<Article _id: 558a0e35766965789600000a, name: "Mongoid", content: "That's great", published_on: 2015-06-24 00:00:00 UTC>
Tham khảo project sample tại đây: https://github.com/vietntframgia/rails4_mongoid
5. Khi nào sử dụng MongoDB
MongoDB thực sự rất hot hiện nay nhưng không phải lúc nào ta sử dụng nó cũng tốt, có những trường hợp không nên sử dụng và nên sử dụng. Nên sử dụng MongoDB trong các trường hợp như sau:
- Nếu website của bạn có tính chất INSERT cao, bởi vì MongoDB có sẵn cơ chế ghi với tốc độ cao và an toàn.
- Website của bạn ở dạng thời gian thực nhiều, nghĩa là nhiều người thao tác với ứng dụng. Nếu trong quá trình load bị lỗi tại thời điểm nào đó thì nó sẽ bỏ qua phần đó nên sẽ an toàn.
- Website bạn có lượng dữ liệu lớn, giả sử web bạn có đến 10 triệu records thì đó là cơn ác mộng với MySQL. Bời vì MongoDB có khả năng tìm kiếm thông tin liên quan cũng khá nhanh nên trường hợp này nên dùng nó.
- Máy chủ không có hệ quản trị CSDL, trường hợp này thường bạn sẽ sử dụng SQLite hoặc MongoDB.
6. Ưu và nhược điểm của MongoDB là gì?
Ưu điểm
- Dữ liệu phi cấu trúc, không có thuộc tính ràng buộc, toàn vẹn nên tính sẵn sàng cao, hiệu suất lớn và dễ dàng mở rộng lưu trữ.
- Dữ liệu được caching lên RAM, hạn chế truy cập vào ổ cứng nên tốc độ đọc và ghi cao.
Nhược điểm
- Không ràng buộc, toàn vẹn nên không ứng dụng được cho các mô hình giao dịch yêu cầu độ chính xác cao.
- Không có cơ chế transaction để phục vụ các ứng dụng ngân hàng.
- Dữ liệu được caching, lấy RAM làm trọng tâm hoạt động vì vậy khi hoạt động yêu cầu một bộ nhớ RAM lớn.
7. Kết Luận
Qua bài viết này chúng ta thấy rằng việc cài đặt và sử dụng MongoDB trong Rails không quá phức tạp. Nhưng chúng ta cũng cần chú ý khi nào cần sử dụng và khi nào không cần sử dụng MongoDB. Đối với các dự án vừa và nhỏ thì việc sử dụng CSDL quan hệ sẽ nhanh hơn rất nhiều. Còn những dự án yêu cầu truy xuất dữ liệu lớn thì MongoDB là giải pháp tốt nhất. Với những ưu điểm của MongoDB thì bạn cũng nên thử trải nghiệm với nó.
Tài liệu tham khảo
All rights reserved