+1

Sử dụng MongoDB với gem MongoID

Sử dụng MongoDB với gem MongoID

I. Giới thiệu 1. Mongodb là gì? - Hiểu một cách nôm na thì MongoDB là một mã nguồn mở và là một tập tài liệu dùng cơ chế NoSQL để truy vấn, nó được viết bởi ngôn ngữ C++. Chính vì được viết bởi C++ nên nó có khả năng tính toán với tốc độ cao chứ không giống như các hệ quản trị CSDL hiện nay. - Document trong MongoDB có cấu trúc tương tự như kiểu dữ liệu JSON, nghĩa là sẽ có các cặp (key => giá trị) nên nó có tính năng động rất lớn. Document ta có thể hiểu nó giống như các record dữ liệu trong MYSQL, tuy nhiên nó có sự khác biệt là các cặp (key => value) có thể không giống nhau ở mỗi document.
- MongoDB có những ưu điểm sau đây: - Dễ học, có một số nét khá giống với CSDL quan hệ – Quản lý bằng command line hoặc bằng GUI như RockMongo hoặc phpMoAdmin - Linh động, không cần phải định nghĩa cấu trúc dữ liệu trước khi tiến hành lưu trữ nó -> rất tốt khi ta cần làm việc với các dạng dữ liệu không có cấu trúc. - Khả năng mở rộng tốt (distributed horizontally), khả năng cân bằng tải cao, tích hợp các công nghệ quản lý dữ liệu vẫn tốt khi kích thước và thông lượng trao đổi dữ liệu tăng. - Miễn phí

2. Mongoid
- Mongoid là 1 Object-Document-Mapper(ODM) cho MongoDB được viết bằng Ruby. Nó được hình tháng vào 8/2009 bởi Durran Jordan.
- Triết lý của Mongoid là cung cấp một API quen thuộc với các nhà phát triển của Ruby đã sử dụng Active Record hoặc dữ liệu Mapper, trong khi tận dụng sức mạnh của schema MongoDB ít hơn và thiết kế dựa trên tài liệu, hiệu suất, truy vấn động, và hoạt động sửa đổi.<br>
Cách cài đặt:
- MongoId được đóng gói như một gem, và nó được lưu trữ Rubygems. Nó có thể được cài đặt bằng tay hoặc với bundler.
    + Bằng tay
		$ gem install mongoid
    + Bằng Gemfile<br>
    Add vào Gemfile
		gem "mongoid"
	Sau đó sử dụng bundle
		$ bundle install
- Khả năng tương thích với các version của Ruby

Ruby Version|2.4.x|2.6.x|3.0.x
--- | --- | ---
MRI 1.8.x|No|No|No
MRI 1.9.x|Yes|Yes|Yes
MRI 2.0.x|Yes|Yes|Yes
MRI 2.1.x|Yes|Yes|Yes
MRI 2.2.x|Yes|Yes|Yes
JRuby 1.7.x|Yes|Yes|Yes
- Config mongodb cho rails app
	+ Cài đặt mongodb xem tại http://docs.mongodb.org/master/installation/
    + Sinh file config bằng cách chạy lệnh
		$ rails g mongoid:config
    sẽ tạo ra file `../config/mongoid.yml`
            default: &default
              database: <%= ENV["MONGODB_NAME"] %>
              hosts:
                - <%= ENV["MONGODB_HOST"] %>
            development:
              sessions:
                default:
                  <<: *default
                  options:
              options:
            test:
              sessions:
                default:
                  <<: *default
                  options:
                    read: primary
                    max_retries: 1
                    retry_interval: 0
            production:
              sessions:
                default:
                  database: two
                  hosts:
                    - mongo_pri.local:27017
                    - mongo_sec.local:27017
                  options:
                    read: :primary_preferred
            staging:
              sessions:
                default:
                  <<: *default
                  options:
- Logging
	+ Config Logging level
		Mongoid.logger.level = Logger::DEBUG
		Mongo::Logger.logger.level = Logger::DEBUG
    Logging level Debug là mặc định của mogoid
- Documents
	+ Documents là những đối tượng cốt lõi trong Mongoid và bất kỳ đối tượng muốn tồn tại phải được include `Mongoid::Document`. Đại diện của Documents trong MongoDB là BSON nó cũng tương tự như HASH trong ruby hoặc đối tượng JSON.
