可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have the following model
class Person
include Mongoid::Document
embeds_many :tasks
end
class Task
include Mongoid::Document
embedded_in :commit, :inverse_of => :tasks
field :name
end
How can I ensure the following?
person.tasks.create :name => "create facebook killer"
person.tasks.create :name => "create facebook killer"
person.tasks.count == 1
different_person.tasks.create :name => "create facebook killer"
person.tasks.count == 1
different_person.tasks.count == 1
i.e. task names are unique within a particular person
Having checked out the docs on indexes I thought the following might work:
class Person
include Mongoid::Document
embeds_many :tasks
index [
["tasks.name", Mongo::ASCENDING],
["_id", Mongo::ASCENDING]
], :unique => true
end
but
person.tasks.create :name => "create facebook killer"
person.tasks.create :name => "create facebook killer"
still produces a duplicate.
The index config shown above in Person would translate into for mongodb
db.things.ensureIndex({firstname : 1, 'tasks.name' : 1}, {unique : true})
回答1:
Indexes are not unique by default. If you look at the Mongo Docs on this, uniqueness is an extra flag.
I don't know the exact Mongoid translation, but you're looking for something like this:
db.things.ensureIndex({firstname : 1}, {unique : true, dropDups : true})
回答2:
Can't you just put a validator on the Task?
validates :name, :uniqueness => true
That should ensure uniqueness within parent document.
回答3:
I don't believe this is possible with embedded documents. I ran into the same issue as you and the only workaround I found was to use a referenced document, instead of an embedded document and then create a compound index on the referenced document.
Obviously, a uniqueness validation isn't enough as it doesn't guard against race conditions. Another problem I faced with unique indexes was that mongoid's default behavior is to not raise any errors if validation passes and the database refuses to accept the document. I had to change the following configuration option in mongoid.yml:
persist_in_safe_mode: true
This is documented at http://mongoid.org/docs/installation/configuration.html
Finally, after making this change, the save/create methods will start throwing an error if the database refuses to store the document. So, you'll need something like this to be able to tell users about what happened:
alias_method :explosive_save, :save
def save
begin
explosive_save
rescue Exception => e
logger.warn("Unable to save record: #{self.to_yaml}. Error: #{e}")
errors[:base] << "Please correct the errors in your form"
false
end
end
Even this isn't really a great option because you're left guessing as to which fields really caused the error (and why). A better solution would be to look inside MongoidError and create a proper error message accordingly. The above suited my application, so I didn't go that far.
回答4:
Add a validation check, comparing the count of array of embedded tasks' IDs, with the count of another array with unique IDs from the same.
validates_each :tasks do |record, attr, tasks|
ids = tasks.map { |t| t._id }
record.errors.add :tasks, "Cannot have the same task more than once." unless ids.count == ids.uniq.count
end
Worked for me.
回答5:
You can define a validates_uniqueness_of on your Task model to ensure this, according to the Mongoid documentation at http://mongoid.org/docs/validation.html this validation applies to the scope of the parent document and should do what you want.
Your index technique should work too, but you have to generate the indexes before they brought into effect. With Rails you can do this with a rake task (in the current version of Mongoid its called db:mongoid:create_indexes). Note that you won't get errors when saving something that violates the index constraint because Mongoid (see http://mongoid.org/docs/persistence/safe_mode.html for more information).
回答6:
You can also specify the index in your model class:
index({ 'firstname' => 1, 'tasks.name' => 1}, {unique : true, drop_dups: true })
and use the rake task
rake db:mongoid:create_indexes
回答7:
you have to run :
db.things.ensureIndex({firstname : 1, 'tasks.name' : 1}, {unique : true})
directly on the database
You appear to including a "create index command" inside of your "active record"(i.e. class Person)