Mongoid学习

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 有两个重要的规则

  1. 如果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

  1. 如果attribute 在document中不存在,Mongoid不提供 getter 和
    setter,并且会强制抛出异常。在这种情况下,你可以使用
    [] 或 []= 或 read_attribute 或 write_attribute 方法

  >> bb = Teacher.new
  => #
  >> 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 #

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
  => #"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
=> #
>> p.addresses
=> [#
, #
] >> p.addresses.target => [#
, #
] >> p.addresses.base => # >> p.addresses.metadata => #

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
=> #
>> p.addresses
=> [#
, #
] >> p.addresses.the_one => [#
] >> p.addresses.find_by_name('two') => #

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

ruby-mongoid-tutorial

你可能感兴趣的:(Mongoid学习)