- Storage
	+ Khai báo 1 storage trong rails
        class UserLastLogin
              include Mongoid::Document
              include Mongoid::Timestamps
              include Mongoid::Paranoia

              field :last_login_date, type: Time
              field :user_id, type: Integer
              index({user_id: 1}, {name: "user_id_index"})
        end
  Khi một document được lưu trữ trong cơ sở dữ liệu đối tượng ruby sẽ xếp theo thứ tự vào BSON và có một cấu trúc như vậy:
    {
        "_id" : ObjectId("55f6728f6672615edc000000"),
        "deleted_at" : null, "user_id" : 21,
        "last_login_date" : ISODate("2015-09-14T07:09:03.420Z"),
        "updated_at" : ISODate("2015-09-14T07:09:03.421Z"),
        "created_at" : ISODate("2015-09-14T07:09:03.421Z")
    }
- Mặc dù MongoDB là một cơ sở dữ liệu schemaless, hầu hết sử dụng sẽ được với các ứng dụng web, nơi tham số đến với các server như chuỗi kí tự. Mongoid cung cấp một cơ chế cho việc chuyển đổi chuỗi kí tự vào loại thích hợp của họ thông qua các định nghĩa của các fields trong Document::Mongoid.
        class User
          include Mongoid::Document
          field :first_name, type: String
          field :last_name, type: String
          field :full_name, type: String
        end
- Danh sách các kiểu dữ liệu được định nghĩa
    Array
    BigDecimal
    Boolean
    Date
    DateTime
    Float
    Hash
    Integer
    BSON::ObjectId
    BSON::Binary
    Range
    Regexp
    String
    Symbol
    Time
    TimeWithZone
- Nếu như không quyết định kiểu dữ liệu cho các fiedls, Mongoid sẽ đối xử với nó như là một đối tượng và không cố gắng để định kiểu đó khi gửi các giá trị cơ sở dữ liệu
- Get Set gía trị field
    pry(main)> user_last_login.user_id
	=> 1075
    pry(main)> user_last_login[:user_id]
	=> 1075
    pry(main)> user_last_login.read_attribute :user_id
	=> 1075
	pry(main)> user_last_login.user_id = 123
    => 123
	pry(main)> user_last_login[:user_id] = 123
    => 123
	pry(main)> user_last_login.write_attribute(:user_id) = 123
	=> 123
- Hash Fields
Khi sử dụng loại Hash, nên chú ý những keyword của MongoDB, nếu không các giá trị sẽ không lưu trữ đúng.
        class User
          include Mongoid::Document
          field :first_name, type: String
          field :last_name, type: String
          field :full_name, type: String
          field :address, type: Hash
        end
      pry(main)> User.create first_name: "First Name", last_name: "Last Name", address: {city: "Ha Noi", country: "Viet Nam"}
      MOPED: 127.0.0.1:27017 INSERT       database=two_development collection=users documents=[{"_id"=><BSON::ObjectId:0x38280040 data=55fb70ce66726111c2000000>, "deleted_at"=>nil, "first_name"=>"First Name", "last_name"=>"Last Name", "address"=>{:city=>"Ha Noi", :country=>"Viet Nam"}, "updated_at"=>2015-09-18 02:02:54 UTC, "created_at"=>2015-09-18 02:02:54 UTC}] flags=[]
	COMMAND      database=two_development command={:getlasterror=>1, :w=>1} runtime: 0.9666ms
    => #<User _id: 55fb70ce66726111c2000000, created_at: 2015-09-18 02:02:54 UTC, updated_at: 2015-09-18 02:02:54 UTC, deleted_at: nil, first_name: "First Name", last_name: "Last Name", address: {:city=>"Ha Noi", :country=>"Viet Nam"}>
