整理 Mongoid 中 set 方法的行为

整理一下Mongoid提供的set方法的行为。

设置不进行验证

class Item
  include Mongoid::Document

  field :name
  field :status

  validates_inclusion_of :status, in: ['open', 'hidden']
end

前提是status只能填入open或hidden。这个验证在保存时会被检查。

irb(main):001:0> item = Item.find_by(name: 'item01')
MONGODB | [7] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item01"}, "lsid"=>{"id"=><BSON::Binary:0x70200774064200 type=uuid data=0x83b34ce665194b82...>}}
MONGODB | [7] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):002:0> item.status = 'closed'
=> "closed"
irb(main):003:0> item.save
=> false
irb(main):004:0> item.errors
=> #<ActiveModel::Errors:0x00007fb198914640 @base=#<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "closed">, @messages={:status=>["is not included in the list"]}, @details={:status=>[{:error=>:inclusion, :value=>"closed"}]}>

代入してsave する代わりに set を使って即座に変更を反映しようとすると以下のようにバリデーションエラーにならずに保存される。

irb(main):005:0> item = Item.find_by(name: 'item01')
MONGODB | [8] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item01"}, "lsid"=>{"id"=><BSON::Binary:0x70200774064200 type=uuid data=0x83b34ce665194b82...>}}
MONGODB | [8] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):006:0> item.set(status: 'closed')
MONGODB | [9] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"items", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "u"=>{"$set"=>{"status"=>"closed"}}}], "lsid"=>{"id"=><BSON::Binary:0x70200774064200 type=uuid data=0x83b34ce665194b82...>}}
MONGODB | [9] localhost:28001 | sandbox.update | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "closed">
irb(main):007:0> Item.find_by(name: 'item01')
MONGODB | [10] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item01"}, "lsid"=>{"id"=><BSON::Binary:0x70200774064200 type=uuid data=0x83b34ce665194b82...>}}
MONGODB | [10] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "closed">

set はコールバックを呼ばない

在 item.rb 文件中添加 after_save。

class Item
  include Mongoid::Document

  field :name
  field :status

  validates_inclusion_of :status, in: ['open', 'hidden']
  after_save { |d| p "#{d.name}, #{d.status}" }
end

如果像先前一样设置并更新值,保存后会调用 after_save 方法。

irb(main):001:0> item = Item.find_by(name: 'item01')
MONGODB | [7] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item01"}, "lsid"=>{"id"=><BSON::Binary:0x70200973293780 type=uuid data=0x1b1ac6692cff4cd9...>}}
MONGODB | [7] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):002:0> item.status = 'hidden'
=> "hidden"
irb(main):003:0> item.save
MONGODB | [8] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"items", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "u"=>{"$set"=>{"status"=>"hidden"}}}], "lsid"=>{"id"=><BSON::Binary:0x70200973293780 type=uuid data=0x1b1ac6692cff4cd9...>}}
MONGODB | [8] localhost:28001 | sandbox.update | SUCCEEDED | 0.002s
"item01, hidden"
=> true

当使用set进行保存时,after_save方法不会被调用。

irb(main):004:0> item = Item.find_by(name: 'item01')
MONGODB | [9] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item01"}, "lsid"=>{"id"=><BSON::Binary:0x70200973293780 type=uuid data=0x1b1ac6692cff4cd9...>}}
MONGODB | [9] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "hidden">
irb(main):005:0> item.set(status: 'open')
MONGODB | [10] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"items", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "u"=>{"$set"=>{"status"=>"open"}}}], "lsid"=>{"id"=><BSON::Binary:0x70200973293780 type=uuid data=0x1b1ac6692cff4cd9...>}}
MONGODB | [10] localhost:28001 | sandbox.update | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">

将信息保存在数据库中的集合中

有一個 / 有很多

以下のようなモデルを想定する。

class Item
  include Mongoid::Document

  field :name
  field :status

  has_one :manufacturer

  validates_inclusion_of :status, in: ['open', 'hidden']
  after_save { |d| p "#{d.name}, #{d.status}" }
