12/08/2018, 13:03

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

Mongoid(tiếp) Eager Loading Mongoid cung cấp một cơ sở để eager load documents từ các mối quan hệ để ngăn chặn các trường hợp n + 1 khi lặp lại quuery với các mối quan hệ. Eager load được hỗ trợ trên tất cả các mối quan hệ với các trường hợp ngoại lệ belongs_to associations. class ...

  1. Mongoid(tiếp)
  • Eager Loading
    Mongoid cung cấp một cơ sở để eager load documents từ các mối quan hệ để ngăn chặn các trường hợp n + 1 khi lặp lại quuery với các mối quan hệ. Eager load được hỗ trợ trên tất cả các mối quan hệ với các trường hợp ngoại lệ belongs_to associations.
        class User
          include Mongoid::Document
          include Mongoid::Timestamps
          include Mongoid::Paranoia
          include Mongoid::Attributes::Dynamic

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

          has_many :books
        end
      class Book
          include Mongoid::Document
          include Mongoid::Timestamps
          include Mongoid::Paranoia
          include Mongoid::Attributes::Dynamic

          field :name, type: String
          field :author, type: String
          field :description, type: String
          field :uesr_id, type: Integer

          belongs_to :user
        end
        [1] pry(main)> User.includes(:books).each do |user|
        [1] pry(main)*   p user.books.first.name
        [1] pry(main)* end
          MOPED: 127.0.0.1:27017 QUERY        database=test_development collection=users selector={"deleted_at"=>nil} flags=[] limit=0 skip=0 batch_size=nil fields=nil runtime: 0.8939ms
          MOPED: 127.0.0.1:27017 QUERY        database=test_development collection=books selector={"deleted_at"=>nil, "user_id"=>{"$in"=>[<BSON::ObjectId:0x34726100 data=5653bead667261191d000000>, <BSON::ObjectId:0x34673880 data=5653bf156672611948000000>, <BSON::ObjectId:0x34671520 data=5653bf186672611948000001>, <BSON::ObjectId:0x34668580 data=5653bf186672611948000002>, <BSON::ObjectId:0x34657020 data=5653bf1c6672611948000003>]}} flags=[] limit=0 skip=0 batch_size=nil fields=nil runtime: 1.0358ms
        "Dolorem quod libero possimus ut dolore."
        "Est id porro incidunt quod consequatur illo."
        "Quia debitis qui quis fugit omnis ipsa distinctio."
        "Odio nemo ab vero dolores neque in rerum quisquam."
        "Iusto dolores enim aut aperiam tenetur sint."
        => #<Mongoid::Contextual::Mongo:0x00000004240910
         @cache=nil,
         @cache_loaded=true,
         @collection=
          #<Moped::Collection:0x0000000423f650
           @database=
            #<Moped::Database:0x0000000423f6a0
             @name="test_development",
             @session=<Moped::Session seeds=[<Moped::Node resolved_address="127.0.0.1:27017">] database=test_development>>,
           @name="users">,
         @criteria=#<Mongoid::Criteria
          selector: {"deleted_at"=>nil}
          options:  {}
          class:    User
          embedded: false>
        ,
         @eager_loaded=true,
         @klass=User,
         @query=
          #<Moped::Query:0x0000000423f510
           @collection=
            #<Moped::Collection:0x0000000423f650
             @database=
              #<Moped::Database:0x0000000423f6a0
               @name="test_development",
               @session=<Moped::Session seeds=[<Moped::Node resolved_address="127.0.0.1:27017">] database=test_development>>,
             @name="users">,
           @operation=
            #<Moped::Protocol::Query
          @length=0
          @request_id=0
          @response_to=0
          @op_code=2004
          @flags=[]
          @full_collection_name="test_development.tests"
          @skip=0
          @limit=0
          @selector={"deleted_at"=>nil}
          @fields=nil>,
           @selector={"deleted_at"=>nil}>>
  • Queries + Persistence
    Mongoid hỗ trợ hiệu qủa bền vững khi bạn muốn thực hiện insert document, update, và delete.
    • Criteria#create Tạo mới document
                [4] pry(main)> User.where(first_name: "Test").create
          MOPED: 127.0.0.1:27017 INSERT       database=test_development collection=users documents=[{"_id"=><BSON::ObjectId:0x12432120 data=5653c34866726119ab000001>, "age"=>18, "deleted_at"=>nil, "first_name"=>"Test", "updated_at"=>2015-11-24 01:54:16 UTC, "created_at"=>2015-11-24 01:54:16 UTC}] flags=[]
                                 COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 0.9733ms
        => #<User _id: 5653c34866726119ab000001, created_at: 2015-11-24 01:54:16 UTC, updated_at: 2015-11-24 01:54:16 UTC, deleted_at: nil, first_name: "Test", last_name: nil, age: 18, address: nil>
  + `Criteria#create!`
  Tạo mới document và trả ra lỗi nếu validation là failure
                [6] pry(main)> User.where(first_name: "Test").create!
        Mongoid::Errors::Validations:
        Problem:
          Validation of User failed.
        Summary:
          The following errors were found: Last name can't be blank
        Resolutuby
          Try persisting the document with valid data or remove the validations.
        from /home/likewise-open/FRAMGIA/bui.van.quynh/.rvm/gems/ruby-2.2.1/gems/mongoid-4.0.2/lib/mongoid/persistable.rb:78:in `fail_due_to_validation!
+ `Criteria#build|new`
Tạo mới document nhưng không save
                [7] pry(main)> User.where(first_name: "Test").new
        => #<User _id: 5653c6226672611c9c000001, created_at: nil, updated_at: nil, deleted_at: nil, first_name: "Test", last_name: nil, age: 18, address: nil>
        [8] pry(main)> User.where(first_name: "Test").build
        => #<User _id: 5653c6266672611c9c000002, created_at: nil, updated_at: nil, deleted_at: nil, first_name: "Test", last_name: nil, age: 18, address: nil>
    + `Criteria#update`
    Update attributes của đối tượng đầu tiên matching
            [9] pry(main)> User.where(first_name: "Test").update last_name: "Last Name"
      MOPED: 127.0.0.1:27017 COMMAND      database=admin command={:ismaster=>1} runtime: 1.4813ms
      MOPED: 127.0.0.1:27017 UPDATE       database=test_development collection=users selector={"deleted_at"=>nil, "first_name"=>"Test"} update={"$set"=>{"last_name"=>"Last Name"}} flags=[]
                             COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 0.9015ms
    => {"connectionId"=>48, "updatedExisting"=>true, "n"=>1, "syncMillis"=>0, "writtenTo"=>nil, "err"=>nil, "ok"=>1.0}
    + `Criteria#update_all`
    Update attributes tất cả đối tượng matching
            [12] pry(main)> User.where(first_name: "Test").update_all last_name: "Last Name"
      MOPED: 127.0.0.1:27017 UPDATE       database=test_development collection=users selector={"deleted_at"=>nil, "first_name"=>"Test"} update={"$set"=>{"last_name"=>"Last Name"}} flags=[:multi]
                             COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 0.7618ms
    => {"connectionId"=>48, "updatedExisting"=>true, "n"=>2, "syncMillis"=>0, "writtenTo"=>nil, "err"=>nil, "ok"=>1.0}
    + `Criteria#add_to_set`: Thực hiện `$addToSet` tất cả documents matching (attributes là dạng array)
        [1] pry(main)> User.where(first_name: "Test").add_to_set address: "Address"
  MOPED: 127.0.0.1:27017 UPDATE       database=test_development collection=users selector={"deleted_at"=>nil, "first_name"=>"Test"} update={"$addToSet"=>{"address"=>"Address"}} flags=[:multi]
                         COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 0.8743ms
