12/08/2018, 12:46

Sử dụng MongoDB với gem MongoID phần II

Mongoid(tiếp) Trong phần đầu ta đã tìm hiểu 1 số khái niệm, và thuộc tính MongoID cung cấp như config, logging, storate, documents, Aliasing Fields, Localized Fields...Phần II ta sẽ tìm hiểu thêm tính chất của MongoID(Dirty Tracking, Readonly Attributes, Inheritance....) Dirty Tracking ...

  1. Mongoid(tiếp)
    • Trong phần đầu ta đã tìm hiểu 1 số khái niệm, và thuộc tính MongoID cung cấp như config, logging, storate, documents, Aliasing Fields, Localized Fields...Phần II ta sẽ tìm hiểu thêm tính chất của MongoID(Dirty Tracking, Readonly Attributes, Inheritance....)
    • Dirty Tracking
      Mongoid hỗ trợ theo dõi các thay đổi hoặc các “dirty” fields với một API thông báo cho Active Model. Nếu một field được xác định đã được sửa đổi trong một model model sẽ được đánh dấu là "dirty" và add hành vi thanh đổi đó
      • Viewing Changes
        Có nhiều cách khác nhau để xem những gì đã được thay đổi vào một model. Những thay đổi được ghi nhận từ thời gian một "document" được khởi tạo, hoặc như là một "document" mới hoặc một "document" lấy từ cơ sở dữ liệu đến thời điểm nó được lưu. Bất kỳ hoạt động thay đổi nào cũng được lưu lại
	        class User
              include Mongoid::Document
              include Mongoid::Timestamps

              field :first_name, type: String
              field :last_name, type: String
              field :age, type: Integer, default: 18
              field :address, type: String
            end
        	[4] pry(main)> user = User.new first_name: "First Name", last_name: "Last Name", address: "Ha Noi"
            => #<User _id: 561c7ae06672611263000001, created_at: nil, updated_at: nil, deleted_at: nil, first_name: "First Name", last_name: "Last Name", age: 18, address: "Ha Noi">
            [5] pry(main)> user.new_record?
			=> true

            [6] pry(main)> user = User.first
              MOPED: 127.0.0.1:27017 QUERY        database=two_development collection=users selector={"$query"=>{"deleted_at"=>nil}, "$orderby"=>{:_id=>1}} flags=[] limit=-1 skip=0 batch_size=nil fields=nil runtime: 31.1959ms
            => #<User _id: 561c7ac16672611263000000, created_at: 2015-10-13 03:30:09 UTC, updated_at: 2015-10-13 03:30:09 UTC, deleted_at: nil, first_name: "First Name", last_name: "Last Name", age: 18, address: "Ha Noi">erson

            [7] pry(main)> user.first_name
			=> "First Name"
            [8] pry(main)> user.changed?
			=> false

            [9] pry(main)> user.first_name = "First"
            => "First"
            [10] pry(main)> user.changed?
            => true

            [11] pry(main)> user.changed
            => ["first_name"]
            [12] pry(main)> user.changes
            => {"first_name"=>["First Name", "First"]}

            [13] pry(main)> user.first_name_changed?
            => true
            [14] pry(main)> user.first_name_change
            => ["First Name", "First"]

            [15] pry(main)> user.first_name_was
			=> "First Name"
	+ Resetting Changes<br>
	Bạn có thể thiết lập lại các thay đổi của một fields với giá trị trước đó của bằng cách gọi phương thức thiết lập lại.
        [1] pry(main)> user = User.first
          MOPED: 127.0.0.1:27017 QUERY        database=two_development collection=users selector={"$query"=>{"deleted_at"=>nil}, "$orderby"=>{:_id=>1}} flags=[] limit=-1 skip=0 batch_size=nil fields=nil runtime: 0.8094ms
        => #<User _id: 561c7ac16672611263000000, created_at: 2015-10-13 03:30:09 UTC, updated_at: 2015-10-13 03:30:09 UTC, deleted_at: nil, first_name: "First Name", last_name: "Last Name", age: 18, ad(address): "Ha Noi">
		[2] pry(main)> user.first_name
		=> "First Name"
        [3] pry(main)> user.first_name = "Test"
        => "Test"
        [4] pry(main)> user.reset_first_name!
        => "First Name"
        [5] pry(main)> user.first_name
        => "First Name"
    + Viewing Previous Changes
	Sau khi một document đã được tồn tại, bạn có thể xem những gì các thay đổi này trước đó bằng cách gọi ``Model#previous_changes``.
        [1] pry(main)> user = User.first
          MOPED: 127.0.0.1:27017 QUERY        database=two_development collection=users selector={"$query"=>{"deleted_at"=>nil}, "$orderby"=>{:_id=>1}} flags=[] limit=-1 skip=0 batch_size=nil fields=nil runtime: 104.2828ms
        => #<User _id: 561c7ac16672611263000000, created_at: 2015-10-13 03:30:09 UTC, updated_at: 2015-10-13 03:30:09 UTC, deleted_at: nil, first_name: "First Name", last_name: "Last Name", age: 18, ad(address): "Ha Noi">
        [2] pry(main)> user.first_name
        => "First Name"
        [3] pry(main)> user.first_name = "First"
        => "First"
        [4] pry(main)> user.save
          MOPED: 127.0.0.1:27017 UPDATE       database=two_development collection=users selector={"_id"=><BSON::ObjectId:0x30860780 data=561c7ac16672611263000000>} update={"$set"=>{"first_name"=>"First", "updated_at"=>2015-10-14 02:05:34 UTC}} flags=[]
                                 COMMAND      database=two_development command={:getlasterror=>1, :w=>1} runtime: 0.6033ms
        => true
        [5] pry(main)> user.previous_changes
        => {"first_name"=>["First Name", "First"], "updated_at"=>[2015-10-13 03:30:09 UTC, 2015-10-14 02:05:34 UTC]}
    + Readonly Attributes<br>
    Bạn có khai báo trong Mongoid các thuộc tính nhất định chỉ có thể đọc. Điều này sẽ cho phép các document được tạo ra các thuộc tính ,nhưng thuộc tính thay đổi nếu ta dùng bộ lọc theo field .
            class User
              include Mongoid::Document
              include Mongoid::Timestamps

              field :first_name, type: String
              field :last_name, type: String
              field :age, type: Integer, default: 18
              field :address, type: String

              attr_readonly :age
            end
            [1] pry(main)> user = User.create first_name:  "First Name", last_name: "Last Name"
                MOPED: 127.0.0.1:27017 INSERT       database=two_development collection=users documents=[{"_id"=><BSON::ObjectId:0x41889580 data=561dbe2266726110a6000000>, "age"=>18, "deleted_at"=>nil, "first_name"=>"First Name", "last_name"=>"Last Name", "updated_at"=>2015-10-14 02:29:54 UTC, "created_at"=>2015-10-14 02:29:54 UTC}] flags=[]
                                     COMMAND      database=two_development command={:getlasterror=>1, :w=>1} runtime: 1.0718ms
            => #<User _id: 561dbe2266726110a6000000, created_at: 2015-10-14 02:29:54 UTC, updated_at: 2015-10-14 02:29:54 UTC, deleted_at: nil, first_name: "First Name", last_name: "Last Name", age: 18, address: nil>

            [3] pry(main)> user.update_attributes age: 20
            => true

            [4] pry(main)> user.update_attribute :age, 20
            Mongoid::Errors::ReadonlyAttribute:
            Problem:
              Attempted to set the readonly attribute 'age' with the value: 20.
            Summary:
              Attributes flagged as readonly via Model.attr_readonly can only have values set when the document is a new record.
            Resolution:
              Don't define 'age' as readonly, or do not attempt to update its value after the document is persisted.
            from /home/likewise-open/FRAMGIA/bui.van.quynh/.rvm/gems/ruby-2.2.1/gems/mongoid-4.0.2/lib/mongoid/persistable/updatable.rb:29:in `update_attribute'

            [6] pry(main)> user.remove_attribute(:age)
            Mongoid::Errors::ReadonlyAttribute:
            Problem:
              Attempted to set the readonly attribute 'age' with the value: nil.
            Summary:
              Attributes flagged as readonly via Model.attr_readonly can only have values set when the document is a new record.
            Resolution:
              Don't define 'age' as readonly, or do not attempt to update its value after the document is persisted.
            from /home/likewise-open/FRAMGIA/bui.van.quynh/.rvm/gems/ruby-2.2.1/gems/mongoid-4.0.2/lib/mongoid/attributes.rb:144:in `remove_attribute'
   	+ Inheritance<br>
   	 Mongoid hỗ trợ thừa kế trong cả root và embedded documents. Khi lớp con thừa kế lớp cha thì tất cả  fields, relations, validations và scopes của lớp cha được sao chép cho lớp con, nhưng lớp cha thi không sao chép được các thuộc tính của lớp con<br>
     `models/document.rb`
            class Document
              include Mongoid::Document
              include Mongoid::Timestamps

              field :user_id, type: Integer
              field :title, type: String
              field :context, type: String
            end
     `models/character.rb`
            class Character < Document
              include Mongoid::Document
              include Mongoid::Timestamps

              field :description, type: String
              field :is_public, type: Boolean
              field :age, type: Integer
              embedded_in :sample
            end
     `models/sample.rb`
            class Sample
              include Mongoid::Document
              include Mongoid::Timestamps

              embeds_many :characters
            end
         [1] pry(main)> Document.count
  MOPED: 127.0.0.1:27017 COMMAND      database=admin command={:ismaster=>1} runtime: 0.7978ms
  MOPED: 127.0.0.1:27017 COMMAND      database=two_development command={:count=>"documents", :query=>{}} runtime: 0.3035ms
