lib/mongoid/persistable/creatable.rb
# frozen_string_literal: true
# rubocop:todo all
module Mongoid
module Persistable
# Defines behavior for persistence operations that create new documents.
module Creatable
extend ActiveSupport::Concern
# Insert a new document into the database. Will return the document
# itself whether or not the save was successful.
#
# @example Insert a document.
# document.insert
#
# @param [ Hash ] options Options to pass to insert.
#
# @return [ Document ] The persisted document.
def insert(options = {})
prepare_insert(options) do
if embedded?
insert_as_embedded
else
insert_as_root
end
end
end
private
# Get the atomic insert for embedded documents, either a push or set.
#
# @api private
#
# @example Get the inserts.
# document.inserts
#
# @return [ Hash ] The insert ops.
def atomic_inserts
{ atomic_insert_modifier => { atomic_position => as_attributes }}
end
# Insert the embedded document.
#
# @api private
#
# @example Insert the document as embedded.
# document.insert_as_embedded
#
# @return [ Document ] The document.
def insert_as_embedded
raise Errors::NoParent.new(self.class.name) unless _parent
if _parent.new_record?
_parent.insert
else
selector = _parent.atomic_selector
_root.collection.find(selector).update_one(
positionally(selector, atomic_inserts),
session: _session)
end
end
# Insert the root document.
#
# @api private
#
# @example Insert the document as root.
# document.insert_as_root
#
# @return [ Document ] The document.
def insert_as_root
collection.insert_one(as_attributes, session: _session)
end
# Post process an insert, which sets the new record attribute to false
# and flags all the children as persisted.
#
# @api private
#
# @example Post process the insert.
# document.post_process_insert
#
# @return [ true ] true.
def post_process_insert
self.new_record = false
remember_storage_options!
flag_descendants_persisted
true
end
# Prepare the insert for execution. Validates and runs callbacks, etc.
#
# @api private
#
# @example Prepare for insertion.
# document.prepare_insert do
# collection.insert(as_document)
# end
#
# @param [ Hash ] options The options.
#
# @return [ Document ] The document.
def prepare_insert(options = {})
raise Errors::ReadonlyDocument.new(self.class) if readonly? && !Mongoid.legacy_readonly
return self if performing_validations?(options) &&
invalid?(options[:context] || :create)
ensure_client_compatibility!
run_callbacks(:commit, with_children: true, skip_if: -> { in_transaction? }) do
run_callbacks(:save, with_children: false) do
run_callbacks(:create, with_children: false) do
run_callbacks(:persist_parent, with_children: false) do
_mongoid_run_child_callbacks(:save) do
_mongoid_run_child_callbacks(:create) do
result = yield(self)
if !result.is_a?(Document) || result.errors.empty?
post_process_insert
post_process_persist(result, options)
end
end
end
end
end
end
end
self
end
module ClassMethods
# Create a new document. This will instantiate a new document and
# insert it in a single call. Will always return the document
# whether save passed or not.
#
# @example Create a new document.
# Person.create(:title => "Mr")
#
# @example Create multiple new documents.
# Person.create({ title: "Mr" }, { title: "Mrs" })
#
# @param [ Hash | Array ] attributes The attributes to create with, or an
# Array of multiple attributes for multiple documents.
#
# @return [ Document | Array<Document> ] The newly created document(s).
def create(attributes = nil, &block)
_creating do
if attributes.is_a?(::Array)
attributes.map { |attrs| create(attrs, &block) }
else
doc = new(attributes, &block)
doc.save
doc
end
end
end
# Create a new document. This will instantiate a new document and
# insert it in a single call. Will always return the document
# whether save passed or not, and if validation fails an error will be
# raise.
#
# @example Create a new document.
# Person.create!(:title => "Mr")
#
# @example Create multiple new documents.
# Person.create!({ title: "Mr" }, { title: "Mrs" })
#
# @param [ Hash | Array ] attributes The attributes to create with, or an
# Array of multiple attributes for multiple documents.
#
# @return [ Document | Array<Document> ] The newly created document(s).
def create!(attributes = nil, &block)
_creating do
if attributes.is_a?(::Array)
attributes.map { |attrs| create!(attrs, &block) }
else
doc = new(attributes, &block)
doc.fail_due_to_validation! unless doc.insert.errors.empty?
doc.fail_due_to_callback!(:create!) if doc.new_record?
doc
end
end
end
end
end
end
end