=> {"connectionId"=>53, "updatedExisting"=>true, "n"=>2, "syncMillis"=>0, "writtenTo"=>nil, "err"=>nil, "ok"=>1.0}
    + `Criteria#pop`: Thực hiện `$pop` với tất cả document matching(chỉ dùng với adtributes kiểu array)
        [4] pry(main)> User.where(first_name: "Test").pop address: "Address Test"
  MOPED: 127.0.0.1:27017 UPDATE       database=test_development collection=users selector={"deleted_at"=>nil, "first_name"=>"Test"} update={"$pop"=>{"address"=>"Address Test"}} flags=[:multi]
                         COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 1.0798ms
=> {"connectionId"=>53, "updatedExisting"=>true, "n"=>2, "syncMillis"=>0, "writtenTo"=>nil, "err"=>nil, "ok"=>1.0}
    + `Criteria#pull`: Thực hiện `$pull` với tất cả document matching
        [5] pry(main)> User.where(first_name: "Test").pull address: "Address Test"
  MOPED: 127.0.0.1:27017 COMMAND      database=admin command={:ismaster=>1} runtime: 0.6608ms
  MOPED: 127.0.0.1:27017 UPDATE       database=test_development collection=users selector={"deleted_at"=>nil, "first_name"=>"Test"} update={"$pull"=>{"address"=>"Address Test"}} flags=[:multi]
                         COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 0.5487ms
