code: func CastAssoc(ch *Changeset, field string, fn ChangeFunc, opts ...Option) {
  	options := Options{
  		message: CastAssocErrorMessage,
  	}
  	options.apply(opts)
  
  	sourceField := options.sourceField
  	if sourceField == "" {
  		sourceField = field
  	}
  
  	typ, texist := ch.types[field]
  	valid := true
  	if texist && ch.params.Exists(sourceField) {
  		if typ.Kind() == reflect.Struct {
  			valid = castOne(ch, sourceField, field, fn)
  		} else if typ.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Struct {
  			valid = castMany(ch, sourceField, field, fn)
  		}
  	}
  
  	if !valid {
  		msg := strings.Replace(options.message, "{field}", field, 1)
  		AddError(ch, field, msg)
  	}
  
  	_, found := ch.changes[field]
  	if options.required && !found {
  		options.message = CastAssocRequiredMessage
  		msg := strings.Replace(options.message, "{field}", field, 1)
  		AddError(ch, field, msg)
  	}
  }
docs: // CastAssoc casts association changes using changeset function.
  // Repo insert or update won't persist any changes generated by CastAssoc.