end
class Manufacturer
  include Mongoid::Document

  field :name
  field :country

  belongs_to :item

  validates_inclusion_of :country, in: ['japan']
  after_save { |d| p "manufacturer: #{d.name}, #{d.country}" }
end

has_one のフィールドに対して set する

以下のように undefined method `_association’ で NoMethodError になる。

irb(main):001:0> item = Item.find_by(name: 'item01')
MONGODB | [7] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item01"}, "lsid"=>{"id"=><BSON::Binary:0x70200973615600 type=uuid data=0x9c51763e627b42a7...>}}
MONGODB | [7] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):002:0> item.set('manufacturer.country' => 'canada')
MONGODB | [8] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"manufacturers", "filter"=>{"item_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70200973615600 type=uuid data=0x9c51763e627b42a7...>}}
MONGODB | [8] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
Traceback (most recent call last):
        1: from (irb):3
NoMethodError (undefined method `_association' for {"country"=>"canada"}:Hash)

has_one そのものを set する

Manufacturer.new した結果をセットすれば通る。
ただし、この場合別途 item.save しなければ item.manufacturer の結果は変わらない(これについては別記事で整理する)。
また、 Manufacturer 側のバリデーションが走るが、その結果がエラーの場合でも set はエラーを出力しない。

irb(main):003:0> item.set(manufacturer: {name: 'creator2', country: 'japan'})
Traceback (most recent call last):
        2: from (irb):4
        1: from (irb):4:in rescue in irb_binding'