=> {"connectionId"=>53, "updatedExisting"=>true, "n"=>2, "syncMillis"=>0, "writtenTo"=>nil, "err"=>nil, "ok"=>1.0}
    + `Criteria#pull_all`: Thực hiện `$pullAll` tất cả document matching
        [33] pry(main)> User.where(first_name: "Test").pull_all(address: ["Address", "Address Test"])
  MOPED: 127.0.0.1:27017 UPDATE       database=test_development collection=users selector={"deleted_at"=>nil, "first_name"=>"Test"} update={"$pullAll"=>{"address"=>["Address", "Address Test"]}} flags=[:multi]
                         COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 1.2285ms
=> {"connectionId"=>53, "updatedExisting"=>true, "n"=>2, "syncMillis"=>0, "writtenTo"=>nil, "err"=>nil, "ok"=>1.0}
    + `Criteria#push`: Thực hiện `$push` tất cả document matching
        [30] pry(main)> User.where(first_name: "Test").push(address: "Adress")
  MOPED: 127.0.0.1:27017 UPDATE       database=test_development collection=users selector={"deleted_at"=>nil, "first_name"=>"Test"} update={"$push"=>{"address"=>"Adress"}} flags=[:multi]
                         COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 1.2555ms
=> {"connectionId"=>53, "updatedExisting"=>true, "n"=>2, "syncMillis"=>0, "writtenTo"=>nil, "err"=>nil, "ok"=>1.0}
    + `Criteria#push_all`: Thực hiện `$pushAll` với tất cả documents matching
        [32] pry(main)> User.where(first_name: "Test").push_all(address: ["Adress", "test"])
  MOPED: 127.0.0.1:27017 COMMAND      database=admin command={:ismaster=>1} runtime: 0.5647ms
  MOPED: 127.0.0.1:27017 UPDATE       database=test_development collection=users selector={"deleted_at"=>nil, "first_name"=>"Test"} update={"$pushAll"=>{"address"=>["Adress", "test"]}} flags=[:multi]
                         COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 0.5213ms
=> {"connectionId"=>53, "updatedExisting"=>true, "n"=>2, "syncMillis"=>0, "writtenTo"=>nil, "err"=>nil, "ok"=>1.0}
    + `Criteria#rename`: Thực hiện `$rename` với tất cả documents matching
        [37] pry(main)> User.where(first_name: "Test").rename(first_name: :name)
  MOPED: 127.0.0.1:27017 COMMAND      database=admin command={:ismaster=>1} runtime: 0.3173ms
  MOPED: 127.0.0.1:27017 UPDATE       database=test_development collection=users selector={"deleted_at"=>nil, "first_name"=>"Test"} update={"$rename"=>{"first_name"=>"name"}} flags=[:multi]
                         COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 0.3785ms