=> 2
[2] pry(main)> Character.count
Creating scope :page. Overwriting existing method Character.page.
  MOPED: 127.0.0.1:27017 COMMAND      database=two_development command={:count=>"documents", :query=>{"_type"=>{"$in"=>["Character"]}}} runtime: 0.4404ms
=> 1
[3] pry(main)> Document.first
  MOPED: 127.0.0.1:27017 QUERY        database=two_development collection=documents selector={"$query"=>{}, "$orderby"=>{:_id=>1}} flags=[] limit=-1 skip=0 batch_size=nil fields=nil runtime: 1.0805ms
=> #<Document _id: 562d8e846672611092000000, created_at: 2015-10-26 02:23:00 UTC, updated_at: 2015-10-26 02:23:00 UTC, user_id: 1, title: "Document test", context: "You should be able to start your Rails app now", _type: "Document">
[4] pry(main)> Document.last
  MOPED: 127.0.0.1:27017 QUERY        database=two_development collection=documents selector={"$query"=>{}, "$orderby"=>{:_id=>-1}} flags=[] limit=-1 skip=0 batch_size=nil fields=nil runtime: 0.5356ms
=> #<Character _id: 562d8efe6672611092000001, created_at: 2015-10-26 02:25:02 UTC, updated_at: 2015-10-26 02:25:02 UTC, user_id: 1, title: "Character test", context: "You should be able to start your Rails app now", _type: "Character", description: "description", is_public: true, age: 20>
[5] pry(main)> Character.last
  MOPED: 127.0.0.1:27017 QUERY        database=two_development collection=documents selector={"$query"=>{"_type"=>{"$in"=>["Character"]}}, "$orderby"=>{:_id=>-1}} flags=[] limit=-1 skip=0 batch_size=nil fields=nil runtime: 1.0469ms