- Gía trị mặc định của field
        class User
          include Mongoid::Document
          field :first_name, type: String
          field :last_name, type: String
          field :full_name, type: String
          field :address, type: Hash
          field :age, type: Integer, default: 18
        end
	pry(main)> User.create first_name: "First Name", last_name: "Last Name", address: {city: "Ha Noi", country: "Viet Nam"}
      MOPED: 127.0.0.1:27017 INSERT       database=two_development collection=users documents=[{"_id"=><BSON::ObjectId:0x38280040 data=55fb70ce66726111c2000000>, "deleted_at"=>nil, "first_name"=>"First Name", "last_name"=>"Last Name", "address"=>{:city=>"Ha Noi", :country=>"Viet Nam"}, "updated_at"=>2015-09-18 02:02:54 UTC, "created_at"=>2015-09-18 02:02:54 UTC}] flags=[]
	COMMAND      database=two_development command={:getlasterror=>1, :w=>1} runtime: 0.9666ms
    => #<User _id: 55fb70ce66726111c2000000, created_at: 2015-09-18 02:02:54 UTC, updated_at: 2015-09-18 02:02:54 UTC, deleted_at: nil, first_name: "First Name", last_name: "Last Name", address: {:city=>"Ha Noi", :country=>"Viet Nam"}, age: 18>
- Aliasing Fields<br>
Một trong những hạn chế của việc có một cơ sở dữ liệu schema-less là MongoDB phải lưu trữ tất cả thông tin trường cùng với tất cả tài liệu, có nghĩa là nó chiếm rất nhiều không gian lưu trữ trong bộ nhớ RAM và trên đĩa nhớ. Một mô hình phổ biến để hạn chế này là sử dụng alias, trong khi vẫn giữ tên miền trong các ứng dụng. Mongoid cho phép bạn làm điều này và tìm đến các fields tương ứng với tên dài hơn trong qúa trình setter, getter.
        class User
          include Mongoid::Document
          include Mongoid::Timestamps
          include Mongoid::Paranoia

          field :first_name, type: String
          field :last_name, type: String
          field :age, type: Integer, default: 18
          field :ad, as: :address, type: String
        end
        [1] pry(main)> User.create first_name: "First name", last_name: "Last name", ad: "Ha Noi"
      MOPED: 127.0.0.1:27017 INSERT       database=two_development collection=users documents=[{"_id"=><BSON::ObjectId:0x42547800 data=5601fad366726110c2000000>, "age"=>18, "deleted_at"=>nil, "first_name"=>"First name", "last_name"=>"Last name", "ad"=>"Ha Noi", "updated_at"=>2015-09-23 01:05:23 UTC, "created_at"=>2015-09-23 01:05:23 UTC}] flags=[]
                             COMMAND      database=two_development command={:getlasterror=>1, :w=>1} runtime: 38.5406ms
    => #<User _id: 5601fad366726110c2000000, created_at: 2015-09-23 01:05:23 UTC, updated_at: 2015-09-23 01:05:23 UTC, deleted_at: nil, first_name: "First name", last_name: "Last name", age: 18, ad(address): "Ha Noi">
		pry(main)> User.last.ad
		=> "Ha Noi"
		pry(main)> User.last.address
		=> "Ha Noi"
- Custom Ids
Đối với trường hợp khi bạn không muốn có BSON::ObjectId ids, bạn có thể ghi đè lên trưòng _id Mongoid và đặt chúng vào bất cứ điều gì bạn muốn.
        class Test
          include Mongoid::Document
          include Mongoid::Timestamps
          include Mongoid::Paranoia

          field :_id, type: Integer, default: 1
          field :first_name, type: String
          field :last_name, type: String
          field :age, type: Integer, default: 18
          field :ad, as: :address, type: String
        end
- Dynamic Fields
Theo mặc định Mongoid không hỗ trợ các dynamic fields. Bạn cần phải thông báo cho Mongoid bạn muốn dynamic fields bằng cách include Mongoid::Attributes::Dynamic trong model. Mongoid::Attributes::Dynamic sẽ cho phép các thuộc tính để thiết lập và tồn tại trên documents nếu field không được xác định.
        class Mash
          include Mongoid::Document
          include Mongoid::Timestamps
          include Mongoid::Paranoia
          include Mongoid::Attributes::Dynamic
        end
