public function save(
        $updateFields = null,
        $forceInsert = false,
        $forceUpdate = false,
        $connection = null
    ) {
        // Ensure that a model instance without a PK hasn't been assigned to
        // a ForeignKey or OneToOneField on this model. If the field is
        // nullable, allowing the save() would result in silent data loss.
        /* @var $relObject Model */
        /** @var $field Field */
        foreach ($this->getMeta()->getConcreteFields() as $name => $field) {
            if ($field->isRelation) {
                //If the related field isn't cached, then an instance hasn't
                //been assigned and there's no need to worry about this check.
                if ($this->hasProperty($field->getCacheName()) &&
                    $this->{$field->getCacheName()}) {
                    continue;
                }

                $relObject = $this->{$field->getName()};

                if ($relObject && is_null($relObject->getMeta()->primaryKey)) {
                    throw new ValueError(
                        sprintf(
                            'save() prohibited to prevent data loss '.
                            "due to unsaved related object '%s'.",
                            $field->getName()
                        )
                    );
                }
            }
        }

        if ($forceInsert && ($forceInsert || $forceUpdate)) {
            throw new ValueError(
                'Cannot force both insert and updating'.
                ' in model saving.'
            );
        }

        $deferedFields = $this->getDeferredFields();

        // if we got update_fields, ensure we got fields actually exist on
        // the model
        if ($updateFields) {
            $modelFields = $this->getMeta()->getNonM2MForwardFields();
            $fieldsNames = [];
            /** @var $modelField Field */
            foreach ($modelFields as $name => $modelField) {
                if ($modelField->primaryKey) {
                    continue;
                }
                $fieldsNames[] = $modelField->getName();

                // if attribute name and the field name provided arent the same,
                // add also add the attribute name.e.g. in related fields
                if ($modelField->getName() !== $modelField->getAttrName()) {
                    $fieldsNames[] = $modelField->getAttrName();
                }
            }

            $nonModelFields = array_diff($updateFields, $fieldsNames);
            if ($nonModelFields) {
                throw new ValueError(
                    sprintf(
                        'The following fields do not exist in this '.
                        'model or are m2m fields: %s'.implode(
                            ', ',
                            $nonModelFields
                        )
                    )
                );
            }
        } elseif (!$forceInsert && $deferedFields) {
            // if we have some deferred fields, we need to set the fields to
            // update as the onces that were loaded.
            $concreteFields = $this->getMeta()->getConcreteFields();

            $fieldsNames = [];
            /** @var $concreteField Field */
            foreach ($concreteFields as $name => $concreteField) {
                if (!$concreteField->primaryKey &&
                    !$concreteField->hasProperty('through')) {
                    $fieldsNames[] = $concreteField->getName();
                }
            }
            $loadedFields = array_diff($fieldsNames, $deferedFields);

            if ($loadedFields) {
                $updateFields = $loadedFields;
            }
        }

        $this->prepareSave($updateFields);
    }