=> {"connectionId"=>53, "updatedExisting"=>true, "n"=>2, "syncMillis"=>0, "writtenTo"=>nil, "err"=>nil, "ok"=>1.0}
    + `Criteria#set`: Thực hiện `$set` với tất cả document matching
        [37] pry(main)> User.where(first_name: "Test").set(age: 20)
  MOPED: 127.0.0.1:27017 COMMAND      database=admin command={:ismaster=>1} runtime: 0.7750ms
  MOPED: 127.0.0.1:27017 UPDATE       database=test_development collection=users selector={"deleted_at"=>nil, "first_name"=>"Test"} update={"$set"=>{"age"=>20}} flags=[:multi]
                         COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 0.5255ms
=> {"connectionId"=>53, "updatedExisting"=>true, "n"=>3, "syncMillis"=>0, "writtenTo"=>nil, "err"=>nil, "ok"=>1.0}
    + `Criteria#unset`: Thực hiện `$unset` với tất cả documents matching
        [38] pry(main)> User.where(first_name: "Test").unset(:age)
  MOPED: 127.0.0.1:27017 UPDATE       database=test_development collection=users selector={"deleted_at"=>nil, "first_name"=>"Test"} update={"$unset"=>{"age"=>true}} flags=[:multi]
                         COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 1.1009ms
=> {"connectionId"=>53, "updatedExisting"=>true, "n"=>3, "syncMillis"=>0, "writtenTo"=>nil, "err"=>nil, "ok"=>1.0}
    + `Criteria#delete`: Thực hiện delete tất cả documents matching
        [39] pry(main)> User.where(first_name: "Test").delete
  MOPED: 127.0.0.1:27017 COMMAND      database=test_development command={:count=>"users", :query=>{"deleted_at"=>nil, "first_name"=>"Test"}} runtime: 0.3828ms
  MOPED: 127.0.0.1:27017 DELETE       database=test_development collection=tests selector={"deleted_at"=>nil, "first_name"=>"Test"} flags=[]
                         COMMAND      database=test_development command={:getlasterror=>1, :w=>1} runtime: 0.3049ms
=> 3
    + `Criteria#destroy`: Xóa tất cả documents phù hợp trong cơ sở dữ liệu trong khi chạy callbacks cho tất cả. Điều này tải tất cả tài liệu vào bộ nhớ và có thể là một hoạt động tốn kém.
        [40] pry(main)> User.where(first_name: "Test").destroy
      MOPED: 127.0.0.1:27017 COMMAND      database=admin command={:ismaster=>1} runtime: 0.3382ms
      MOPED: 127.0.0.1:27017 COMMAND      database=test_development command={:count=>"users", :query=>{"deleted_at"=>nil, "first_name"=>"Test"}} runtime: 0.3359ms
      MOPED: 127.0.0.1:27017 QUERY        database=test_development collection=tests selector={"deleted_at"=>nil, "first_name"=>"Test"} flags=[] limit=0 skip=0 batch_size=nil fields=nil runtime: 0.1932ms
    => 2
  • Scoping
    • Scope cung cấp 1 cách thuận tiện để tái sử dụng cú pháp ở nhiều nơi
      • Named Scopes: Named Scopes là tiêu chí quy định tại lớp được tham chiếu bởi một tên đã cung cấp.
            class Book
              include Mongoid::Document
              include Mongoid::Timestamps
              include Mongoid::Paranoia
              include Mongoid::Attributes::Dynamic

              field :name, type: String
              field :author, type: String
              field :description, type: String
              field :uesr_id, type: Integer
              field :country, type: String

              belongs_to :test

              scope :english_books, ->{where country: "en"}
            end
        [16] pry(main)> Book.english_books
        => #<Mongoid::Criteria
          selector: {"deleted_at"=>nil, "country"=>"en"}
          options:  {}
          class:    Book
          embedded: false>
        [17] pry(main)> Book.english_books.count
          MOPED: 127.0.0.1:27017 COMMAND      database=test_development command={:count=>"books", :query=>{"deleted_at"=>nil, "country"=>"en"}} runtime: 1.1498ms
        => 12
    + `Named scopes` có thể mở rộng thêm các tham số và có thể nằm trong blocks hoặc mở rộng chức năng
            class Book
              include Mongoid::Document
              include Mongoid::Timestamps
              include Mongoid::Paranoia
              include Mongoid::Attributes::Dynamic

              field :name, type: String
              field :author, type: String
              field :description, type: String
              field :uesr_id, type: Integer
              field :country, type: String

              belongs_to :test

              scope :english_books, ->{where country: "en"}
              scope :by_country, ->country {where country: country}
              scope :jp_books, -> do
                where country: "jp"
              end
            end
            [1] pry(main)> Book.by_country("en")
            => #<Mongoid::Criteria
              selector: {"deleted_at"=>nil, "country"=>"jp"}
              options:  {}
              class:    Book
              embedded: false>

            [2] pry(main)> Book.by_country("en").count
              MOPED: 127.0.0.1:27017 COMMAND      database=test_development command={:count=>"books", :query=>{"deleted_at"=>nil, "country"=>"jp"}} runtime: 1.7488ms
            => 12
            [3] pry(main)> Book.jp_books
            => #<Mongoid::Criteria
              selector: {"deleted_at"=>nil, "country"=>"jp"}
              options:  {}
              class:    Book
              embedded: false>

            [4] pry(main)> Book.jp_books.count
              MOPED: 127.0.0.1:27017 COMMAND      database=test_development command={:count=>"books", :query=>{"deleted_at"=>nil, "country"=>"jp"}} runtime: 0.9492ms
            => 12
