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 ...
- 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
- Viewing Changes
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.
- 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...