=> #<Character _id: 562d8efe6672611092000001, created_at: 2015-10-26 02:25:02 UTC, updated_at: 2015-10-26 02:25:02 UTC, user_id: 1, title: "Character test", context: "You should be able to start your Rails app now", _type: "Character", description: "description", is_public: true, age: 20>
	+ Associations<br>
	Bạn có thể thêm bất kỳ subclass để có một hoặc có nhiều association, hoặc thông qua thiết lập bình thường hoặc thông qua việc `build` và `create` trên association
            [6] pry(main)> sample = Sample.new
=> #<Sample _id: 562d962966726111f8000000, created_at: nil, updated_at: nil>
[7] pry(main)> sample.characters.build({user_id: 1, context: "context", is_public: true, age: 20})
=> #<Character _id: 562d965366726111f8000001, created_at: nil, updated_at: nil, user_id: 1, title: nil, context: "context", _type: "Character", description: nil, is_public: true, age: 20>
[8] pry(main)> sample.characters
=> [#<Character _id: 562d965366726111f8000001, created_at: nil, updated_at: nil, user_id: 1, title: nil, context: "context", _type: "Character", description: nil, is_public: true, age: 20>]
    + Timestamping<br>
    Mongoid cung cấp một module timestamping là Mongoid :: Timestamps thông qua 2 trường là `created_at` và "updated_at"
            class User
              include Mongoid::Document
              include Mongoid::Timestamps

              field :first_name, type: String
              field :last_name, type: String
              field :age, type: Integer, default: 18
              field :address, type: String
            end
    Ta cũng có thể chỉ chọn lưu timestamping cụ thể khi `create` hay `update`
            class Sample
              include Mongoid::Document
              include Mongoid::Timestamps::Created

              embeds_many :characters
            end
            class Document
              include Mongoid::Document
              include Mongoid::Timestamps::Updated
            end
    Nếu bạn muốn tắt timestamping cho specific calls cụ thể, sử dụng các phương thức timeless:
    [2] pry(main)> Document.timeless.create user_id: 1, title: "Title", context: "Context"
      MOPED: 127.0.0.1:27017 INSERT       database=two_development collection=documents documents=[{"_id"=><BSON::ObjectId:0x45452540 data=562d9a5f66726112fe000001>, "user_id"=>1, "title"=>"Title", "context"=>"Context"}] flags=[]
                             COMMAND      database=two_development command={:getlasterror=>1, :w=>1} runtime: 0.4057ms
    => #<Document _id: 562d9a5f66726112fe000001, created_at: nil, updated_at: nil, user_id: 1, title: "Title", context: "Context">
- Persistence
Mongoid hỗ trợ tất cả các thao tác CRUD quen thuộc với người dùng như 1 số Ruby mappers khác như Active Record hoặc dữ Data Mapper.

Mongoid là 1 mapper khác cho MongoDB là các hoạt động bền vững nói chung thực hiện cập nhật vào trường nào thay đổi, thay vì viết toàn bộ document của database mỗi lần. + Standard
Mongoid có phương thức bền vững chuẩn trong cấu trúc của phương pháp phổ biến mà bạn sẽ tìm thấy trong các mapping frameworks. Bảng dưới đây cho thấy tất cả các phương thức chuẩn: + Model.create thêm mới 1 hoặc nhiều documents vào database + Model.create! thêm mới 1 hoặc nhiều documents vào database, trả ra lỗi nếu như xảy ra lỗi + Model#save Lưu lại các trường bị thay đổi hoặc thêm mới documents nếu như documents đấy là tạo mới. Có thể pass validations nếu bạn muốn. + Model#save! Tương tự như Model#save nhưng sẽ trả ra lỗi nếu như có lỗi xảy ra + Model#update_attributes cập nhật các thuộc tính của model + Model#update_attribute cập nhật 1 trường của model và bỏ qua validatios + Model#upsert Thực hiện một upsert MongoDB trên document. Nếu document tồn tại trong cơ sở dữ liệu, nó sẽ được ghi đè với các thuộc tính hiện tại của documents trong bộ nhớ. Nếu document không tồn tại trong cơ sở dữ liệu, nó sẽ được chèn vào. Lưu ý rằng điều này chỉ chạy {before|after|around}_upsert callbacks. + Model#touch Cập nhật updated_at của document, tùy chọn với một fild cung cấp thời gian. Điều này sẽ liên lạc với tất cả các mối quan hệ belongs_to của document với các tùy chọn thiết lập. Thao tác này sẽ bỏ qua validations và callbacks. + Model#delete Xóa document từ cơ sở dữ liệu mà không cần chạy callbacks + Model.delete_all Xóa tất cả documents trong khi chạy callbacks - Querying Một trong những tính năng lớn nhất MongoDB là khả năng để thực hiện truy vấn động. Tất cả các queries trong Mongoid là Mongoid::Criteria + Additional Query Methods + Criteria#count Đếm số documents tồn tại trong database

            [1]pry(main)> User.count
      MOPED: 127.0.0.1:27017 COMMAND      database=two_development command={:count=>"users", :query=>{"deleted_at"=>nil}} runtime: 67.9965ms
    => 1
       + `Criteria#distinct` lấy được một danh sách các giá trị khác biệt cho một fields duy nhất.
           [4] pry(main)> Test.distinct(:first_name)
  MOPED: 127.0.0.1:27017 COMMAND      database=two_development command={:distinct=>"tests", :key=>"first_name", :query=>{"deleted_at"=>nil}} runtime: 1.0248ms
=> ["First Name"]
       + `Criteria#each` Lặp qua tất cả các tài liệu phù hợp trong các điều kiện.
           	[2] pry(main)> User.where(age: 18).each do |user|
            [2] pry(main)*   puts user.first_name
            [2] pry(main)* end
              MOPED: 127.0.0.1:27017 QUERY        database=two_development collection=users selector={"deleted_at"=>nil, "age"=>18} flags=[] limit=0 skip=0 batch_size=nil fields=nil runtime: 2.7335ms
            First Name
            Winfield Bartell
            Noemy Romaguera
            Dashawn Gislason DVM
            Spencer Grimes
            Rachael Kreiger Jr.
            Tania Lebsack
            Johnnie Grady
            Lila Hyatt
            Raymond Langosh V
            Corene Kuhic
	  + `Criteria#exists?` Xác định nếu bất kỳ document phù hợp tồn tại. Sẽ trả về true nếu có 1 hoặc nhiều hơn.
          [3] pry(main)> User.where(age: 18).exists?
  MOPED: 127.0.0.1:27017 QUERY        database=two_development collection=users selector={"deleted_at"=>nil, "age"=>18} flags=[] limit=-1 skip=0 batch_size=nil fields={:_id=>1} runtime: 0.9913ms
=> true
		  [4] pry(main)> User.where(age: 19).exists?
  MOPED: 127.0.0.1:27017 COMMAND      database=admin command={:ismaster=>1} runtime: 0.4003ms
  MOPED: 127.0.0.1:27017 QUERY        database=two_development collection=users selector={"deleted_at"=>nil, "age"=>19} flags=[] limit=-1 skip=0 batch_size=nil fields={:_id=>1} runtime: 0.2926ms
=> false
      + `Criteria#find` Tìm documents bằng id. Sẽ trả lỗi theo mặc định nếu bất kỳ của các id không khớp.
          [6] pry(main)> User.find(id: 123)
  MOPED: 127.0.0.1:27017 QUERY        database=two_development collection=users selector={"deleted_at"=>nil, "_id"=>{:id=>123}} flags=[] limit=0 skip=0 batch_size=nil fields=nil runtime: 0.9115ms
Mongoid::Errors::DocumentNotFound
      + `Criteria#find_by` Tìm documnets theo điều kiện, trả ra lỗi mặc định nếu không tìm thấy documents nào phù hợp
          [2] pry(main)> Test.find_by age: 18
  MOPED: 127.0.0.1:27017 QUERY        database=two_development collection=tests selector={"deleted_at"=>nil, "age"=>18} flags=[] limit=-1 skip=0 batch_size=nil fields=nil runtime: 1.0014ms
=> #<Test _id: 561dbe2266726110a6000000, created_at: 2015-10-14 02:29:54 UTC, updated_at: 2015-10-14 02:29:54 UTC, deleted_at: nil, first_name: "First Name", last_name: "Last Name", age: 18, address: nil>
      + `Criteria#find_or_create_by` Tìm một document bằng các thuộc tính cung cấp, và nếu không tìm thấy tạo mới và trả về document vừa tạo mới đã tồn tại
      + `Criteria#find_or_initialize_by` Tìm document đầu tiên bởi các thuộc tính cung cấp, và nếu không tìm thấy trả lại một document mới.
      + `Criteria#length|size` Tương tự câu lệnh `count`
      + `Criteria#pluck` lấy toàn bộ gía trị không `nil` cho thuộc tính cung cấp.
  1. Kết luận
    • Ta có thể thấy MongoID có nhiều điểm chung với ActiveRecord, phần sau ta sẽ tìm hiểu tiếp về Atomic, Scoping,Eager Loading, Relations...
0