+ Default Scopes
 `Default scopes `có thể hữu ích khi bạn thấy mình áp dụng cùng một tiêu chuẩn cho hầu hết các truy vấn.
             class Book
              include Mongoid::Document
              include Mongoid::Timestamps
              include Mongoid::Paranoia
              include Mongoid::Attributes::Dynamic

              field :name, type: String
              field :author, type: String
              field :description, type: String
              field :uesr_id, type: Integer
              field :country, type: String

              belongs_to :test

              scope :english_books, ->{where country: "en"}
              scope :by_country, ->country {where country: country}
              scope :jp_books, -> do
                where country: "jp"
              end

              default_scope ->{order_by(:created_at.desc)}
            end
        [4] pry(main)> Book.jp_books
        => #<Mongoid::Criteria
          selector: {"deleted_at"=>nil, "country"=>"jp"}
          options:  {:sort=>{"created_at"=>-1}}
          class:    Book
          embedded: false>
    + Bạn có thể nói với Mongoid không áp dụng `default_scope` bằng cách sử dụng `unscoped` có thể trong 1 dòng hoặc 1 khối.
       [5] pry(main)> Book.unscoped.jp_books
        => #<Mongoid::Criteria
          selector: {"country"=>"jp"}
          options:  {}
          class:    Book
          embedded: false>
   + Bạn có thể nói với Mongoid 1 cách rõ ràng khi nào sử dụng `default_scope` bằng cách sử dụng `scoped`
       [6] pry(main)> Book.unscoped.jp_books.scoped
        => #<Mongoid::Criteria
          selector: {"country"=>"jp", "deleted_at"=>nil}
          options:  {:sort=>{"created_at"=>-1}}
          class:    Book
          embedded: false>
    + Class Methods