Khi thực hiện với các dynamic fields ta thực hiện các luật sau:
	+ Nếu thuộc tính tồn tại trong document, Mongoid sẽ cung cấp cho bạn phương thức getter và setter chuẩn.
	pry(main)> Mash.first.user_id
	=> 11
    pry(main)> Mash.first[:user_id]
	=> 11
    pry(main)> Mash.first.read_attribute(:user_id)
    => 11
	pry(main)> Mash.first.write_attribute(:user_id, 12)
	=> 12
	pry(main)> Mash.first.user_id = 12
	=> 12
    pry(main)> Mash.first[:user_id] = 12
	=> 12
	+ Nếu các thuộc tính không tồn tại trên các document, Mongoid sẽ không cung cấp cho bạn với các getter và setter chuẩn và sẽ thông báo lỗi `method_missing`
	pry(main)> a = Mash.new
	=> #<Mash _id: 5602059e667261127e000001, created_at: nil, updated_at: nil, deleted_at: nil>
    pry(main)> a.user_id
    NoMethodError: undefined method `user_id' for #<Mash:0x0000000727ff68>
    from /home/likewise-open/FRAMGIA/bui.van.quynh/.rvm/gems/ruby-2.2.1/gems/mongoid-4.0.2/lib/mongoid/attributes/dynamic.rb:136:in `method_missing'
    pry(main)> a.user_id = 11
    NoMethodError: undefined method `user_id=' for #<Mash:0x0000000727ff68>
    from /home/likewise-open/FRAMGIA/bui.van.quynh/.rvm/gems/ruby-2.2.1/gems/mongoid-4.0.2/lib/mongoid/attributes/dynamic.rb:136:in `method_missing'
Cách getter và setter an toàn:
    pry(main)> a[:user_id]
	=> nil
    pry(main)> a.read_attribute(:user_id)
	=> nil
    pry(main)> a[:user_id] = 11
	=> 11
	pry(main)> a.write_attribute(:user_id, 11)
	=> 11
- Localized Fields
MongoId hỗ trợ các localized fields mà không cần sử dụng thêm các gem bên ngoài
		class Mash
          include Mongoid::Document
          include Mongoid::Timestamps
          include Mongoid::Paranoia

          field :message, localize: true
        end
Để sử dụng localize ta set `localize: true`, Mongoid sẽ lưu trữ nó dưới dạng hash dạng key/value
    pry(main)> I18n.default_locale = :en
	=> :en
    pry(main)> mash = Mash.new
    => #<Mash _id: 56020fd36672610817000000, created_at: nil, updated_at: nil, deleted_at: nil, message: nil>
    pry(main)> I18n.locale = :ja
	=> :ja
     pry(main)> mash.message = "新しいマッチがあります"
	=> "新しいマッチがあります"
    pry(main)> mash
    => #<Mash _id: 56020fd36672610817000000, created_at: nil, updated_at: nil, deleted_at: nil, message: {"en"=>"new match", "ja"=>"新しいマッチがあります"}>
Bạn có thể get và set tất cả các bản dịch cùng lúc bằng cách sử dụng `_translations method`
    pry(main)> mash.message_translations
	=> {"en"=>"new match", "ja"=>"新しいマッチがあります"}
	/n
    pry(main)> mash.message_translations = {en: "get new match", message: "新しいマッチがあります"}
	=> {:en=>"get new match", :ja=>"新しいマッチがあります"}
	+ Querying
    Khi ta querying của trường chứa localized sử dụng Mongoid API, Mongoid sex tự động thay đổi để lấy gía trị phù hợp với locale hiện tại.
         pry(main)> mash
    => #<Mash _id: 56020371667261127e000000, created_at: 2015-09-23 01:42:09 UTC, updated_at: 2015-09-23 03:17:50 UTC, deleted_at: nil, message: {"en"=>"new match", "ja"=>"新しいマッチがあります"}>
    	pry(main)> I18n.locale
		=> :en
		pry(main)> mash.message
		=> "new match"
        pry(main)> I18n.locale = :ja
		=> :ja
        pry(main)> mash.message
		=> "新しいマッチがあります"
    + Indexing
	Nếu bạn sẽ querying nhiều trên trường localized, bạn nên đánh index cho từng locales mà bạn sẽ tìm kiếm.
            class Mash
              include Mongoid::Document
              include Mongoid::Timestamps
              include Mongoid::Paranoia

              field :message, localize: true
              index "message.en" => 1
              index "message.ja" => 1
            end

II. Kết luận - MongoId là công cụ hiệu quả và dễ dàng sử dùng để kết nối và làm việc với mongodb của rails - Phần 2 giới thiệu tiếp về Runtime Persistence Options, Querying, Relations...

III. Tài liệu tham khảo - http://docs.mongodb.org/ecosystem/tutorial/ruby-mongoid-tutorial/ - https://rubygems.org/gems/mongoid/versions/4.0.2 - https://github.com/mongoid/mongoid


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.