NoMethodError (undefined method `_association' for {:name=>"creator2", :country=>"japan"}:Hash)
irb(main):004:0> item.set(manufacturer: Manufacturer.new(name: 'creator2', country: 'japan'))
MONGODB | [9] localhost:28001 #1 | sandbox.insert | STARTED | {"insert"=>"manufacturers", "ordered"=>true, "documents"=>[{"_id"=>BSON::ObjectId('5df7360f21b62043a2b04334'), "name"=>"creator2", "country"=>"japan", "item_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}], "lsid"=>{"id"=><BSON::Binary:0x702009736156...
MONGODB | [9] localhost:28001 | sandbox.insert | SUCCEEDED | 0.002s
"manufacturer: creator2, japan"
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):007:0> item.set(manufacturer: Manufacturer.new(name: 'creator3', country: 'canada')) # エラーが出ていないが Manufacturer の保存に失敗している
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">

通过与has_one字段相关联的变量设置

这种方法本质上与刚才解释的相同的步骤,无需进行验证检查或调用回调函数,直接保存。

irb(main):001:0> item = Item.find_by(name: 'item01')
MONGODB | [7] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item01"}, "lsid"=>{"id"=><BSON::Binary:0x70200374638340 type=uuid data=0x241a5c9ba6d748d7...>}}
MONGODB | [7] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):002:0> item.manufacturer
MONGODB | [8] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"manufacturers", "filter"=>{"item_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70200374638340 type=uuid data=0x241a5c9ba6d748d7...>}}
MONGODB | [8] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Manufacturer _id: 5df6ff5921b62081adb04334, name: "creator", country: "japan", item_id: BSON::ObjectId('5df5288495243d5498999e2a')>
irb(main):003:0> item.manufacturer.set(country: 'canada')
MONGODB | [9] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"manufacturers", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5df6ff5921b62081adb04334')}, "u"=>{"$set"=>{"country"=>"canada"}}}], "lsid"=>{"id"=><BSON::Binary:0x70200374638340 type=uuid data=0x241a5c9ba6d748d7...>}}
MONGODB | [9] localhost:28001 | sandbox.update | SUCCEEDED | 0.002s
=> #<Manufacturer _id: 5df6ff5921b62081adb04334, name: "creator", country: "canada", item_id: BSON::ObjectId('5df5288495243d5498999e2a')>
irb(main):004:0> item.reload
MONGODB | [10] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "lsid"=>{"id"=><BSON::Binary:0x70200374638340 type=uuid data=0x241a5c9ba6d748d7...>}}
MONGODB | [10] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):005:0> item.manufacturer
MONGODB | [11] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"manufacturers", "filter"=>{"item_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70200374638340 type=uuid data=0x241a5c9ba6d748d7...>}}
MONGODB | [11] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Manufacturer _id: 5df6ff5921b62081adb04334, name: "creator", country: "canada", item_id: BSON::ObjectId('5df5288495243d5498999e2a')>

嵌入一个/嵌入多个

设想以下的模型。

class Item
  include Mongoid::Document

  field :name
  field :status

  embeds_one :manufacturer

  validates_inclusion_of :status, in: ['open', 'hidden']
  after_save { |d| p "#{d.name}, #{d.status}" }
end
class Manufacturer
  include Mongoid::Document

  field :name
  field :country

  embedded_in :item

  validates_inclusion_of :country, in: ['japan']
  after_save { |d| p "manufacturer: #{d.name}, #{d.country}" }
end

对于embeds_one字段进行设置

这不会出错。

irb(main):001:0> item = Item.find_by(name: 'item01')
MONGODB | [7] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item01"}, "lsid"=>{"id"=><BSON::Binary:0x70200373029300 type=uuid data=0x049d23d448434f31...>}}
MONGODB | [7] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):002:0> item.set('manufacturer.name' => 'creator02')
MONGODB | [8] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"items", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "u"=>{"$set"=>{"manufacturer"=>{"_id"=>BSON::ObjectId('5df747b521b6202786b04334'), "name"=>"creator02", "country"=>"japan"}}}}], "lsid"=>{"id"=...
MONGODB | [8] localhost:28001 | sandbox.update | SUCCEEDED | 0.002s
"manufacturer: creator02, japan"
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">

然而,需要注意的是使用这种方法。
因为如果尝试多次使用相同的方法,会导致错误。
可以通过重新加载来解决问题,但本来就不应该使用这种方法来更新。

irb(main):003:0> item.set('manufacturer.name' => 'creator03')
Traceback (most recent call last):
        1: from (irb):3
FrozenError (can't modify frozen BSON::Document)
irb(main):004:0> item.reload
MONGODB | [9] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "lsid"=>{"id"=><BSON::Binary:0x70200372631360 type=uuid data=0xd67817a79db44896...>}}
MONGODB | [9] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):005:0> item.set('manufacturer.name' => 'creator04')
MONGODB | [10] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"items", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "u"=>{"$set"=>{"manufacturer"=>{"_id"=>BSON::ObjectId('5df747b521b6202786b04334'), "name"=>"creator04", "country"=>"japan"}}}}], "lsid"=>{"id"=...
MONGODB | [10] localhost:28001 | sandbox.update | SUCCEEDED | 0.002s
"manufacturer: creator04, japan"
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">

顺便提一句,在这种情况下也会进行验证。

irb(main):001:0> item = Item.find_by(name: 'item01')
MONGODB | [7] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item01"}, "lsid"=>{"id"=><BSON::Binary:0x70200772647620 type=uuid data=0x1f23c2846fb44ee3...>}}
MONGODB | [7] localhost:28001 | sandbox.find | SUCCEEDED | 0.001s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):002:0> item.set('manufacturer.country' => 'canada')
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):003:0> item.manufacturer.errors
=> #<ActiveModel::Errors:0x00007fb1a808c6e8 @base=#<Manufacturer _id: 5df747b521b6202786b04334, name: "creator", country: "canada">, @messages={:country=>["is not included in the list"]}, @details={:country=>[{:error=>:inclusion, :value=>"canada"}]}>

将embeds_one的本身设为set。

与 has_one 不同,hash 也可以用于设置 set。
制造商的回调被调用并且验证也被检查。

irb(main):001:0> item = Item.find_by(name: 'item01')
MONGODB | [7] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item01"}, "lsid"=>{"id"=><BSON::Binary:0x70200372582840 type=uuid data=0x8193f419dbb54950...>}}
MONGODB | [7] localhost:28001 | sandbox.find | SUCCEEDED | 0.003s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):002:0> item.set(manufacturer: {name: 'creator02', country: 'japan'})
MONGODB | [8] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"items", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "u"=>{"$set"=>{"manufacturer"=>{"_id"=>BSON::ObjectId('5df74aaa21b6204c99b04334'), "name"=>"creator02", "country"=>"japan"}}}}], "lsid"=>{"id"=...
MONGODB | [8] localhost:28001 | sandbox.update | SUCCEEDED | 0.002s
"manufacturer: creator02, japan"
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):003:0> item.set(manufacturer: {name: 'creator03', country: 'japan'})
MONGODB | [9] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"items", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "u"=>{"$set"=>{"manufacturer"=>{"_id"=>BSON::ObjectId('5df74ab921b6204c99b04335'), "name"=>"creator03", "country"=>"japan"}}}}], "lsid"=>{"id"=...
MONGODB | [9] localhost:28001 | sandbox.update | SUCCEEDED | 0.003s
"manufacturer: creator03, japan"
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):004:0> item.set(manufacturer: {name: 'creator04', country: 'canada'})
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):005:0> item.manufacturer.errors
=> #<ActiveModel::Errors:0x00007fb198a752c8 @base=#<Manufacturer _id: 5df74ad921b6204c99b04336, name: "creator04", country: "canada">, @messages={:country=>["is not included in the list"]}, @details={:country=>[{:error=>:inclusion, :value=>"canada"}]}>

もちろん Manufacturer のインスタンスを代入して set もできる。

irb(main):006:0> item.set(manufacturer: Manufacturer.new(name: 'creator05', country: 'japan'))
MONGODB | [10] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"items", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "u"=>{"$set"=>{"manufacturer"=>{"_id"=>BSON::ObjectId('5df74b6c21b6204c99b04337'), "name"=>"creator05", "country"=>"japan"}}}}], "lsid"=>{"id"=...
MONGODB | [10] localhost:28001 | sandbox.update | SUCCEEDED | 0.002s
"manufacturer: creator05, japan"
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">

通过与embeds_one字段相关联的变量进行设置

我认为从目前的情况来看这是显而易见的,这当然是可以通过的。

irb(main):001:0> item = Item.find_by(name: 'item01')
MONGODB | [7] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item01"}, "lsid"=>{"id"=><BSON::Binary:0x70200375178620 type=uuid data=0xbf7b298badda4b22...>}}
MONGODB | [7] localhost:28001 | sandbox.find | SUCCEEDED | 0.001s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):002:0> item.manufacturer
=> #<Manufacturer _id: 5df74b6c21b6204c99b04337, name: "creator", country: "japan">
irb(main):003:0> item.manufacturer.set(country: 'canada')
MONGODB | [8] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"items", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5df5288495243d5498999e2a'), "manufacturer._id"=>BSON::ObjectId('5df74b6c21b6204c99b04337')}, "u"=>{"$set"=>{"manufacturer.country"=>"canada"}}}], "lsid"=>{"id"=><BSON::Bina...
MONGODB | [8] localhost:28001 | sandbox.update | SUCCEEDED | 0.002s
=> #<Manufacturer _id: 5df74b6c21b6204c99b04337, name: "creator", country: "canada">
irb(main):004:0> item.reload
MONGODB | [9] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"_id"=>BSON::ObjectId('5df5288495243d5498999e2a')}, "lsid"=>{"id"=><BSON::Binary:0x70200375178620 type=uuid data=0xbf7b298badda4b22...>}}
MONGODB | [9] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Item _id: 5df5288495243d5498999e2a, name: "item01", status: "open">
irb(main):005:0> item.manufacturer
=> #<Manufacturer _id: 5df74b6c21b6204c99b04337, name: "creator", country: "canada">

总结

    • set を使ってフィールドを変更すると、バリデーションは検証されないしコールバックは呼ばれない

 

    • has/embeds なフィールドに対しての set はやり方によってはできる

多くのケースではバリデーションは検証されるしコールバックも呼ばれる

bannerAds