`Class Method` trên `models` trả về các đối tượng , nó cũng được coi như `scoped` và có thể ràng buộc tốt.
          class Book
            include Mongoid::Document
            include Mongoid::Timestamps
            include Mongoid::Paranoia
            include Mongoid::Attributes::Dynamic

            field :name, type: String
            field :author, type: String
            field :description, type: String
            field :uesr_id, type: Integer
            field :country, type: String

            belongs_to :test

            scope :english_books, ->{where country: "en"}
            scope :by_country, ->country {where country: country}
            scope :jp_books, -> do
              where country: "en"
            end

            default_scope ->{order_by(:created_at.desc)}

            class << self
              def us_books
                where country: "us"
              end
            end
          end
        [2] pry(main)> Book.us_books
        => #<Mongoid::Criteria
          selector: {"deleted_at"=>nil, "country"=>"us"}
          options:  {:sort=>{"created_at"=>-1}}
          class:    Book
          embedded: false>
    + Map/Reduce
    Mongoid cung cấp một DSL xung quanh MongoDB’s map/reduce framework, để thực hiện các công việc map/reduce hoặc tập hợp đơn giản.
      + Execution:
        Bạn có thể khai báo `Mongoid` ra khỏi `class` hoặc một tiêu chí để thực hiện một `map/reduce` bằng cách gọi `map_reduce` và cung cấp `map` và `reduce` cho javascript functions.
  • Relation

    • Common Behaviour + Attributes: Tất cả relations đều chứa target, đó là những proxied document or documents, base là document của relation nguồn.
              class Test
                  include Mongoid::Document
                  include Mongoid::Timestamps
                  include Mongoid::Paranoia
                  include Mongoid::Attributes::Dynamic

                  field :first_name, type: String
                  field :last_name, type: String
                  field :age, type: Integer, default: 18
                  field :address, type: Array
                  validates :last_name, presence: true

                  has_many :books
         end
            [9] pry(main)> User.first.books
          MOPED: 127.0.0.1:27017 QUERY        database=test_development collection=users selector={"$query"=>{"deleted_at"=>nil}, "$orderby"=>{:_id=>1}} flags=[] limit=-1 skip=0 batch_size=nil fields=nil runtime: 1.1196ms
          MOPED: 127.0.0.1:27017 QUERY        database=test_development collection=books selector={"$query"=>{"deleted_at"=>nil, "user_id"=><BSON::ObjectId:0x52474000 data=5653bead667261191d000000>}, "$orderby"=>{"created_at"=>-1}} flags=[] limit=0 skip=0 batch_size=nil fields=nil runtime: 0.8906ms
     =>[#<Book _id: 5653bf636672611948000004, created_at: 2015-11-24 01:37:39 UTC, updated_at: 2015-11-24 01:37:39 UTC, deleted_at: nil, name: "Consectetur nulla autem natus voluptatem saepe eum quia.", author: "Mrs. Hettie Jenkins", description: "Est iste aperiam blanditiis quod dolorum sint ullam.", country: nil, user_id: <BSON::ObjectId:0x52345920 data=5653bead667261191d000000>>ư
            [10] pry(main)> User.first.books.target
  MOPED: 127.0.0.1:27017 QUERY        database=test_development collection=users selector={"$query"=>{"deleted_at"=>nil}, "$orderby"=>{:_id=>1}} flags=[] limit=-1 skip=0 batch_size=nil fields=nil runtime: 0.4310ms
  MOPED: 127.0.0.1:27017 QUERY        database=test_development collection=books selector={"$query"=>{"deleted_at"=>nil, "user_id"=><BSON::ObjectId:0x51830620 data=5653bead667261191d000000>}, "$orderby"=>{"created_at"=>-1}} flags=[] limit=0 skip=0 batch_size=nil fields=nil runtime: 0.9328ms
=> [#<Book _id: 5653bf636672611948000004, created_at: 2015-11-24 01:37:39 UTC, updated_at: 2015-11-24 01:37:39 UTC, deleted_at: nil, name: "Consectetur nulla autem natus voluptatem saepe eum quia.", author: "Mrs. Hettie Jenkins", description: "Est iste aperiam blanditiis quod dolorum sint ullam.", country: nil, user_id: <BSON::ObjectId:0x51775860 data=5653bead667261191d000000>>]
      [11] pry(main)> User.first.books.base
  MOPED: 127.0.0.1:27017 COMMAND      database=admin command={:ismaster=>1} runtime: 0.7714ms
  MOPED: 127.0.0.1:27017 QUERY        database=test_development collection=users selector={"$query"=>{"deleted_at"=>nil}, "$orderby"=>{:_id=>1}} flags=[] limit=-1 skip=0 batch_size=nil fields=nil runtime: 1.1108ms
=> #<User _id: 5653bead667261191d000000, created_at: 2015-11-24 01:34:37 UTC, updated_at: 2015-11-24 01:34:37 UTC, deleted_at: nil, first_name: "Alfredo", last_name: "Keebler", age: 18, address: "67756 Reba Place">
0