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