1. 安裝mongoid
在Rails 配置文件Gemfile中,做如下配置
gem 'mongoid', '~> 5.0.0'
2. 配置文件
執行 rails g mongoid:config
即可生成
myapp/config/mongoid.yml
在該配置文件中,你可以定制自己的配置
development:
clients:
default:
database: mongoid
hosts:
- localhost:27017
還需要在 application.rb 文件中做如下配置,設置關系映射使用mongoid
config.generators do |g|
g.orm :mongoid
end
3. Document
Document可以作為集合存儲,也可以鑲嵌到其他的Document中
Documents can be stored in their own collections in the database
or can be embedded in other Documents n levels deep.
4. 存儲
class Person
include Mongoid::Document
end
你可以在類級別更改該Person的存儲位置
class Person
include Mongoid::Document
store_in collection: "citizens", database: "other", client: "secondary"
end
5. Fields
通過網絡傳輸的String 參數,
Mongoid 提供了一個簡單的機制來將他們轉換成正確合適的類型,
通過定field定義
class Person
include Mongoid::Document
field :first_name, type: String
field :middle_name, type: String
field :last_name, type: String
end
除了String, 還有如下類型:
Array
BigDecimal
Boolean
Date
DateTime
Float
Hash
Integer
BSON::ObjectId
BSON::Binary
Range
Regexp
String
Symbol
Time
TimeWithZone
6. 訪問設置Field
如果field已經定義,Mongoid提供了多種方式來訪問field
# Get the value of the first_name field
person.first_name
person[:first_name]
person.read_attribute(:first_name)
# Set the value for the first_name field
person.first_name = "Jean"
person[:first_name] = "Jean"
person.write_attribute(:first_name, "Jean")
通過以下途徑訪問設置多值
# Get the field values as a hash.
person.attributes
# Set the field values in the document.
Person.new(first_name: "Jean-Baptiste", middle_name: "Emmanuel")
person.attributes = { first_name: "Jean-Baptiste", middle_name: "Emmanuel" }
person.write_attributes(
first_name: "Jean-Baptiste",
middle_name: "Emmanuel"
)
存儲設置Hash Field
class Person
include Mongoid::Document
field :first_name
field :url, type: Hash
# will update the fields properly and save the values
def set_vals
self.first_name = 'Daniel'
self.url = {'home_page' => 'http://www.homepage.com'}
save
end
end
存儲到數據庫具體形式如下:
{ "_id" : ObjectId("56877d3408d67e50cb000008"), "first_name" : "zhangsan", "url" : { "home_page" : "http://www.homepage.com" }, "updated_at" : ISODate("2016-01-02T07:33:34.721Z"), "created_at" : ISODate("2016-01-02T07:33:34.721Z") }
7. 設置默認值
class Person
include Mongoid::Document
field :blood_alcohol_level, type: Float, default: 0.40
field :last_drink, type: Time, default: ->{ 10.minutes.ago }
end
或者根據Object的狀態來設置默認值
field :intoxicated_at, type: Time, default: ->{ new_record? ? 2.hours.ago : Time.now }
8. Custom Ids
如果你不想使用BSON::ObjectID ids, 你可以覆蓋 Mongoid`s _id
值,只要你喜歡樂意。
class Band
include Mongoid::Document
field :name, type: String
field :_id, type: String, default: ->{ name }
end
9. Dynamic Fields
默認情況Mongoid 不支持 dynamic fields,
你可以告訴mongoid支持dynamic fields 通過配置
including Mongoid::Attributes::Dynamic in model
class Person
include Mongoid::Document
include Mongoid::Attributes::Dynamic
end
關于dynamic fields 有兩個重要的規則
- 如果attribute 在document中存在,Mongoid 提供標準的getter 和 setter
# Set the person's gender to male.
person[:gender] = "Male"
person.gender = "Male"
# Get the person's gender.
person.gender
- 如果attribute 在document中不存在,Mongoid不提供 getter 和
setter,并且會強制拋出異常。在這種情況下,你可以使用
[] 或 []= 或 read_attribute 或 write_attribute 方法
>> bb = Teacher.new
=> #<Teacher _id: 5688bdb908d67e29cd000004, >
>> bb[:gender]
=> nil
>> bb.read_attribute(:gender)
=> nil
>> bb[:gender] = "Male"
=> "Male"
>> bb.write_attribute(:gender, "Female")
=> "Female"
>> bb[:gender]
=> "Female"
>> bb.gender
=> "Female"
>> bb.name
NoMethodError: undefined method `name' for #<Teacher _id: 5688bdb908d67e29cd000004, gender: "Female">
10. 查看attribute changes變化
class Person
include Mongoid::Document
field :name, type: String
end
person = Person.first
person.name = "Alan Garner"
# Check to see if the document has changed.
person.changed? #=> true
# Get an array of the names of the changed fields.
person.changed #=> [ :name ]
# Get a hash of the old and changed values for each field.
person.changes #=> { "name" => [ "Alan Parsons", "Alan Garner" ] }
# Check if a specific field has changed.
person.name_changed? #=> true
# Get the changes for a specific field.
person.name_change #=> [ "Alan Parsons", "Alan Garner" ]
# Get the previous value for a field.
person.name_was #=> "Alan Parsons"
11. 重置changes
person = Person.first
person.name = "Alan Garner"
# Reset the changed name back to the original
person.reset_name!
person.name #=> "Alan Parsons"
12. 關于changes 和 save的關系
如果changes沒有任何變更,Mongoid 也不會變更數據庫的內容 通過Model#save
方法
>> bb = Student.find(BSON::ObjectId("56877eae08d67e50cb00000b"))
MONGODB | localhost:27017 | funds_development.find | STARTED | {"find"=>"students", "filter"=>{"_id"=>BSON::ObjectId('56877eae08d67e50cb00000b')}}
MONGODB | localhost:27017 | funds_development.find | SUCCEEDED | 0.010262s
=> #<Student _id: 56877eae08d67e50cb00000b, created_at: 2016-01-02 07:39:26 UTC, updated_at: 2016-01-02 07:43:52 UTC, name: "jean", url: {"home_page"=>"www.jd.com"}, last_drink: 2016-01-02 07:29:26 UTC, intoxicated_at: 2016-01-02 05:39:26 UTC>
>> bb.attributes
=> {"_id"=>BSON::ObjectId('56877eae08d67e50cb00000b'), "name"=>"jean", "url"=>{"home_page"=>"www.jd.com"}, "last_drink"=>2016-01-02 07:29:26 UTC, "intoxicated_at"=>2016-01-02 05:39:26 UTC, "updated_at"=>2016-01-02 07:43:52 UTC, "created_at"=>2016-01-02 07:39:26 UTC}
>> bb.updated_at
=> Sat, 02 Jan 2016 07:43:52 UTC +00:00
>> bb.updated_at.to_s(:db)
=> "2016-01-02 07:43:52"
>> bb.save
=> true
>> Student.find(BSON::ObjectId("56877eae08d67e50cb00000b")).updated_at.to_s(:db)
MONGODB | localhost:27017 | funds_development.find | STARTED | {"find"=>"students", "filter"=>{"_id"=>BSON::ObjectId('56877eae08d67e50cb00000b')}}
MONGODB | localhost:27017 | funds_development.find | SUCCEEDED | 0.0007s
=> "2016-01-02 07:43:52"
13. Readonly Attributes
你可以設置某些field為只讀,創建之后便不能修改。
class Band
include Mongoid::Document
field :name, type: String
field :origin, type: String
attr_readonly :name, :origin
end
band = Band.create(name: "Placebo")
band.update_attributes(name: "Tool") # Filters out the name change.
如果你強制更新或修改某一個只讀的field,便會拋出異常。
band.update_attribute(:name, "Tool") # Raises the error.
band.remove_attribute(:name) # Raises the error.
14. 繼承
Document 繼承來自與他們的fields, relations, validations 或者是
scopes 來自于他們的child documents.
class Canvas
include Mongoid::Document
field :name, type: String
embeds_many :shapes
end
class Browser < Canvas
field :version, type: Integer
scope :recent, where(:version.gt => 3)
end
class Firefox < Browser
end
class Shape
include Mongoid::Document
field :x, type: Integer
field :y, type: Integer
embedded_in :canvas
end
class Circle < Shape
field :radius, type: Float
end
class Rectangle < Shape
field :width, type: Float
field :height, type: Float
end
**Querying Subclasses**
# Returns Canvas documents and subclasses
Canvas.where(name: "Paper")
# Returns only Firefox documents
Firefox.where(name: "Window 1")
**Associations**
```
firefox = Firefox.new
# Builds a Shape object
firefox.shapes.build({ x: 0, y: 0 })
# Builds a Circle object
firefox.shapes.build({ x: 0, y: 0 }, Circle)
# Creates a Rectangle object
firefox.shapes.create({ x: 0, y: 0 }, Rectangle)
rect = Rectangle.new(width: 100, height: 200)
firefox.shapes
```
15. 時間戳
Mongoid::Timestamps 提供了 created_at 和 updated_at field.
class Person
include Mongoid::Document
include Mongoid::Timestamps
end
或者你可以使用特殊的時間戳,creation 或 modification
class Person
include Mongoid::Document
include Mongoid::Timestamps::Created
end
class Post
include Mongoid::Document
include Mongoid::Timestamps::Updated
end
16. Persistence
Mongoid supports all expected CRUD operations for those familiar with
other Ruby mappers like Active Record or Data Mapper. What
distinguished Mongoid from other mappers for MongoDB is that the
general persistence operations perform atomic updated on only the
fields that have changed instead of writing the entire document to
the database each time.
Person.create(
first_name: "Heinrich",
last_name: "Heine"
)
Person.create([
{ first_name: "Heinrich", last_name: "Heine" },
{ first_name: "Willy", last_name: "Brandt" }
])
person.save
person.save(validate: false)
person.update_attributes!(
first_name: "Jean",
last_name: "Zorg"
)
person.update_attribute(:first_name, "Jean")
特殊的field用法
Model#upsert
如果這個attribute 存在,則覆蓋,如果沒有則插入。
person = Person.new(
first_name: "Heinrich",
last_name: "Heine"
)
person.upsert
Model#touch
更新updated_at 時間戳,也可以根據配置級聯更改關聯關系。
該方法跳過validation 和 callbacks
person.touch
person.touch(:audited_at)
Model#delete
刪除數據,并且沒有任何回調
Model#destory
刪除數據,并且執行回調
17. Atomic
當執行這些原子操作時,沒有回調,沒有校驗
person.add_to_set(aliases: "Bond") # $addToSet
person.bit(age: { and: 10, or: 12 }) # $bit
person.inc(age: 1) # $inc
person.pop(aliases: 1) # $pop
person.pull(aliases: "Bond") # $pull
18. 查詢
查詢 DSL
Band.where(name: "Depeche Mode")
Band.
where(:founded.gte => "1980-1-1").
in(name: [ "Tool", "Deftones" ]).
union.
in(name: [ "Melvins" ])
Additional Query Methods
操作 | 例子 |
---|---|
操作 | 例子 |
---- | ---- |
Criteria#count count 查詢會涉及到數據庫查詢 |
Band.count Band.where(name: "Photek").count |
Criteria#distinct 獲取單一field |
Band.distinct(:name) Band.where(:fans.gt => 100000).distinct(:name) |
Criteria#each 迭代獲取匹配數據 |
Band.where(members: 1).each do |
Criteria#exists? 判斷獲取數據是否存在 |
Band.exists? Band.where(name: "Photek").exists? |
Criteria#find 獲取一個文檔或多個文檔通過ids,如果沒有找到就會報錯 |
Band.find("4baa56f1230048567300485c") Band.find( "4baa56f1230048567300485c", "4baa56f1230048567300485d") |
Criteria#find_by 根據某一個field 來獲取文檔 |
Band.find_by(name: "Photek") |
Criteria#find_or_create_by 根據attributes來查找文檔,如果沒有找到,就創建該文檔 |
Band.where(:likes.gt => 10).find_or_create_by(name: "Photek") |
Criteria#find_or_initialize_by 查找文檔,若沒找到,則初始化 |
Band.where(:likes.gt => 10).find_or_initialize_by(name: "Photek") |
Criteria#first_or_create 根據attributes 查找第一個文檔,如果不存在,就創建 |
Band.where(name: "Photek").first_or_create |
Criteria#first_or_initialize 根據attributes 查找第一個文檔,如果不存在,就初始化 |
Band.where(name: "Photek").first_or_initialize |
Criteria#pluck 獲取不為nil的值 |
Band.all.pluck(:name) |
19. Eager Loading
Eager loaded is supported on all relations with the exception of polymorphic belongs_to associations.
class Band
include Mongoid::Document
has_many :albums
end
class Album
include Mongoid::Document
belongs_to :band
end
Band.includes(:albums).each do |band|
p band.albums.first.name # Does not hit the database again.
end
20. 關聯關系
>> p = Person.find('5687839408d67e50cb00000f')
MONGODB | localhost:27017 | funds_development.find | STARTED | {"find"=>"people", "filter"=>{"_id"=>BSON::ObjectId('5687839408d67e50cb00000f')}}
MONGODB | localhost:27017 | funds_development.find | SUCCEEDED | 0.005945s
=> #<Person _id: 5687839408d67e50cb00000f, first_name: "zhang", middle_name: "san", last_name: "bb">
>> p.addresses
=> [#<Address _id: 568783ce08d67e50cb000010, name: "one", addressable_type: nil, addressable_id: nil>, #<Address _id: 568783ce08d67e50cb000011, name: "two", addressable_type: nil, addressable_id: nil>]
>> p.addresses.target
=> [#<Address _id: 568783ce08d67e50cb000010, name: "one", addressable_type: nil, addressable_id: nil>, #<Address _id: 568783ce08d67e50cb000011, name: "two", addressable_type: nil, addressable_id: nil>]
>> p.addresses.base
=> #<Person _id: 5687839408d67e50cb00000f, first_name: "zhang", middle_name: "san", last_name: "bb">
>> p.addresses.metadata
=> #<Mongoid::Relations::Metadata
autobuild: false
class_name: Address
cyclic: nil
counter_cache:false
dependent: nil
inverse_of: nil
key: addresses
macro: embeds_many
name: addresses
order: nil
polymorphic: false
relation: Mongoid::Relations::Embedded::Many
setter: addresses=>
21. embeds_many 擴展
class Person
include Mongoid::Document
field :first_name, type: String
field :middle_name, type: String
field :last_name, type: String
embeds_many :addresses do
def find_by_name(name)
where(name: name).first
end
def the_one
@target.select{ |address| address.name == 'one' }
end
end
has_many :posts, autosave: true
end
p.addresses.the_one # return [ address]
p.addresses.find_by_name('aaa') # return address
>> p = Person.find('5687839408d67e50cb00000f')
MONGODB | localhost:27017 | funds_development.find | STARTED | {"find"=>"people", "filter"=>{"_id"=>BSON::ObjectId('5687839408d67e50cb00000f')}}
MONGODB | localhost:27017 | funds_development.find | SUCCEEDED | 0.000462s
=> #<Person _id: 5687839408d67e50cb00000f, first_name: "zhang", middle_name: "san", last_name: "bb">
>> p.addresses
=> [#<Address _id: 568783ce08d67e50cb000010, name: "one", addressable_type: nil, addressable_id: nil>, #<Address _id: 568783ce08d67e50cb000011, name: "two", addressable_type: nil, addressable_id: nil>]
>> p.addresses.the_one
=> [#<Address _id: 568783ce08d67e50cb000010, name: "one", addressable_type: nil, addressable_id: nil>]
>> p.addresses.find_by_name('two')
=> #<Address _id: 568783ce08d67e50cb000011, name: "two", addressable_type: nil, addressable_id: nil>
22. 定制關系的名字
你可以隨意定義關聯關系的名字,但是你需要填寫必要的選項來讓mongoid找到對應
的類。
class Lush
include Mongoid::Document
embeds_one :whiskey, class_name: "Drink", inverse_of: :alcoholic
end
class Drink
include Mongoid::Document
embedded_in :alcoholic, class_name: "Lush", inverse_of: :whiskey
end
23. 關聯關系校驗
默認情況下,如下的關聯關系,會存在校驗。
如果需要去掉關聯關系校驗,則可以在定義類的時候,就去除。
embeds_many
embeds_one
has_many
has_one
has_and_belongs_to_many
class Person
include Mongoid::Document
embeds_many :addresses, validate: false
has_many :posts, validate: false
end
24. embeds_many 與 has_many 的區別
addresses 屬于 embeds_many
posts 屬于 has_many
class Person
include Mongoid::Document
field :first_name, type: String
field :middle_name, type: String
field :last_name, type: String
embeds_many :addresses do
def find_by_name(name)
where(name: name).first
end
def the_one
@target.select{ |address| address.name == 'one' }
end
end
has_many :posts, autosave: true
end
> db.posts.find()
{ "_id" : ObjectId("5687862c08d67e29cd000001"), "name" : "101", "person_id" : ObjectId("5687839408d67e50cb00000f") }
> db.people.find({ "_id": ObjectId("5687839408d67e50cb00000f") })
{ "_id" : ObjectId("5687839408d67e50cb00000f"), "first_name" : "zhang", "middle_name" : "san", "last_name" : "bb", "addresses" : [ { "_id" : ObjectId("568783ce08d67e50cb000010"), "name" : "one" }, { "_id" : ObjectId("568783ce08d67e50cb000011"), "name" : "two" } ] }
由此可見,embeds_many 是嵌入到其他的document中
而has_many 是額外存在于其他的collections 中
25. 多態
一個child document 可以屬于多個parent document,在parent document
中添加 as
選項來告訴Mongoid, 并且在child document中設置 polymorphic
選項。在child選項中,根據這個額外添加的選項,就可以獲取到父類型。
多態存在于 has_and_belongs_to_many 的關系中。
class Band
include Mongoid::Document
embeds_many :photos, as: :photographic
has_one :address, as: :addressable
end
class Photo
include Mongoid::Document
embedded_in :photographic, polymorphic: true
end
26. 級聯回調
如果你想在父類上調用embedded document 的級聯,需要做如下設置
class Band
include Mongoid::Document
embeds_many :albums, cascade_callbacks: true
embeds_one :label, cascade_callbacks: true
end
band.save # Fires all save callbacks on the band, albums, and label.
27. 約束行為
如果關聯關系的一方刪除,可以設置關聯的另一方的關系
:delete: 刪除關系,不會調用什么回調
:destroy: 刪除關系,但是會調用回調函數
:nullify: 使關聯關系的另一方,孤立
:restrict: 如果child不為空,則拋出異常
class Band
include Mongoid::Document
has_many :albums, dependent: :delete
belongs_to :label, dependent: :nullify
end
class Album
include Mongoid::Document
belongs_to :band
end
class Label
include Mongoid::Document
has_many :bands, dependent: :restrict
end
label = Label.first
label.bands.push(Band.first)
label.delete # Raises an error since bands is not empty.
Band.first.delete # Will delete all associated albums.
28. 自動保存
Mongoid 和 Active Record 的另一個不同就是,不會自動
保存級聯關系,是為了性能考慮。
鑲嵌的關聯并不需要設置autosave,因為實際上他就是屬于父Object
的一部分。其余的關聯關系,都可以設置 autosave
class Band
include Mongoid::Document
has_many :albums, autosave: true
end
band = Band.first
band.albums.build(name: "101")
band.save #=> Will save the album as well.
29. 級聯關系中存在的實用方法
在所有關聯關系中都存在 name? 和 has_name? 的方法來校驗relation
是否是空的
class Band
include Mongoid::Document
embeds_one :label
embeds_many :albums
end
band.label?
band.has_label?
band.albums?
band.has_albums?
30. Embeds One
一對一的關聯關系中,使用embeds_one 和 embedded_in 來作為屬性關聯。
class Band
include Mongoid::Document
embeds_one :label
end
class Label
include Mongoid::Document
field :name, type: String
embedded_in :band
end
最后存儲的結果如下
{
"_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
"label" : {
"_id" : ObjectId("4d3ed089fb60ab534684b7e0"),
"name" : "Mute",
}
}
31. Embeds Many
一對多的嵌入關聯關系,使用 embeds_many 和 embedded_in
class Band
include Mongoid::Document
embeds_many :albums
end
class Album
include Mongoid::Document
field :name, type: String
embedded_in :band
end
最后存儲結果如下:
{
"_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
"albums" : [
{
"_id" : ObjectId("4d3ed089fb60ab534684b7e0"),
"name" : "Violator",
}
]
}
32. Has One
一對一的關聯關系中,使用 has_one 和 belongs_to
class Band
include Mongoid::Document
has_one :studio
end
class Studio
include Mongoid::Document
field :name, type: String
belongs_to :band
end
最后存儲結果如下
# The parent band document.
{ "_id" : ObjectId("4d3ed089fb60ab534684b7e9") }
# The child studio document.
{
"_id" : ObjectId("4d3ed089fb60ab534684b7f1"),
"band_id" : ObjectId("4d3ed089fb60ab534684b7e9")
}
33. Has Many
一對多的關聯關系中,使用has_many 和 belongs_to
class Band
include Mongoid::Document
has_many :members
end
class Member
include Mongoid::Document
field :name, type: String
belongs_to :band
end
最終存儲結果如下
# The parent band document.
{ "_id" : ObjectId("4d3ed089fb60ab534684b7e9") }
# A child member document.
{
"_id" : ObjectId("4d3ed089fb60ab534684b7f1"),
"band_id" : ObjectId("4d3ed089fb60ab534684b7e9")
}
34. 多對多的關聯關系
has_and_belongs_to_many
第一種方式
class Band
include Mongoid::Document
has_and_belongs_to_many :tags
end
class Tag
include Mongoid::Document
field :name, type: String
has_and_belongs_to_many :bands
end
第二種方式
class Band
include Mongoid::Document
has_and_belongs_to_many :tags, inverse_of: nil
end
class Tag
include Mongoid::Document
field :name, type: String
end
最終存儲結果如下
# The band document.
{
"_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
"tag_ids" : [ ObjectId("4d3ed089fb60ab534684b7f2") ]
}
# The tag document.
{
"_id" : ObjectId("4d3ed089fb60ab534684b7f2"),
"band_ids" : [ ObjectId("4d3ed089fb60ab534684b7e9") ]
}
35. 回調函數
- after_initialize
- after_build
- before_validation
- after_validation
- before_create
- around_create
- after_create
- after_find
- before_update
- around_update
- after_update
- before_upsert
- around_upsert
- after_upsert
- before_save
- around_save
- after_save
- before_destroy
- around_destroy
- after_destroy
36. 索引
給普通的field添加索引
class Person
include Mongoid::Document
field :ssn
index({ ssn: 1 }, { unique: true, name: "ssn_index" })
end
給 embedded document 添加索引
class Person
include Mongoid::Document
embeds_many :addresses
index "addresses.street" => 1
end
給多個列添加索引
class Person
include Mongoid::Document
field :first_name
field :last_name
index({ first_name: 1, last_name: 1 }, { unique: true })
end
使用下面命令添加索引
rake db:mongoid:create_indexes
亦提供了刪除所有二級索引的命令
rake db:mongoid:remove_indexes